Linux驱动之按键中断


该按键驱动原理虽简单,但是在处理中却运用到了Linux驱动中中断的一些关键技术,比如“顶半部”和“底半部”使用,等待队列的设置。

这里“顶半部”即中断处理函数运行时间很短,基本就做了两件事:1、关中断;2、调用定时器。具体代码如下:

  1. <span style="font-size:13px;">static irqreturn_t key_eint_handler(int irq, void *dev_id)  
  2. {  
  3.     int cnt,key_index;  
  4.     key_index = 0;  
  5.     //下面的for循环确定是那个按键产生了中断,并把按键编码存储在key_index中   
  6.     for(cnt=0; cnt<KEY_NUM; cnt++)  
  7.     {  
  8.         if(g_key_info[cnt].irq_no == irq)  
  9.         {  
  10.             key_index = g_key_info[cnt].key_no;  
  11.             break;  
  12.         }  
  13.     }  
  14. //    printk(KERN_NOTICE "Eint %d\n",key_index);       
  15.   
  16.     disable_irq(g_key_info[key_index].irq_no); //disable irq  关对应中断   
  17.   
  18.   
  19.     g_key_dev->keystatus[key_index] = KEYSTATUS_X; //set key in unsure state    状态设置成不确定型   
  20.   
  21.   
  22.     g_key_timer[key_index].expires = jiffies + KEY_DELAY_20MS; //set timer value    timer_list函数设置   
  23.   
  24.     add_timer(&g_key_timer[key_index]); //start timer  激活定时器  20ms后执行g_key_timer 函数   
  25.   
  26.     return IRQ_HANDLED;      //中断服务程序返回值   
  27. }</span>  

这里“底半部”通过设置内核定时器实现,“顶半部”调用定时器后便马上退出了,而定时器等待定时时间到达时调用设定的函数完成中断处理函数应该完成的事,即去抖动情况,确定按键被按下后,保存该按键序号,并唤醒被睡眠的进程,读取按键序号值。具体代码:

  1. static void keyEvent(int key_index)  
  2. {  
  3.     g_key_dev->buf[g_key_dev->head] = key_index;  
  4.     g_key_dev->head = INC_BUF_POINTOR(g_key_dev->head,MAX_KEY_BUF);  
  5.     wake_up_interruptible(&g_key_dev->wq);  
  6. }  
  7.   
  8. static void key_timer_handler(unsigned long data)   //data是key_eint_handler中传来的key_index值   
  9. {  
  10.     int key_index = data;  
  11.     //printk("B:get key %d\n",s3c2410_gpio_getpin(g_tkey_info[key_index].gpio_port));   
  12.   
  13.     if (ISKEY_DOWN(key_index))      //按键被按下   
  14.     {  
  15.        printk(KERN_NOTICE "B\n");  
  16.   
  17.         if(g_key_dev->keystatus[key_index] == KEYSTATUS_X)   //情况一、按键状态不明确   
  18.         {  
  19.             g_key_dev->keystatus[key_index] = KEYSTATUS_DOWN; //change key state  // 按键设置为低   
  20.   
  21.             g_key_timer[key_index].expires = jiffies + KEY_DELAY_100MS; //re_initial timer   //设定延时时间   
  22.   
  23.               
  24.             keyEvent(key_index);     //保存按键序号,并唤醒等待队列中的读进程   
  25.   
  26.             add_timer(&g_key_timer[key_index]); //restart timer   //激活定时器   
  27.   
  28.         }  
  29.         else //wait for user release the key    //情况二、按键按下 进入等待释放程序   
  30.   
  31.         {  
  32.             g_key_timer[key_index].expires = jiffies + KEY_DELAY_100MS;    //设定延时时间   
  33.             add_timer(&g_key_timer[key_index]);    //激活定时间,时间到后,再次进入g_key_timer函数 ,检测按键状态   
  34.         }  
  35.     }  
  36.     else //user have released the key      
  37.   
  38.     {  
  39.         g_key_dev->keystatus[key_index] = KEYSTATUS_UP;  
  40.         //del_timer(&g_key_timer[key_index]);   
  41.   
  42.         enable_irq(g_key_info[key_index].irq_no);  
  43.     }  
  44. }  

当上层应用调用read接口时,若按键缓存区头尾不相等时表明有数据可读,但是在读取数据前调用local_irq_save( )函数将中断关闭,读取完毕之后在恢复中断;相反,如果按键缓存区头尾相等则说明没有数据可读,该进程就会被睡眠,实现如下:

  1. static ssize_t key_read(struct file *filp, char *buf, size_t count, loff_t *ppos)  
  2. {  
  3.     unsigned int ret,temp;  
  4.     unsigned long flag;  
  5.     retry:  
  6.     if(g_key_dev->head != g_key_dev->tail)  
  7.     {  
  8.         local_irq_save(flag); //进入临界区,关闭中断   
  9.   
  10.         ret = g_key_dev->buf[g_key_dev->tail]; //读取尾部指针所指内容   
  11.   
  12.         g_key_dev->tail = INC_BUF_POINTOR(g_key_dev->tail, MAX_KEY_BUF);  
  13.         local_irq_restore(flag); //退出临界区   
  14.   
  15.         //printk(KERN_NOTICE "driver key_read,key no:%d\n",ret);   
  16.   
  17.         temp = copy_to_user(buf, &ret, sizeof(unsigned int));  
  18.         //printk(KERN_NOTICE "copy to user return %d\n", temp);   
  19.   
  20.         return (sizeof(unsigned int));  
  21.     }  
  22.     else  
  23.     {  
  24.         //printk(KERN_NOTICE "A\n");   
  25.   
  26.         if(filp->f_flags & O_NONBLOCK)  
  27.         {  
  28.             return -EAGAIN;  
  29.         }  
  30.   
  31.         //printk("E:test %d\n",s3c2410_gpio_getpin(g_tkey_info[0].gpio_port));   
  32.   
  33.         interruptible_sleep_on(&(g_key_dev->wq));         //将该等待队列设置为可中断睡眠   
  34.           
  35.         goto retry;  
  36.     }  
  37. //    return 0;   
  38.   
  39. }  

相关内容