四 . 樹莓派A20 GPIO中斷程序編寫(1基本處理)


1 參考資料

  1. \marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\arch\arm\plat-sunxi\include\plat\Irqs.h
  2. \marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\input\touchscreen\Gt818_ts.c

2 硬件原理圖

關(guān)于按鍵侠姑,在DVK521上為:


按鍵

我又從一份數(shù)據(jù)手冊中看到,PI7身笤,PI8糖声,PI9是沒有外部中斷功能的斤彼。如下圖所示:


中斷功能

還好,目前的按鍵是通過短接帽來連接PI7PI9的蘸泻,那么可以將短接帽拿掉琉苇,使用杜邦線連接PI10PI12。

現(xiàn)在我將Key2連接到PI10上悦施。那么KEY2的中斷引腳為EINT22并扇。

配置sys_config.fex文件:

[key_test_para]
key_test_enable     = 1
key2                = port:PI10<0><1><default><default>

3 軟件配置基礎(chǔ)知識(shí)

現(xiàn)在使用的樹莓派A20,是一個(gè)雙核A7的芯片抡诞,而這個(gè)屬于SMP架構(gòu)穷蛹,中斷處理方式也已經(jīng)和原先的理念大有不同。所以還是要知道關(guān)于linux的中斷原理昼汗,可以從網(wǎng)絡(luò)中獲取相關(guān)歷史性技術(shù)知識(shí)肴熏。

3.1 中斷

從A20的數(shù)據(jù)手冊中,可以看到外部中斷數(shù)到了EINT31顷窒。也就是說PIO中斷功能有32個(gè)蛙吏。

3.2 一些關(guān)于IO功能的API函數(shù)

下面列出的API函數(shù)是在 \linux-sunxi\arch\arm\plat-sunxi\Sys_config.c中。

/*
 * CSP_GPIO_Request_EX
 * 函數(shù)名稱:
 *
 * 參數(shù)說明:
 * main_name   傳進(jìn)的主鍵名稱鞋吉,匹配模塊(驅(qū)動(dòng)名稱)
 * sub_name    傳進(jìn)的子鍵名稱鸦做,如果是空,表示全部谓着,否則尋找到匹配的單獨(dú)GPIO
 *
 * 返回值  :0 :    err
 *          other: success
 *
 * 說明    :暫時(shí)沒有做沖突檢查
 */
u32 gpio_request_ex(char *main_name, const char *sub_name)  /* 設(shè)備申請GPIO函數(shù)擴(kuò)展接口 */
/*
 * CSP_GPIO_Set_One_PIN_IO_Status
 * Description:
 * 修改用戶申請過的GPIO中的某一個(gè)IO口的泼诱,輸入輸出狀態(tài)
 * Arguments  :
 *  p_handler    :    handler
 *  if_set_to_output_status    :    設(shè)置成輸出狀態(tài)還是輸入狀態(tài)
 *  gpio_name    :    要操作的GPIO的名稱
 */
__s32  gpio_set_one_pin_io_status(u32 p_handler, __u32 if_set_to_output_status,
                  const char *gpio_name)

/*
 * CSP_GPIO_Write_One_PIN_Value
 * Description:
 *  修改用戶申請過的GPIO中的某一個(gè)IO口的端口的電平
 * Arguments:
 *  p_handler    :    handler
 *  value_to_gpio:  要設(shè)置的電平的電壓
 *  gpio_name    :    要操作的GPIO的名稱
 */
__s32  gpio_write_one_pin_value(u32 p_handler, __u32 value_to_gpio,
                const char *gpio_name)
/*
 * CSP_GPIO_Set_One_PIN_Pull
 * Description:
 * 修改用戶申請過的GPIO中的某一個(gè)IO口的,PULL狀態(tài)
 * Arguments  :
 *        p_handler    :    handler
 *        if_set_to_output_status    :    所設(shè)置的pull狀態(tài)
 *        gpio_name    :    要操作的GPIO的名稱
 */
__s32  gpio_set_one_pin_pull(u32 p_handler, __u32 set_pull_status,
                 const char *gpio_name)

3.2 按鍵中斷中用到的API

1.gpio_request_ex()赊锚,獲取sys_config.fex中設(shè)置的中斷IO口治筒。
2.gpio_set_one_pin_io_status(),設(shè)置為輸入狀態(tài)改抡。
3.gpio_set_one_pin_pull()矢炼,設(shè)置輸入引腳的上下拉狀態(tài)。
4.request_irq()注冊中斷函數(shù)阿纤。

request_irq()函數(shù):
第一個(gè)參數(shù)為SW_INT_IRQNO_PIO,表示是外部端口的中斷號(hào)夷陋。
第二個(gè)參數(shù)為中斷處理函數(shù)名欠拾。
第三個(gè)參數(shù)為中斷方式胰锌,如IRQ_TYPE_EDGE_RISING,上升沿觸發(fā)藐窄,而IRQ_TYPE_EDGE_FALLING則是下降沿觸發(fā)资昧,IRQF_SHARED為共享。
第四個(gè)參數(shù)為中斷名荆忍。
第五個(gè)參數(shù)為中斷傳遞的數(shù)據(jù)格带。

3.3 中斷處理邏輯

1.獲取IO中斷源信息
由于內(nèi)核使用的是虛擬地址尋址硬件地址,獲取中斷源就需要將IO硬件地址空間映射到虛擬地址上刹枉∵闯可以使用ioremap(PIO_BASE_ADDRESS, PIO_RANGE_SIZE)進(jìn)行映射。

2.屏蔽中斷源
a.讀取中斷源寄存器的狀態(tài)微宝,可以使用readl(映射的IO地址 + PIO_INT_STAT_OFFSET);
b.判斷對應(yīng)的中斷棺亭,使用writel(reg_val&(1<<(CTP_IRQ_NO)),映射的IO地址 + PIO_INT_STAT_OFFSET);清除狀態(tài)位。

寫到這里蟋软,本應(yīng)該很順利镶摘,可是,在驅(qū)動(dòng)程序加載進(jìn)內(nèi)核的時(shí)候岳守,明顯是報(bào)錯(cuò)凄敢。錯(cuò)誤我就不貼出來了,可是我可以將中斷信息附上:

