四 . 树莓派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 硬件原理图

关于按键,在DVK521上为:


按键

我又从一份数据手册中看到,PI7,PI8,PI9是没有外部中断功能的。如下图所示:


中断功能

还好,目前的按键是通过短接帽来连接PI7PI9的,那么可以将短接帽拿掉,使用杜邦线连接PI10PI12。

现在我将Key2连接到PI10上。那么KEY2的中断引脚为EINT22。

配置sys_config.fex文件:

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

3 软件配置基础知识

现在使用的树莓派A20,是一个双核A7的芯片,而这个属于SMP架构,中断处理方式也已经和原先的理念大有不同。所以还是要知道关于linux的中断原理,可以从网络中获取相关历史性技术知识。

3.1 中断

从A20的数据手册中,可以看到外部中断数到了EINT31。也就是说PIO中断功能有32个。

3.2 一些关于IO功能的API函数

下面列出的API函数是在 \linux-sunxi\arch\arm\plat-sunxi\Sys_config.c中。

/*
 * CSP_GPIO_Request_EX
 * 函数名称:
 *
 * 参数说明:
 * main_name   传进的主键名称,匹配模块(驱动名称)
 * sub_name    传进的子键名称,如果是空,表示全部,否则寻找到匹配的单独GPIO
 *
 * 返回值  :0 :    err
 *          other: success
 *
 * 说明    :暂时没有做冲突检查
 */
u32 gpio_request_ex(char *main_name, const char *sub_name)  /* 设备申请GPIO函数扩展接口 */
/*
 * CSP_GPIO_Set_One_PIN_IO_Status
 * Description:
 * 修改用户申请过的GPIO中的某一个IO口的,输入输出状态
 * Arguments  :
 *  p_handler    :    handler
 *  if_set_to_output_status    :    设置成输出状态还是输入状态
 *  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中的某一个IO口的端口的电平
 * Arguments:
 *  p_handler    :    handler
 *  value_to_gpio:  要设置的电平的电压
 *  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中的某一个IO口的,PULL状态
 * Arguments  :
 *        p_handler    :    handler
 *        if_set_to_output_status    :    所设置的pull状态
 *        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中设置的中断IO口。
2.gpio_set_one_pin_io_status(),设置为输入状态。
3.gpio_set_one_pin_pull(),设置输入引脚的上下拉状态。
4.request_irq()注册中断函数。

request_irq()函数:
第一个参数为SW_INT_IRQNO_PIO,表示是外部端口的中断号。
第二个参数为中断处理函数名。
第三个参数为中断方式,如IRQ_TYPE_EDGE_RISING,上升沿触发,而IRQ_TYPE_EDGE_FALLING则是下降沿触发,IRQF_SHARED为共享。
第四个参数为中断名。
第五个参数为中断传递的数据。

3.3 中断处理逻辑

1.获取IO中断源信息
由于内核使用的是虚拟地址寻址硬件地址,获取中断源就需要将IO硬件地址空间映射到虚拟地址上。可以使用ioremap(PIO_BASE_ADDRESS, PIO_RANGE_SIZE)进行映射。

2.屏蔽中断源
a.读取中断源寄存器的状态,可以使用readl(映射的IO地址 + PIO_INT_STAT_OFFSET);
b.判断对应的中断,使用writel(reg_val&(1<<(CTP_IRQ_NO)),映射的IO地址 + PIO_INT_STAT_OFFSET);清除状态位。

写到这里,本应该很顺利,可是,在驱动程序加载进内核的时候,明显是报错。错误我就不贴出来了,可是我可以将中断信息附上:

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中断号60已经注册进内核了。我们现在使用的一个IO中断是被包含在里面的。所以,需要在内核中找到sunxi-gpio是怎么去注册中断,而我们就需要将我们的中断程序内容附加到已经注册的中断上去。

在 marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\gpio\Gpio-sunxi.c中我们可以找到函数:

/* 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;
}

里面最重要的函数是:

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

最终调用的是:

/*
 * 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已经将中断基本处理做好了,我们要做的只是和它共享中断。

3.4 实践逻辑

sys_config.fex文件配置如下:


sys_config.fex配置

从上面的实验中,已经发现在request_irq中设置边沿等等触发,在安装ko文件的时候,都会报错,从这里看出,在共享中断的时候,是不允许设置其他的内容的。那么,只能去找A20寄存器中关于io口中断的设置。在这些设置已经设置好的情况下,中断应该就能响应了。这里贴出一个比较简单的驱动程序:

#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
    /*设置为输入,没有上下拉*/
    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");```

测试程序为:

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;

}

也就是说,只需要打开一个文件即可。

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


测试结果为:

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,简单的测试程序,暂时就这样了。下面再去优化。


### 3.5 polling查询按键值
使用查询按键值的程序代码如下:
key.c驱动程序:

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

/*设置为输入,没有上下拉*/
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命令查询应用程序占用cpu资源:
![按键驱动程序资源占用](http://upload-images.jianshu.io/upload_images/3549048-fb05a6115daedca8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

所以,使用这种方式,在程序设计角度来说,明显是不合理的。所以,必须要进行改进。

### 3.6 使用休眠方式
由于3.5的程序中,应用程序是一直在读取内核数据。这样的方式明显将整个系统的性能降低。而程序设计的整体思路是,只需要在内核有数据的时候,应用层读取就可以了。所以呢,可以使用这种方式,应用层读取数据,在内核没有数据需要返回的时候,内核可以让该进程异步阻塞,这样就不会占用整个系统的资源。

这种技巧,使用的主要是内核提供的几个函数:
> * wait_event_interruptible
> * wake_up_interruptible
> * DECLARE_WAIT_QUEUE_HEAD
> * init_waitqueue_head

关于这种操作方式,可以参考文件:
\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\staging\iio\adc\Ad7780.c

我把程序贴在下面:
key.c驱动程序如下:

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

/*设置为输入,没有上下拉*/
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)



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容