1、设备的阻塞方式访问
  应用编程
      sd=socke(...)
      recv(sd, ....)//无数据阻塞等待 有数据立即返回
      
  希望按键设备 也能达到在用户空间阻塞方式访问
 
  内核中为了实现设备的阻塞方式访问,提供了一套机制: 等待队列
      核心数据结构
         wait_queue_head_t
 
  实验步骤:
      1)定义一个等待队列头变量
         wait_queue_head_t  btn_wqh;
      2) 初始化等待队列头变量
         init_waitqueue_head(&btn_wqh);
        
        //等价于步骤1) 2)
        DECLARE_WAIT_QUEUE_HEAD(btn_wqh)   
      3) 驱动程序中,对设备执行读写操作时
         如果设备I/O为就绪 可以调用以下函数,
         实现进程的阻塞
         //该函数会使得调用者进程进入睡眠状态
         wait_event(btn_wqh, condition)  
         //进入的是可中断的睡眠状态
         wait_event_interruptible(btn_wqh, condition)
            condition,为TRUE  直接返回 不睡眠
                      为FALSE,进程进入睡眠状态
                
      4)当设备I/O就绪时
         唤醒因I/O未就绪而进入睡眠状态的进程
         wake_up(&btn_wqh);
         wake_up_interruptible(&btn_wqh);      
         
   实现原理:
       1)内核中管理进程,会为每个进程建立PCB(进程控制块)
          linux中PCB在内核中对应的数据结构       
          struct task_struct
          {
             //代表了进程的当前状态
             volatile long state
             ...
          }
          linux内核会为每个进程创建一个task_struct变量
          该变量在内核软件中代表着该进程
       2)current  
          始终执行正在CPU中执行的那个进程的数据结构
       3) wait_event_interruptible
          
          __wait_event_interruptible(btn_wqh, ....)
          {
              //创建了一个新的节点
              //该节点中保存了当前进程信息(task_struct变量地址)
              DEFINE_WAIT(__wait);
              
              
              prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE)
              {
                  //把前边创建的新节点 插入btn_wqh指向的链表中去
                  __add_wait_queue(q, wait);
                  //将当前进程的状态改为TASK_INTERRUPTIBLE(可中断的睡眠状态)
                  set_current_state(state);
              }    
              //从处于running的集合中选出一个进程放入CPU中执行
              //当前任务真的就放弃了CPU 进入睡眠状态
              schedule();
          }
       4)唤醒
         //btn_wqh指向的链表中有多个节点
         //每个节点中记录了因某类事件而睡眠的进程
         //当该类事件条件满足时
         //内核会唤醒多个节点对应的多个进程
         wake_up_interruptible(&btn_wqh)

2、延时去抖
   1)timer_list
   
   2) delayed_work


3、按键的按下和释放都关注
   1)如何使得上升沿和下降沿都触发中断
     requst_irq(..., btn_isr
         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                ...)
   2)btn_timer_func
     哪个按键触发的
     
     按下触发还是释放触发 通过读取管脚的电平状态获取
        gpio_get_value(...)
 
  练习:
      ./test
      up 按下 输出 0x10
         释放      0x11
 
4、设备的非阻塞方式访问           
           
   //默认为阻塞方式访问
   fd = open("/dev/mybuttons", O_RDONLY);
   //实现设备的非阻塞方式访问
   fd = open("/dev/mybuttons", O_RDONLY|O_NONBLOCK);         
           
           
   btn_read(filp, buf, len, offset)
  {
     要实现非阻塞,关键在于要知道open设备时是否
     使用了O_NONBLOCK参数
     
     该参数信息存储在了filp->f_flags
  }    
 
 
  struct file//文件表
  {
      //其中存储了用户空间open设备时的绝大部分参数信息
      unsigned int f_flags;
      //记录对应的操作函数集合地址
      const struct file_operations    *f_op;
      ...
  }    

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
    unsigned char key=0;
    int fd = open("/dev/mybuttons", O_RDONLY);
    if(fd < 0)
    {
        perror("open failed:");
        return -1;
    }
    printf("open successed!\n");

    while(1)
    {
        read(fd, &key, sizeof(key));
        printf("key=%#x\n", key);
    }

    close(fd);
    return 0;
}
#include "../../global.h"
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/sched.h>