root@marsboard:~# cat /proc/interrupts 
           CPU0       CPU1       
 29:       3989       2679       GIC  arch_timer
 30:          0          0       GIC  arch_timer
 32:          0          0       GIC  axp_mfd
 33:        286          0       GIC  serial
 39:       1396          0       GIC  sunxi-i2c.0
 40:          0          0       GIC  sunxi-i2c.1
 41:          0          0       GIC  sunxi-i2c.2
 54:          0          0       GIC  timer0
 55:         12          0       GIC  aw_clock_event
 56:          2          0       GIC  sunxi-rtc alarm
 59:      35778          0       GIC  dma_irq
 60:          0          0       GIC  sunxi-gpio
 64:          0          0       GIC  sunxi-mmc
 69:       5876          0       GIC  nand
 71:          0          0       GIC  ehci_hcd:usb2
 72:          0          0       GIC  ehci_hcd:usb4
 76:       7001          0       GIC  sunxi lcd0
 77:          0          0       GIC  sunxi lcd1
 78:          0          0       GIC  g2d
 79:       3495          0       GIC  sunxi scaler0
 80:          0          0       GIC  sunxi scaler1
 85:          0          0       GIC  cedar_dev
 87:        419          0       GIC  eth0
 88:          0          0       GIC  sw_ahci
 92:          0          0       GIC  ace_dev
 96:          0          0       GIC  ohci_hcd:usb3
 97:          0          0       GIC  ohci_hcd:usb5
101:          0          0       GIC  mali_gp_irq_handlers
102:          0          0       GIC  mali_mmu_irq_handlers
103:          0          0       GIC  mali_pp_irq_handlers
104:          0          0       GIC  mali_mmu_irq_handlers
106:          0          0       GIC  mali_pp_irq_handlers
107:          0          0       GIC  mali_mmu_irq_handlers
IPI0:          0          0  Timer broadcast interrupts
IPI1:       1564       4112  Rescheduling interrupts
IPI2:          0          0  Function call interrupts
IPI3:          4         20  Single function call interrupts
IPI4:          0          0  CPU stop interrupts
IPI5:          0          0  CPU backtrace
Err:          0

從這里湿痢,可以看出來涝缝,PIO中斷號(hào)60已經(jīng)注冊進(jìn)內(nèi)核了。我們現(xiàn)在使用的一個(gè)IO中斷是被包含在里面的蒙袍。所以俊卤,需要在內(nèi)核中找到sunxi-gpio是怎么去注冊中斷,而我們就需要將我們的中斷程序內(nèi)容附加到已經(jīng)注冊的中斷上去害幅。

在 marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\gpio\Gpio-sunxi.c中我們可以找到函數(shù):

/* IRQ handler - redirect interrupts to virtual irq chip */
static irqreturn_t sunxi_gpio_irq_handler(int irq, void *devid)
{
    __u32 status = 0;
    int i = 0;
    struct sunxi_gpio_chip *sgpio = devid;
    status = readl(sgpio->gaddr + PIO_INT_STAT_OFFSET);

    for (i = 0; i < EINT_NUM; i++) {
        if ((status & (1 << i)) &&
            (gpio_eint_list[i].gpio >= 0)) {
            status &= ~(1 << i);
            SUNXI_CLEAR_EINT(sgpio->gaddr, i);
            generic_handle_irq(sgpio->irq_base + i);
        }
    }

    if (status)
        return IRQ_NONE;

    return IRQ_HANDLED;
}

里面最重要的函數(shù)是:

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:    The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)

最終調(diào)用的是:

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

然而消恍,它又被賦值了:

/* caller has locked the irq_desc and both params are valid */
static inline void __irq_set_handler_locked(unsigned int irq,
                        irq_flow_handler_t handler)
{
    struct irq_desc *desc;

    desc = irq_to_desc(irq);
    desc->handle_irq = handler;
}

在一定程度的意義上,gpio-sunxi.c已經(jīng)將中斷基本處理做好了以现,我們要做的只是和它共享中斷狠怨。

3.4 實(shí)踐邏輯

sys_config.fex文件配置如下:


sys_config.fex配置

從上面的實(shí)驗(yàn)中,已經(jīng)發(fā)現(xiàn)在request_irq中設(shè)置邊沿等等觸發(fā)邑遏,在安裝ko文件的時(shí)候佣赖,都會(huì)報(bào)錯(cuò),從這里看出记盒,在共享中斷的時(shí)候憎蛤,是不允許設(shè)置其他的內(nèi)容的。那么,只能去找A20寄存器中關(guān)于io口中斷的設(shè)置俩檬。在這些設(shè)置已經(jīng)設(shè)置好的情況下萎胰,中斷應(yīng)該就能響應(yīng)了。這里貼出一個(gè)比較簡單的驅(qū)動(dòng)程序:

#include "linux/init.h"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <plat/sys_config.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/irqs.h>
#include <mach/system.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <linux/gpio.h>

/* EINT type PIO controller registers */
#define PIO_INT_CFG0_OFFSET 0x200
#define PIO_INT_CFG1_OFFSET 0x204
#define PIO_INT_CFG2_OFFSET 0x208
#define PIO_INT_CFG3_OFFSET 0x20c

#define PIO_INT_STAT_OFFSET (0x214)
#define IRQ_EINT22          22
#define PIO_BASE_ADDRESS    SW_PA_PORTC_IO_BASE
#define PIO_RANGE_SIZE      (0x400)
#define PIO_INT_CTRL_OFFSET (0x210)

/* EINT type defines */
#define POSITIVE_EDGE       0x0
#define NEGATIVE_EDGE       0x1
#define HIGH_LEVEL      0x2
#define LOW_LEVEL       0x3
#define DOUBLE_EDGE     0x4

static int int_cfg_addr[] = {PIO_INT_CFG0_OFFSET,
                 PIO_INT_CFG1_OFFSET,
                 PIO_INT_CFG2_OFFSET,
                 PIO_INT_CFG3_OFFSET};

/* Setup GPIO irq mode (FALLING, RISING, BOTH, etc */
#define SUNXI_SET_GPIO_IRQ_TYPE(addr, offs, mode) ({ \
    __u32 reg_bit = offs % 8; \
    __u32 reg_num = offs / 8; \
    __u32 reg_val = readl(addr + int_cfg_addr[reg_num]); \
    reg_val &= (~(0xf << (reg_bit * 4))); \
    reg_val |= (mode << (reg_bit * 4)); \
    writel(reg_val, addr + int_cfg_addr[reg_num]); \
})

/* Enable GPIO interrupt for pin */
#define SUNXI_UNMASK_GPIO_IRQ(addr, irq) ({ \
    __u32 reg_val = readl(addr + PIO_INT_CTRL_OFFSET); \
    reg_val |= (1 << irq); \
    writel(reg_val, addr + PIO_INT_CTRL_OFFSET); \
})

/* Disable GPIO interrupt for pin */
#define SUNXI_MASK_GPIO_IRQ(addr, irq) ({ \
    __u32 reg_val = readl(addr + PIO_INT_CTRL_OFFSET); \
    reg_val &= ~(1 << irq); \
    writel(reg_val, addr + PIO_INT_CTRL_OFFSET); \
})

/* Set GPIO pin mode (input, output, etc)            */
/* GPIO port has 4 cfg 32bit registers (8 pins each) */
/* First port cfg register addr = port_num * 0x24    */
#define SUNXI_SET_GPIO_MODE(addr, port, pin, mode) ({ \
    __u32 reg_val = 0; \
    __u32 pin_idx = pin >> 3; \
    void *raddr = addr + (((port)-1)*0x24 + ((pin_idx)<<2) + 0x00); \
    reg_val = readl(raddr); \
    reg_val &= ~(0x07 << (((pin - (pin_idx<<3))<<2))); \
    reg_val |= mode << (((pin - (pin_idx<<3))<<2)); \
    writel(reg_val, raddr); \
})

static script_gpio_set_t info;
static unsigned key_handler;
static struct class *key_class;
static struct device *key_device;
static unsigned int key_major;

static void *__iomem gpio_addr = NULL;

static int key_open(struct inode *inode, struct file *filp);
static ssize_t key_write (struct file *filp, const char __user *buf, size_t len, loff_t *off);
static int key_close(struct inode *inode, struct file *filp);

struct file_operations key_operations = {
    .owner   = THIS_MODULE,
    .open    = key_open,
    .write   = key_write,
    .release = key_close,
};

struct key_str{
    char *name;
    int val;
};

struct key_str *g_key_str={"my_key",2};

static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    int reg_val;
    //clear the IRQ_EINT22 interrupt pending
    reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

    printk("key irq Interrupt\r\n");

#if 1
    if (reg_val & (1 << (IRQ_EINT22))) {
        printk("==IRQ_EINT22=\r\n");
        writel(reg_val & (1 << (IRQ_EINT22)),
               gpio_addr + PIO_INT_STAT_OFFSET);
        
    } else {
        printk("Other Interrupt\r\n");
        return IRQ_NONE;
    }
#endif
    return IRQ_HANDLED;
}

static int key_open(struct inode *inode, struct file *filp)
{
    int err = 0;
    int key_test_enabled = 0;
    int ret = 0;
    
    err = script_parser_fetch("key_test_para", "key_test_enable", &key_test_enabled,
                    sizeof(key_test_enabled)/sizeof(int));

    if(!err){
        printk("---script.bin key get ok,value:%d----\n",key_test_enabled);
    }
    else
    {
        printk("---script.bin key get false----\n");    
        return -1;
    }

    err = script_parser_fetch("key_test_para", "key2",
                (int *)&info,
                sizeof(script_gpio_set_t));
    if (err) {
        printk("----script.bin get io error----\r\n");
        return -1;
    }
    /* reserve gpio for led */
    key_handler = gpio_request_ex("key_test_para", "key2");
    if (!key_handler) {
        printk("----script.bin can't requst handler----\r\n");
        return -1;
    }

#if 0
    /*設(shè)置為輸入棚辽,沒有上下拉*/
    err = gpio_set_one_pin_io_status(key_handler,0,"key2");
    if (err) {
        printk("----set io input error----\r\n");
        return -1;
    }   
#endif

    err = gpio_set_one_pin_pull(key_handler,1,"key2");
    if (err) {
        printk("----set io pull error----\r\n");
        return -1;
    }
    
    if (!gpio_addr) {
        gpio_addr = ioremap(PIO_BASE_ADDRESS, PIO_RANGE_SIZE);
    }

    if(!gpio_addr)
    {
        printk("-----address error-----\r\n");
    }
    
    err = request_irq(SW_INT_IRQNO_PIO, key_irq_handler,
              IRQF_SHARED, "gpio_pin_2", g_key_str);

    if (err < 0) {
        printk(" request irq error:%d\n",err);
        return -1;
    }

    /*set the gpio register*/
    SUNXI_SET_GPIO_IRQ_TYPE(gpio_addr,IRQ_EINT22,POSITIVE_EDGE);    
    SUNXI_UNMASK_GPIO_IRQ(gpio_addr,IRQ_EINT22);
    SUNXI_SET_GPIO_MODE(gpio_addr,9,10,6);/*PI10 EINT22 settiings*/

    return 0;
}

static ssize_t key_write (struct file *filp, const char __user *buf, size_t len, loff_t *off)
{

    return 0;
}

static int key_close(struct inode *inode, struct file *filp)
{
    SUNXI_MASK_GPIO_IRQ(gpio_addr,IRQ_EINT22);

    free_irq(SW_INT_IRQNO_PIO, g_key_str);

    printk("----key close----\r\n");


    return 0;
}

static int __init key_init(void)
{
    key_major = register_chrdev(0, "key_chrdev", &key_operations);

    key_class = class_create(THIS_MODULE, "key_class");

    if(!key_class){
        unregister_chrdev(key_major, "key_chrdev");
        printk("----key_chrdev error----\r\n");
        return -1;
    }
    key_device = device_create(key_class, NULL, MKDEV(key_major,0),
                          NULL, "key_device");
    if(!key_device){
        class_destroy(key_class);
        unregister_chrdev(key_major, "key_chrdev");
        printk("----key_device error----\r\n");
        return -1;
    }

    printk("----key init ok----\r\n");
    return 0;
}