dev_t dev = 0;
struct cdev btn_cdev;
struct class *cls = NULL;

/*定义信号量*/
struct semaphore btn_sem;
/*按键键值缓冲区*/
unsigned char key_buf;
/*记录按键缓冲区中是否有键值
 *0,无键值
 *1, 有键值
 * */
volatile int ev_press = 0;
/*定义等待队列头变量*/
wait_queue_head_t btn_wqh;

static int btn_open(struct inode *inode,
                    struct file *filp)
{
    int ret = 0;
    //down(&btn_sem);
    ret = down_interruptible(&btn_sem);
    if(ret) //被信号打断
    {
        return -EAGAIN;
    }
    return 0;
}
static int btn_release(struct inode *inode,
                       struct file *filp)
{
    up(&btn_sem);
    return 0;
}
static ssize_t btn_read(struct file *filp,
                        char __user *buf,
                        size_t len,
                        loff_t *off)
{
    int ret = 0;
    /*
     *按键缓冲区中有键值 直接返回
     *如果无键值 让test进程进入睡眠状态
     * */
    wait_event_interruptible(btn_wqh, ev_press);

    //*buf = key_buf;//有风险
    ret = copy_to_user(buf, &key_buf, len);
    if(ret)
    {
        return -EFAULT;
    }
    ev_press = 0;
    return ret;
}
struct file_operations btn_fops =
{
    .owner = THIS_MODULE,
    .open = btn_open,
    .release = btn_release,
    .read = btn_read,
};
irqreturn_t btn_isr(int irq, void *dev)
{
    /*保存按键值*/
    key_buf = 0x10;
    ev_press = 1;//有键值
    /*唤醒因无键值而睡眠的进程*/
    wake_up_interruptible(&btn_wqh);

    return IRQ_HANDLED; //中断处理完毕
}
int __init btn_drv_init(void)
{
    if(request_irq(IRQ_GPIO_A_START+28, btn_isr,
                   IRQF_TRIGGER_FALLING,
                   "up", NULL))
    {
        printk("<1>"  "request_irq failed!");
        return -EAGAIN;
    }
    /*申请注册设备号*/
    alloc_chrdev_region(&dev, 0, 1, "buttons");
    /*初始化cdev*/
    cdev_init(&btn_cdev, &btn_fops);
    /*注册cdev*/
    cdev_add(&btn_cdev, dev, 1);
    /*自动创建设备文件*/
    cls = class_create(THIS_MODULE, "buttons");
    device_create(cls, NULL, dev, NULL, "mybuttons");
    /*初始化信号量*/
    /*该设备可以同时被一个进程访问*/
    sema_init(&btn_sem, 1);
    /*初始化等待队列头*/
    init_waitqueue_head(&btn_wqh);

    return 0;
}
void __exit btn_drv_exit(void)
{
    /*销毁设备文件*/
    device_destroy(cls, dev);
    class_destroy(cls);
    /*注销cdev*/
    cdev_del(&btn_cdev);
    /*注销设备号*/
    unregister_chrdev_region(dev, 1);
    free_irq(IRQ_GPIO_A_START+28, NULL);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
#include "../../global.h"
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/sched.h>

dev_t dev = 0;
struct cdev btn_cdev;
struct class *cls = NULL;

/*定义信号量*/
struct semaphore btn_sem;
/*按键键值缓冲区*/
unsigned char key_buf;
/*记录按键缓冲区中是否有键值
 *0,无键值
 *1, 有键值
 * */
volatile int ev_press = 0;
/*定义等待队列头变量*/
wait_queue_head_t btn_wqh;

struct timer_list btn_timer;
typedef struct btn_desc
{
    unsigned char code;// 按键编码
    int irq;
    char *name;
}btn_desc_t;
btn_desc_t buttons[]=
{
    {0x10, IRQ_GPIO_A_START+28, "up"},
    {0x20, IRQ_GPIO_B_START+30, "down"},
    {0x30, IRQ_GPIO_B_START+31, "left"},
    {0x40, IRQ_GPIO_B_START+9,  "right"},
};

static int btn_open(struct inode *inode,
                    struct file *filp)
{
    int ret = 0;
    //down(&btn_sem);
    ret = down_interruptible(&btn_sem);
    if(ret) //被信号打断
    {
        return -EAGAIN;
    }
    return 0;
}
static int btn_release(struct inode *inode,
                       struct file *filp)
{
    up(&btn_sem);
    return 0;
}
static ssize_t btn_read(struct file *filp,
                        char __user *buf,
                        size_t len,
                        loff_t *off)
{
    int ret = 0;
    /*
     *按键缓冲区中有键值 直接返回
     *如果无键值 让test进程进入睡眠状态
     * */
    wait_event_interruptible(btn_wqh, ev_press);

    //*buf = key_buf;//有风险
    ret = copy_to_user(buf, &key_buf, len);
    if(ret)
    {
        return -EFAULT;
    }
    ev_press = 0;
    return ret;
}
struct file_operations btn_fops =
{
    .owner = THIS_MODULE,
    .open = btn_open,
    .release = btn_release,
    .read = btn_read,
};

void btn_timer_func(unsigned long data)
{
    /*获取按键的描述信息*/
    btn_desc_t *pdata = (btn_desc_t *)data;
    /*保存按键值*/
    key_buf = pdata->code;
    ev_press = 1;//有键值
    /*唤醒因无键值而睡眠的进程*/
    wake_up_interruptible(&btn_wqh);

}
irqreturn_t btn_isr(int irq, void *dev)
{
    /*传递按键的描述信息*/
    btn_timer.data = (unsigned long)dev;
    mod_timer(&btn_timer, jiffies+HZ/100);
    return IRQ_HANDLED; //中断处理完毕
}
int __init btn_drv_init(void)
{
    int i = 0;
    int ret = 0;
    /*申请注册设备号*/
    alloc_chrdev_region(&dev, 0, 1, "buttons");
    /*初始化cdev*/
    cdev_init(&btn_cdev, &btn_fops);
    /*注册cdev*/
    cdev_add(&btn_cdev, dev, 1);
    /*自动创建设备文件*/
    cls = class_create(THIS_MODULE, "buttons");
    device_create(cls, NULL, dev, NULL, "mybuttons");
    /*初始化信号量*/
    /*该设备可以同时被一个进程访问*/
    sema_init(&btn_sem, 1);
    /*初始化等待队列头*/
    init_waitqueue_head(&btn_wqh);

    for(; i<ARRAY_SIZE(buttons);i++)
    {
        ret = request_irq(buttons[i].irq, btn_isr,
                          IRQF_TRIGGER_FALLING,
                          buttons[i].name,
                          &(buttons[i]));
    }
    init_timer(&btn_timer);
    btn_timer.function = btn_timer_func;


    return 0;
}
void __exit btn_drv_exit(void)
{
    int i = 0;
    for(; i<ARRAY_SIZE(buttons); i++)
    {
        free_irq(buttons[i].irq, buttons+i);
    }
    /*销毁设备文件*/
    device_destroy(cls, dev);
    class_destroy(cls);
    /*注销cdev*/
    cdev_del(&btn_cdev);
    /*注销设备号*/
    unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
#include "../../global.h"
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/sched.h>
#include <linux/gpio.h>

dev_t dev = 0;
struct cdev btn_cdev;
struct class *cls = NULL;

/*定义信号量*/
struct semaphore btn_sem;
/*按键键值缓冲区*/
unsigned char key_buf;
/*记录按键缓冲区中是否有键值
 *0,无键值
 *1, 有键值
 * */
volatile int ev_press = 0;
/*定义等待队列头变量*/
wait_queue_head_t btn_wqh;

struct timer_list btn_timer;
typedef struct btn_desc
{
    unsigned char code;// 按键编码
    int irq;
    char *name;
    int gpio;  //管脚编号
}btn_desc_t;
btn_desc_t buttons[]=
{
    {0x10, IRQ_GPIO_A_START+28, "up",PAD_GPIO_A+28},
    {0x20, IRQ_GPIO_B_START+30, "down",PAD_GPIO_B+30},
    {0x30, IRQ_GPIO_B_START+31, "left",PAD_GPIO_B+31},
    {0x40, IRQ_GPIO_B_START+9,  "right",PAD_GPIO_B+9},
};

static int btn_open(struct inode *inode,
                    struct file *filp)
{
    int ret = 0;
    //down(&btn_sem);
    ret = down_interruptible(&btn_sem);
    if(ret) //被信号打断
    {
        return -EAGAIN;
    }
    return 0;
}
static int btn_release(struct inode *inode,
                       struct file *filp)
{
    up(&btn_sem);
    return 0;
}
static ssize_t btn_read(struct file *filp,
                        char __user *buf,
                        size_t len,
                        loff_t *off)
{
    int ret = 0;
    /*
     *按键缓冲区中有键值 直接返回
     *如果无键值 让test进程进入睡眠状态
     * */
    wait_event_interruptible(btn_wqh, ev_press);

    //*buf = key_buf;//有风险
    ret = copy_to_user(buf, &key_buf, len);
    if(ret)
    {
        return -EFAULT;
    }
    ev_press = 0;
    return ret;
}
struct file_operations btn_fops =
{
    .owner = THIS_MODULE,
    .open = btn_open,
    .release = btn_release,
    .read = btn_read,
};

void btn_timer_func(unsigned long data)
{
    int stat = 0;
    /*获取按键的描述信息*/
    btn_desc_t *pdata = (btn_desc_t *)data;
    /*判断是按下触发还是释放触发
     *按下 返回0
     *释放 返回1
     * */
    stat = gpio_get_value(pdata->gpio);
    /*保存按键值*/
    key_buf = pdata->code + stat;
    ev_press = 1;//有键值
    /*唤醒因无键值而睡眠的进程*/
    wake_up_interruptible(&btn_wqh);

}
irqreturn_t btn_isr(int irq, void *dev)
{
    /*传递按键的描述信息*/
    btn_timer.data = (unsigned long)dev;
    mod_timer(&btn_timer, jiffies+HZ/100);
    return IRQ_HANDLED; //中断处理完毕
}
int __init btn_drv_init(void)
{
    int i = 0;
    int ret = 0;
    /*申请注册设备号*/
    alloc_chrdev_region(&dev, 0, 1, "buttons");
    /*初始化cdev*/
    cdev_init(&btn_cdev, &btn_fops);
    /*注册cdev*/
    cdev_add(&btn_cdev, dev, 1);
    /*自动创建设备文件*/
    cls = class_create(THIS_MODULE, "buttons");
    device_create(cls, NULL, dev, NULL, "mybuttons");
    /*初始化信号量*/
    /*该设备可以同时被一个进程访问*/
    sema_init(&btn_sem, 1);
    /*初始化等待队列头*/
    init_waitqueue_head(&btn_wqh);

    for(; i<ARRAY_SIZE(buttons);i++)
    {
        ret = request_irq(buttons[i].irq, btn_isr,
            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                          buttons[i].name,
                          &(buttons[i]));
    }
    init_timer(&btn_timer);
    btn_timer.function = btn_timer_func;


    return 0;
}
void __exit btn_drv_exit(void)
{
    int i = 0;
    for(; i<ARRAY_SIZE(buttons); i++)
    {
        free_irq(buttons[i].irq, buttons+i);
    }
    /*销毁设备文件*/
    device_destroy(cls, dev);
    class_destroy(cls);
    /*注销cdev*/
    cdev_del(&btn_cdev);
    /*注销设备号*/
    unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
#include "../../global.h"
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/sched.h>
#include <linux/gpio.h>

dev_t dev = 0;
struct cdev btn_cdev;
struct class *cls = NULL;

/*定义信号量*/
struct semaphore btn_sem;
/*按键键值缓冲区*/
unsigned char key_buf;
/*记录按键缓冲区中是否有键值
 *0,无键值
 *1, 有键值
 * */
volatile int ev_press = 0;
/*定义等待队列头变量*/
wait_queue_head_t btn_wqh;

struct timer_list btn_timer;
typedef struct btn_desc
{
    unsigned char code;// 按键编码
    int irq;
    char *name;
    int gpio;  //管脚编号
}btn_desc_t;
btn_desc_t buttons[]=
{
    {0x10, IRQ_GPIO_A_START+28, "up",PAD_GPIO_A+28},
    {0x20, IRQ_GPIO_B_START+30, "down",PAD_GPIO_B+30},
    {0x30, IRQ_GPIO_B_START+31, "left",PAD_GPIO_B+31},
    {0x40, IRQ_GPIO_B_START+9,  "right",PAD_GPIO_B+9},
};

static int btn_open(struct inode *inode,
                    struct file *filp)
{
    int ret = 0;
    //down(&btn_sem);
    ret = down_interruptible(&btn_sem);
    if(ret) //被信号打断
    {
        return -EAGAIN;
    }
    return 0;
}
static int btn_release(struct inode *inode,
                       struct file *filp)
{
    up(&btn_sem);
    return 0;
}
static ssize_t btn_read(struct file *filp,
                        char __user *buf,
                        size_t len,
                        loff_t *off)
{
    int ret = 0;
    //if(用户非阻塞方式访问设备 && ev_press==0)
    if((filp->f_flags&O_NONBLOCK) && ev_press==0)
    {
        return -EAGAIN;
    }
    /*
     *按键缓冲区中有键值 直接返回
     *如果无键值 让test进程进入睡眠状态
     * */
    wait_event_interruptible(btn_wqh, ev_press);

    //*buf = key_buf;//有风险
    ret = copy_to_user(buf, &key_buf, len);
    if(ret)
    {
        return -EFAULT;
    }
    ev_press = 0;
    return ret;
}
struct file_operations btn_fops =
{
    .owner = THIS_MODULE,
    .open = btn_open,
    .release = btn_release,
    .read = btn_read,
};

void btn_timer_func(unsigned long data)
{
    int stat = 0;
    /*获取按键的描述信息*/
    btn_desc_t *pdata = (btn_desc_t *)data;
    /*判断是按下触发还是释放触发
     *按下 返回0
     *释放 返回1
     * */
    stat = gpio_get_value(pdata->gpio);
    /*保存按键值*/
    key_buf = pdata->code + stat;
    ev_press = 1;//有键值
    /*唤醒因无键值而睡眠的进程*/
    wake_up_interruptible(&btn_wqh);

}
irqreturn_t btn_isr(int irq, void *dev)
{
    /*传递按键的描述信息*/
    btn_timer.data = (unsigned long)dev;
    mod_timer(&btn_timer, jiffies+HZ/100);
    return IRQ_HANDLED; //中断处理完毕
}
int __init btn_drv_init(void)
{
    int i = 0;
    int ret = 0;
    /*申请注册设备号*/
    alloc_chrdev_region(&dev, 0, 1, "buttons");
    /*初始化cdev*/
    cdev_init(&btn_cdev, &btn_fops);
    /*注册cdev*/
    cdev_add(&btn_cdev, dev, 1);
    /*自动创建设备文件*/
    cls = class_create(THIS_MODULE, "buttons");
    device_create(cls, NULL, dev, NULL, "mybuttons");
    /*初始化信号量*/
    /*该设备可以同时被一个进程访问*/
    sema_init(&btn_sem, 1);
    /*初始化等待队列头*/
    init_waitqueue_head(&btn_wqh);

    for(; i<ARRAY_SIZE(buttons);i++)
    {
        ret = request_irq(buttons[i].irq, btn_isr,
            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                          buttons[i].name,
                          &(buttons[i]));
    }
    init_timer(&btn_timer);
    btn_timer.function = btn_timer_func;


    return 0;
}
void __exit btn_drv_exit(void)
{
    int i = 0;
    for(; i<ARRAY_SIZE(buttons); i++)
    {
        free_irq(buttons[i].irq, buttons+i);
    }
    /*销毁设备文件*/
    device_destroy(cls, dev);
    class_destroy(cls);
    /*注销cdev*/
    cdev_del(&btn_cdev);
    /*注销设备号*/
    unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
01-03 18:57