static void __exit  key_exit(void)
{
    if (gpio_addr) {
        iounmap(gpio_addr);
    }

    if (key_handler)
        gpio_release(key_handler, 1);
    
    device_destroy(key_class, MKDEV(key_major, 0));
    class_destroy(key_class);
    unregister_chrdev(key_major, "key_chrdev");

    printk("---driver exit---\r\n");
}

module_init(key_init);
module_exit(key_exit);

MODULE_DESCRIPTION("Driver for key");
MODULE_AUTHOR("wit_yuan");
MODULE_LICENSE("GPL");```

測試程序?yàn)椋?

include "stdio.h"

include <sys/types.h>

include <sys/stat.h>

include <fcntl.h>

int main(int argc,char *argv[])
{
int fd;
int val;
fd = open("/dev/key_device",O_RDWR);
if(fd < 0){
printf("---open file error----\r\n");
return -1;
}

while(1);

return 0;

}

也就是說技竟,只需要打開一個(gè)文件即可。

Makefile文件為:

ifeq ($(KERNELRELEASE),)
KERNEL_DIR=/home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.2/linux-sunxi
PWD=$(shell pwd)

modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc -o key key.c

modules_install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -rf *.ko *.o .tmp_versions .mod.c modules.order Module.symvers ..cmd
else
obj-m:=key.o
endif


測試結(jié)果為:

root@marsboard:~# ./key_test
---script.bin key get ok,value:1----
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
key irq Interrupt
==IRQ_EINT22=
^Ckey irq Interrupt
Other Interrupt
----key close----

root@marsboard:~#
root@marsboard:~#


ok屈藐,簡單的測試程序榔组,暫時(shí)就這樣了。下面再去優(yōu)化联逻。


### 3.5 polling查詢按鍵值
使用查詢按鍵值的程序代碼如下:
key.c驅(qū)動(dòng)程序:

include "linux/init.h"

include <linux/kernel.h>

include <linux/module.h>

include <linux/leds.h>

include <plat/sys_config.h>

include <linux/major.h>

include <linux/fs.h>

include <linux/device.h>

include <asm/io.h>

include <asm/uaccess.h>

include <linux/interrupt.h>

include <linux/ioport.h>

include <asm/irq.h>

include <asm/io.h>

include <mach/irqs.h>

include <mach/system.h>

include <asm/uaccess.h>

include <mach/hardware.h>

include <linux/gpio.h>

/* EINT type PIO controller registers */

define PIO_INT_CFG0_OFFSET 0x200

define PIO_INT_CFG1_OFFSET 0x204

define PIO_INT_CFG2_OFFSET 0x208

define PIO_INT_CFG3_OFFSET 0x20c

define PIO_INT_STAT_OFFSET (0x214)

define PIO_INT_DATA_OFFSET (0x130)

define IRQ_EINT22 22

define IRQ_EINT23 23

define PIO_BASE_ADDRESS SW_PA_PORTC_IO_BASE

define PIO_RANGE_SIZE (0x400)

define PIO_INT_CTRL_OFFSET (0x210)

/* EINT type defines */

define POSITIVE_EDGE 0x0

define NEGATIVE_EDGE 0x1

define HIGH_LEVEL 0x2

define LOW_LEVEL 0x3

define DOUBLE_EDGE 0x4

static int int_cfg_addr[] = {PIO_INT_CFG0_OFFSET,
PIO_INT_CFG1_OFFSET,
PIO_INT_CFG2_OFFSET,
PIO_INT_CFG3_OFFSET};

/* Setup GPIO irq mode (FALLING, RISING, BOTH, etc */

define SUNXI_SET_GPIO_IRQ_TYPE(addr, offs, mode) ({ \

__u32 reg_bit = offs % 8; \
__u32 reg_num = offs / 8; \
__u32 reg_val = readl(addr + int_cfg_addr[reg_num]); \
reg_val &= (~(0xf << (reg_bit * 4))); \
reg_val |= (mode << (reg_bit * 4)); \
writel(reg_val, addr + int_cfg_addr[reg_num]); \

})

/* Enable GPIO interrupt for pin */

define SUNXI_UNMASK_GPIO_IRQ(addr, irq) ({ \

__u32 reg_val = readl(addr + PIO_INT_CTRL_OFFSET); \
reg_val |= (1 << irq); \
writel(reg_val, addr + PIO_INT_CTRL_OFFSET); \

})

/* Disable GPIO interrupt for pin */

define SUNXI_MASK_GPIO_IRQ(addr, irq) ({ \

__u32 reg_val = readl(addr + PIO_INT_CTRL_OFFSET); \
reg_val &= ~(1 << irq); \
writel(reg_val, addr + PIO_INT_CTRL_OFFSET); \

})

/* Set GPIO pin mode (input, output, etc) /
/
GPIO port has 4 cfg 32bit registers (8 pins each) /
/
First port cfg register addr = port_num * 0x24 */

define SUNXI_SET_GPIO_MODE(addr, port, pin, mode) ({ \

__u32 reg_val = 0; \
__u32 pin_idx = pin >> 3; \
void *raddr = addr + (((port)-1)*0x24 + ((pin_idx)<<2) + 0x00); \
reg_val = readl(raddr); \
reg_val &= ~(0x07 << (((pin - (pin_idx<<3))<<2))); \
reg_val |= mode << (((pin - (pin_idx<<3))<<2)); \
writel(reg_val, raddr); \

})

static script_gpio_set_t info;
static unsigned key_handler1;
static unsigned key_handler2;

static struct class *key_class;
static struct device *key_device;
static unsigned int key_major;

static unsigned int key_value;

static void *__iomem gpio_addr = NULL;

static int key_open(struct inode *inode, struct file *filp);
static ssize_t key_read (struct file *, char __user *, size_t, loff_t *);
static ssize_t key_write (struct file *filp, const char __user *buf, size_t len, loff_t *off);
static int key_close(struct inode *inode, struct file *filp);

struct file_operations key_operations = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_close,
};

struct key_str{
char *name;
int val;
};

struct key_str g_key_str[2]={{"key1",0x1},{"key2",2}};

static irqreturn_t key_irq_handler1(int irq, void *dev_id)
{
int err;
int reg_val = 0;
int ret_val = 0;

struct key_str *key_t = (struct key_str *)dev_id;
//clear the IRQ_EINT22 interrupt pending
reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

if (reg_val & (1 << (IRQ_EINT22))) {
    //printk("==IRQ_EINT22=\r\n");
    
    writel(reg_val & (1 << (IRQ_EINT22)),
       gpio_addr + PIO_INT_STAT_OFFSET);

    ret_val = readl(gpio_addr + PIO_INT_DATA_OFFSET);
    if(!(ret_val&(1<<10)))
    {
        //printk("key1 pressed \r\n");
        key_value |= key_t->val;
    }
    else
    {
        //printk("key1 released \r\n");
        key_value &= ~key_t->val;
    }   

    //printk("key%d irq Interrupt,%d \r\n",key_t->val,key_value);
}   

return IRQ_HANDLED;

}

static irqreturn_t key_irq_handler2(int irq, void *dev_id)
{

int reg_val;
int ret_val;
//clear the IRQ_EINT23 interrupt pending
reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

struct key_str *key_t = (struct key_str *)dev_id;
//clear the IRQ_EINT22 interrupt pending
reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

if 1

if (reg_val & (1 << (IRQ_EINT23))) {
    //printk("==IRQ_EINT23=\r\n");
    writel(reg_val & (1 << (IRQ_EINT23)),
           gpio_addr + PIO_INT_STAT_OFFSET);
    
    ret_val = readl(gpio_addr + PIO_INT_DATA_OFFSET);
    if(!(ret_val&(1<<11)))
    {
        //printk("key2 pressed \r\n");
        key_value |= key_t->val;
    }
    else
    {
        //printk("key2 released \r\n");
        key_value &= ~key_t->val;
    }   
    //printk("key%d irq Interrupt,%d \r\n",key_t->val,key_value);

} 

endif

return IRQ_HANDLED;

}

static ssize_t key_read (struct file *file, char __user *buf, size_t len, loff_t *off)
{
unsigned int value = 0;
value = copy_to_user(buf,&key_value,4);

return value;

}

static int key_open(struct inode *inode, struct file *filp)
{
int err = 0;
int key_test_enabled = 0;
int ret = 0;

err = script_parser_fetch("key_test_para", "key_test_enable", &key_test_enabled,
                sizeof(key_test_enabled)/sizeof(int));

if(!err){
    printk("---script.bin key get ok,value:%d----\n",key_test_enabled);
}
else
{
    printk("---script.bin key get false----\n");    
    return -1;
}

err = script_parser_fetch("key_test_para", "key1",
            (int *)&info,
            sizeof(script_gpio_set_t));
if (err) {
    printk("----script.bin get io error----\r\n");
    return -1;
}

err = script_parser_fetch("key_test_para", "key2",
            (int *)&info,
            sizeof(script_gpio_set_t));
if (err) {
    printk("----script.bin get io error----\r\n");
    return -1;
}



/* reserve gpio for led */
key_handler1 = gpio_request_ex("key_test_para", "key1");
if (!key_handler1) {
    printk("----script.bin can't requst handler----\r\n");
    return -1;
}


/* reserve gpio for led */
key_handler2 = gpio_request_ex("key_test_para", "key2");
if (!key_handler2) {
    printk("----script.bin can't requst handler----\r\n");
    return -1;
}

if 1

/*設(shè)置為輸入搓扯,沒有上下拉*/
err = gpio_set_one_pin_io_status(key_handler1,0,"key1");
if (err) {
    printk("----set io input 1 error----\r\n");
    return -1;
}   
err = gpio_set_one_pin_io_status(key_handler2,0,"key2");
if (err) {
    printk("----set io input 2 error----\r\n");
    return -1;
}   

endif

err = gpio_set_one_pin_pull(key_handler1,1,"key1");
if (err) {
    printk("----set io pull error----\r\n");
    return -1;
}

err = gpio_set_one_pin_pull(key_handler2,1,"key2");
if (err) {
    printk("----set io pull error----\r\n");
    return -1;
}




if (!gpio_addr) {
    gpio_addr = ioremap(PIO_BASE_ADDRESS, PIO_RANGE_SIZE);
}

if(!gpio_addr)
{
    printk("-----address error-----\r\n");
}

err = request_irq(SW_INT_IRQNO_PIO, key_irq_handler1,
          IRQF_SHARED, "gpio_pin_1", &g_key_str[0]);

if (err < 0) {
    printk(" request irq 1 error:%d\n",err);
    return -1;
}

err = request_irq(SW_INT_IRQNO_PIO, key_irq_handler2,
          IRQF_SHARED, "gpio_pin_2", &g_key_str[1]);

if (err < 0) {
    printk(" request irq error:%d\n",err);
    return -1;
}

/*set the gpio 1 register*/
SUNXI_SET_GPIO_IRQ_TYPE(gpio_addr,IRQ_EINT22,DOUBLE_EDGE);  
SUNXI_UNMASK_GPIO_IRQ(gpio_addr,IRQ_EINT22);
SUNXI_SET_GPIO_MODE(gpio_addr,9,10,6);/*PI10 EINT22 settiings*/

/*set the gpio 2 register*/
SUNXI_SET_GPIO_IRQ_TYPE(gpio_addr,IRQ_EINT23,DOUBLE_EDGE);  
SUNXI_UNMASK_GPIO_IRQ(gpio_addr,IRQ_EINT23);
SUNXI_SET_GPIO_MODE(gpio_addr,9,11,6);/*PI11 EINT23 settiings*/


return 0;

}

static ssize_t key_write (struct file *filp, const char __user *buf, size_t len, loff_t *off)
{

return 0;

}

static int key_close(struct inode *inode, struct file *filp)
{
SUNXI_MASK_GPIO_IRQ(gpio_addr,IRQ_EINT22);
SUNXI_MASK_GPIO_IRQ(gpio_addr,IRQ_EINT23);

free_irq(SW_INT_IRQNO_PIO, &g_key_str[0]);
free_irq(SW_INT_IRQNO_PIO, &g_key_str[1]);
printk("----key close----\r\n");


return 0;

}

static int __init key_init(void)
{
key_major = register_chrdev(0, "key_chrdev", &key_operations);

key_class = class_create(THIS_MODULE, "key_class");

if(!key_class){
    unregister_chrdev(key_major, "key_chrdev");
    printk("----key_chrdev error----\r\n");
    return -1;
}
key_device = device_create(key_class, NULL, MKDEV(key_major,0),
                      NULL, "key_device");
if(!key_device){
    class_destroy(key_class);
    unregister_chrdev(key_major, "key_chrdev");
    printk("----key_device error----\r\n");
    return -1;
}

printk("----key init ok----\r\n");
return 0;

}

static void __exit key_exit(void)
{
if (gpio_addr) {
iounmap(gpio_addr);
}

if (key_handler1)
    gpio_release(key_handler1, 1);
if (key_handler2)
    gpio_release(key_handler2, 1);  

device_destroy(key_class, MKDEV(key_major, 0));
class_destroy(key_class);
unregister_chrdev(key_major, "key_chrdev");

printk("---driver exit---\r\n");

}

module_init(key_init);
module_exit(key_exit);

MODULE_DESCRIPTION("Driver for key");
MODULE_AUTHOR("wit_yuan");
MODULE_LICENSE("GPL");


Makefile程序:

ifeq ($(KERNELRELEASE),)
KERNEL_DIR=/home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.2/linux-sunxi
PWD=$(shell pwd)

modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc -o key key.c

modules_install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -rf *.ko *.o .tmp_versions .mod.c modules.order Module.symvers ..cmd
else
obj-m:=key.o

endif


key_test.c測試程序:

include "stdio.h"

include <sys/types.h>

include <sys/stat.h>

include <fcntl.h>

int main(int argc,char *argv[])
{
int fd;
int val;
fd = open("/dev/key_device",O_RDWR);
if(fd < 0){
printf("---open file error----\r\n");
return -1;
}

while(1)
{
    read(fd,&val,1);
    if(val!=0)
        printf("val:%0x\r\n",val);
}

return 0;

}


使用top命令查詢應(yīng)用程序占用cpu資源:
![按鍵驅(qū)動(dòng)程序資源占用](http://upload-images.jianshu.io/upload_images/3549048-fb05a6115daedca8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

所以,使用這種方式遣妥,在程序設(shè)計(jì)角度來說擅编,明顯是不合理的。所以箫踩,必須要進(jìn)行改進(jìn)爱态。

### 3.6 使用休眠方式
由于3.5的程序中,應(yīng)用程序是一直在讀取內(nèi)核數(shù)據(jù)境钟。這樣的方式明顯將整個(gè)系統(tǒng)的性能降低锦担。而程序設(shè)計(jì)的整體思路是,只需要在內(nèi)核有數(shù)據(jù)的時(shí)候慨削,應(yīng)用層讀取就可以了洞渔。所以呢,可以使用這種方式缚态,應(yīng)用層讀取數(shù)據(jù)磁椒,在內(nèi)核沒有數(shù)據(jù)需要返回的時(shí)候,內(nèi)核可以讓該進(jìn)程異步阻塞玫芦,這樣就不會(huì)占用整個(gè)系統(tǒng)的資源浆熔。

這種技巧,使用的主要是內(nèi)核提供的幾個(gè)函數(shù):
> * wait_event_interruptible
> * wake_up_interruptible
> * DECLARE_WAIT_QUEUE_HEAD
> * init_waitqueue_head

關(guān)于這種操作方式桥帆,可以參考文件:
\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\staging\iio\adc\Ad7780.c

我把程序貼在下面:
key.c驅(qū)動(dòng)程序如下:

include "linux/init.h"

include <linux/kernel.h>

include <linux/module.h>

include <linux/leds.h>

include <plat/sys_config.h>

include <linux/major.h>

include <linux/fs.h>

include <linux/device.h>

include <asm/io.h>

include <asm/uaccess.h>

include <linux/interrupt.h>

include <linux/ioport.h>

include <asm/irq.h>

include <asm/io.h>

include <mach/irqs.h>

include <mach/system.h>

include <asm/uaccess.h>

include <mach/hardware.h>

include <linux/gpio.h>

include <linux/sched.h>

include <linux/wait.h>

/* EINT type PIO controller registers */

define PIO_INT_CFG0_OFFSET 0x200

define PIO_INT_CFG1_OFFSET 0x204

define PIO_INT_CFG2_OFFSET 0x208

define PIO_INT_CFG3_OFFSET 0x20c

define PIO_INT_STAT_OFFSET (0x214)

define PIO_INT_DATA_OFFSET (0x130)

define IRQ_EINT22 22

define IRQ_EINT23 23

define PIO_BASE_ADDRESS SW_PA_PORTC_IO_BASE

define PIO_RANGE_SIZE (0x400)

define PIO_INT_CTRL_OFFSET (0x210)

/* EINT type defines */

define POSITIVE_EDGE 0x0

define NEGATIVE_EDGE 0x1

define HIGH_LEVEL 0x2

define LOW_LEVEL 0x3

define DOUBLE_EDGE 0x4

static wait_queue_head_t key_data_avail;
static unsigned int key_done = 0;

static int int_cfg_addr[] = {PIO_INT_CFG0_OFFSET,
PIO_INT_CFG1_OFFSET,
PIO_INT_CFG2_OFFSET,
PIO_INT_CFG3_OFFSET};

/* Setup GPIO irq mode (FALLING, RISING, BOTH, etc */

define SUNXI_SET_GPIO_IRQ_TYPE(addr, offs, mode) ({ \

__u32 reg_bit = offs % 8; \
__u32 reg_num = offs / 8; \
__u32 reg_val = readl(addr + int_cfg_addr[reg_num]); \
reg_val &= (~(0xf << (reg_bit * 4))); \
reg_val |= (mode << (reg_bit * 4)); \
writel(reg_val, addr + int_cfg_addr[reg_num]); \

})

/* Enable GPIO interrupt for pin */

define SUNXI_UNMASK_GPIO_IRQ(addr, irq) ({ \

__u32 reg_val = readl(addr + PIO_INT_CTRL_OFFSET); \
reg_val |= (1 << irq); \
writel(reg_val, addr + PIO_INT_CTRL_OFFSET); \

})

/* Disable GPIO interrupt for pin */

define SUNXI_MASK_GPIO_IRQ(addr, irq) ({ \

__u32 reg_val = readl(addr + PIO_INT_CTRL_OFFSET); \
reg_val &= ~(1 << irq); \
writel(reg_val, addr + PIO_INT_CTRL_OFFSET); \

})

/* Set GPIO pin mode (input, output, etc) /
/
GPIO port has 4 cfg 32bit registers (8 pins each) /
/
First port cfg register addr = port_num * 0x24 */

define SUNXI_SET_GPIO_MODE(addr, port, pin, mode) ({ \

__u32 reg_val = 0; \
__u32 pin_idx = pin >> 3; \
void *raddr = addr + (((port)-1)*0x24 + ((pin_idx)<<2) + 0x00); \
reg_val = readl(raddr); \
reg_val &= ~(0x07 << (((pin - (pin_idx<<3))<<2))); \
reg_val |= mode << (((pin - (pin_idx<<3))<<2)); \
writel(reg_val, raddr); \

})

static script_gpio_set_t info;
static unsigned key_handler1;
static unsigned key_handler2;

static struct class *key_class;
static struct device *key_device;
static unsigned int key_major;

static unsigned int key_value;

static void *__iomem gpio_addr = NULL;

static int key_open(struct inode *inode, struct file *filp);
static ssize_t key_read (struct file *, char __user *, size_t, loff_t *);
static ssize_t key_write (struct file *filp, const char __user *buf, size_t len, loff_t *off);
static int key_close(struct inode *inode, struct file *filp);

struct file_operations key_operations = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_close,
};

struct key_str{
char *name;
int val;
};

struct key_str g_key_str[2]={{"key1",0x1},{"key2",2}};

static irqreturn_t key_irq_handler1(int irq, void *dev_id)
{
int err;
int reg_val = 0;
int ret_val = 0;

struct key_str *key_t = (struct key_str *)dev_id;
//clear the IRQ_EINT22 interrupt pending
reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

if (reg_val & (1 << (IRQ_EINT22))) {
    //printk("==IRQ_EINT22=\r\n");
    
    writel(reg_val & (1 << (IRQ_EINT22)),
       gpio_addr + PIO_INT_STAT_OFFSET);

    ret_val = readl(gpio_addr + PIO_INT_DATA_OFFSET);
    if(!(ret_val&(1<<10)))
    {
        //printk("key1 pressed \r\n");
        key_value |= key_t->val;
    }
    else
    {
        //printk("key1 released \r\n");
        key_value &= ~key_t->val;
    }

    key_done = 1;
    wake_up_interruptible(&key_data_avail);


    //printk("key%d irq Interrupt,%d \r\n",key_t->val,key_value);
}   


return IRQ_HANDLED;

}

static irqreturn_t key_irq_handler2(int irq, void *dev_id)
{

int reg_val;
int ret_val;
//clear the IRQ_EINT23 interrupt pending
reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

struct key_str *key_t = (struct key_str *)dev_id;
//clear the IRQ_EINT22 interrupt pending
reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);

if 1

if (reg_val & (1 << (IRQ_EINT23))) {
    //printk("==IRQ_EINT23=\r\n");
    writel(reg_val & (1 << (IRQ_EINT23)),
           gpio_addr + PIO_INT_STAT_OFFSET);
    
    ret_val = readl(gpio_addr + PIO_INT_DATA_OFFSET);
    if(!(ret_val&(1<<11)))
    {
        //printk("key2 pressed \r\n");
        key_value |= key_t->val;
    }
    else
    {
        //printk("key2 released \r\n");
        key_value &= ~key_t->val;
    }   

    key_done = 1;
    wake_up_interruptible(&key_data_avail);     
    //printk("key%d irq Interrupt,%d \r\n",key_t->val,key_value);

} 

endif

return IRQ_HANDLED;

}

static ssize_t key_read (struct file *file, char __user *buf, size_t len, loff_t *off)
{
unsigned int value = 0;

key_done = 0;
wait_event_interruptible(key_data_avail,key_done);

value = copy_to_user(buf,&key_value,4);


return value;

}

static int key_open(struct inode *inode, struct file *filp)
{
int err = 0;
int key_test_enabled = 0;
int ret = 0;

err = script_parser_fetch("key_test_para", "key_test_enable", &key_test_enabled,
                sizeof(key_test_enabled)/sizeof(int));

if(!err){
    printk("---script.bin key get ok,value:%d----\n",key_test_enabled);
}
else
{
    printk("---script.bin key get false----\n");    
    return -1;
}

err = script_parser_fetch("key_test_para", "key1",
            (int *)&info,
            sizeof(script_gpio_set_t));
if (err) {
    printk("----script.bin get io error----\r\n");
    return -1;
}

err = script_parser_fetch("key_test_para", "key2",
            (int *)&info,
            sizeof(script_gpio_set_t));
if (err) {
    printk("----script.bin get io error----\r\n");
    return -1;
}



/* reserve gpio for led */
key_handler1 = gpio_request_ex("key_test_para", "key1");
if (!key_handler1) {
    printk("----script.bin can't requst handler----\r\n");
    return -1;
}


/* reserve gpio for led */
key_handler2 = gpio_request_ex("key_test_para", "key2");
if (!key_handler2) {
    printk("----script.bin can't requst handler----\r\n");
    return -1;
}

if 1

/*設(shè)置為輸入医增,沒有上下拉*/
err = gpio_set_one_pin_io_status(key_handler1,0,"key1");
if (err) {
    printk("----set io input 1 error----\r\n");
    return -1;
}   
err = gpio_set_one_pin_io_status(key_handler2,0,"key2");
if (err) {
    printk("----set io input 2 error----\r\n");
    return -1;
}   

endif

err = gpio_set_one_pin_pull(key_handler1,1,"key1");
if (err) {
    printk("----set io pull error----\r\n");
    return -1;
}

err = gpio_set_one_pin_pull(key_handler2,1,"key2");
if (err) {
    printk("----set io pull error----\r\n");
    return -1;
}




if (!gpio_addr) {
    gpio_addr = ioremap(PIO_BASE_ADDRESS, PIO_RANGE_SIZE);
}

if(!gpio_addr)
{
    printk("-----address error-----\r\n");
}

err = request_irq(SW_INT_IRQNO_PIO, key_irq_handler1,
          IRQF_SHARED, "gpio_pin_1", &g_key_str[0]);

if (err < 0) {
    printk(" request irq 1 error:%d\n",err);
    return -1;
}

err = request_irq(SW_INT_IRQNO_PIO, key_irq_handler2,
          IRQF_SHARED, "gpio_pin_2", &g_key_str[1]);

if (err < 0) {
    printk(" request irq error:%d\n",err);
    return -1;
}

/*set the gpio 1 register*/
SUNXI_SET_GPIO_IRQ_TYPE(gpio_addr,IRQ_EINT22,DOUBLE_EDGE);  
SUNXI_UNMASK_GPIO_IRQ(gpio_addr,IRQ_EINT22);
SUNXI_SET_GPIO_MODE(gpio_addr,9,10,6);/*PI10 EINT22 settiings*/

/*set the gpio 2 register*/
SUNXI_SET_GPIO_IRQ_TYPE(gpio_addr,IRQ_EINT23,DOUBLE_EDGE);  
SUNXI_UNMASK_GPIO_IRQ(gpio_addr,IRQ_EINT23);
SUNXI_SET_GPIO_MODE(gpio_addr,9,11,6);/*PI11 EINT23 settiings*/


return 0;

}

static ssize_t key_write (struct file *filp, const char __user *buf, size_t len, loff_t *off)
{

return 0;

}

static int key_close(struct inode *inode, struct file *filp)
{
SUNXI_MASK_GPIO_IRQ(gpio_addr,IRQ_EINT22);
SUNXI_MASK_GPIO_IRQ(gpio_addr,IRQ_EINT23);

free_irq(SW_INT_IRQNO_PIO, &g_key_str[0]);
free_irq(SW_INT_IRQNO_PIO, &g_key_str[1]);
printk("----key close----\r\n");


return 0;

}

static int __init key_test_init(void)
{
key_major = register_chrdev(0, "key_chrdev", &key_operations);

key_class = class_create(THIS_MODULE, "key_class");

if(!key_class){
    unregister_chrdev(key_major, "key_chrdev");
    printk("----key_chrdev error----\r\n");
    return -1;
}
key_device = device_create(key_class, NULL, MKDEV(key_major,0),
                      NULL, "key_device");
if(!key_device){
    class_destroy(key_class);
    unregister_chrdev(key_major, "key_chrdev");
    printk("----key_device error----\r\n");
    return -1;
}

printk("----key init ok----\r\n");

init_waitqueue_head(&key_data_avail);
return 0;

}

static void __exit key_test_exit(void)
{
if (gpio_addr) {
iounmap(gpio_addr);
}

if (key_handler1)
    gpio_release(key_handler1, 1);
if (key_handler2)
    gpio_release(key_handler2, 1);  

device_destroy(key_class, MKDEV(key_major, 0));
class_destroy(key_class);
unregister_chrdev(key_major, "key_chrdev");

printk("---driver exit---\r\n");

}

module_init(key_test_init);
module_exit(key_test_exit);

MODULE_DESCRIPTION("Driver for key");
MODULE_AUTHOR("wit_yuan");
MODULE_LICENSE("GPL");


Makefile程序如下:

ifeq ($(KERNELRELEASE),)
KERNEL_DIR=/home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.2/linux-sunxi
PWD=$(shell pwd)

modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc -o key key.c

modules_install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -rf *.ko *.o .tmp_versions .mod.c modules.order Module.symvers ..cmd
else
obj-m:=key.o

endif


key_test.c測試程序如下:

include "stdio.h"

include <sys/types.h>

include <sys/stat.h>

include <fcntl.h>

int main(int argc,char *argv[])
{
int fd;
int val;
fd = open("/dev/key_device",O_RDWR);
if(fd < 0){
printf("---open file error----\r\n");
return -1;
}

while(1)
{
    read(fd,&val,1);

    if(val!=0)
        printf("val:%0x\r\n",val);

}

return 0;

}

再使用top命令查詢:
![按鍵程序基本不占用cpu資源](http://upload-images.jianshu.io/upload_images/3549048-34240160c4c26f5e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灌具,一起剝皮案震驚了整個(gè)濱河市乞娄,隨后出現(xiàn)的幾起案子鼠证,更是在濱河造成了極大的恐慌疚膊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌蓄,死亡現(xiàn)場離奇詭異熬尺,居然都是意外死亡褂始,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門缔恳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宝剖,“玉大人洁闰,你說我怎么就攤上這事歉甚。” “怎么了扑眉?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵纸泄,是天一觀的道長。 經(jīng)常有香客問我腰素,道長聘裁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任弓千,我火速辦了婚禮衡便,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洋访。我一直安慰自己镣陕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布姻政。 她就那樣靜靜地躺著呆抑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汁展。 梳的紋絲不亂的頭發(fā)上鹊碍,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音食绿,去河邊找鬼侈咕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛器紧,可吹牛的內(nèi)容都是我干的耀销。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼品洛,長吁一口氣:“原來是場噩夢啊……” “哼树姨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起桥状,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤帽揪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后辅斟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體转晰,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了查邢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔗崎。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扰藕,靈堂內(nèi)的尸體忽然破棺而出缓苛,到底是詐尸還是另有隱情,我是刑警寧澤邓深,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布未桥,位于F島的核電站,受9級(jí)特大地震影響芥备,放射性物質(zhì)發(fā)生泄漏冬耿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一萌壳、第九天 我趴在偏房一處隱蔽的房頂上張望亦镶。 院中可真熱鬧,春花似錦袱瓮、人聲如沸缤骨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荷憋。三九已至,卻和暖如春褐望,著一層夾襖步出監(jiān)牢的瞬間勒庄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工瘫里, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留实蔽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓谨读,卻偏偏與公主長得像局装,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子劳殖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容