1. 程式人生 > >7.自己寫中斷方式按鍵驅動程序

7.自己寫中斷方式按鍵驅動程序

ops tar open ask edge tpi 資源 logs 修改

request_irq()和free_irq()分析完畢後,接下來開始編寫上升沿中斷的按鍵驅動

如下圖,需要設置4個按鍵的EINT0, EINT2, EINT11, EINT19的模式為雙邊沿,且設置按鍵引腳為中斷引腳

技術分享

這裏我們只需要使用request_irq函數就行了, 在request_irq函數裏會初始chip->set_type(設置引腳和中斷模式)

1.首先添加頭文件

#include <linux/irq.h>         //要用到IRQ_EINT0和IRQT_RISING這些變量

2.在second_drv_open函數中,申請4個中斷:

/* IRQ_EINT0:     中斷號, 定義在 asm/arch/irqs.h,被linux/irq.h調用
buttons_irq :     中斷服務函數,
IRQT_ BOTHEDGE:   雙邊沿中斷, 定義在 asm/irq.h,被linux/irq.h調用
“S1”:             保存文件到/proc/interrupt/S1,
1:                dev_id,中斷函數的參數, 被用來釋放中斷服務函數,中斷時並會傳入中斷服務函數
*/ request_irq(IRQ_EINT0, buttons_irq,IRQT_BOTHEDGE, “S1”, 1); request_irq(IRQ_EINT2, buttons_irq,IRQT_ BOTHEDGE, “S2”, 1); request_irq(IRQ_EINT11, buttons_irq,IRQT_ BOTHEDGE, “S3”, 1); request_irq(IRQ_EINT19, buttons_irq,IRQT_ BOTHEDGE, “S4”, 1);

3.在file_oprations結構體中添加.release成員函數,用來釋放中斷

static
struct file_operations second_drv_fops={ .owner = THIS_MODULE, .open = second_drv_open, .read = second_drv_read, .release=second_drv_class, //裏面添加free_irq函數,來釋放中斷服務函數 };

然後寫.release成員函數,釋放中斷:

int  second_drv_class(struct inode *inode, struct file  *file)
{
  free_irq(IRQ_EINT0,
1); free_irq(IRQ_EINT2,1); free_irq(IRQ_EINT11,1); free_irq(IRQ_EINT19,1); return 0; }

4.寫action->handler中斷服務函數,在第2小節裏request_irq函數的中斷服務函數是buttons_irq

static irqreturn_t  buttons_irq (int irq, void *dev_id)     //irq:中斷號, void *:表示支持所有類型
{
  printk(“irq=%d\n”);
  return IRQ_HANDLED;
}

5.make後,然後放在開發板裏insmod,並掛載好了buttons設備節點,如下圖:

技術分享

6.通過exec 5</dev/buttons 將/dev/buttons 設備節點掛載到-sh進程下描述符5:

如下圖,使用ps查看-sh進程為801,然後ls -l /proc/801/fd 找到描述符5指向/dev/buttons

技術分享

如下圖,並申請中斷,當有按鍵按下時,就進入中斷服務函數buttons_irq()打印數據:

技術分享

6.通過exec 5<&- 將描述符5卸載

會進入.release成員second_drv_class()函數釋放中斷,

然後cat /proc/interrupts會發現申請的中斷已經註銷掉了,在-sh進程fd文件裏也沒有文件描述符5

7.改進中斷按鍵驅動程序

使用等待隊列,讓read函數沒有中斷時,進入休眠狀態,降低CPU.

使用dev_id來獲取不同按鍵的狀態,是上升沿還是下降沿觸發?

7.1接下來要用到以下幾個函數:

s3c2410_gpio_getpin(unsigned int pin);     //獲取引腳高低電平

pin: 引腳名稱,例如:S3C2410_GPA0,定義在<asm/arch/regs-gpio.h>

隊列3個函數(聲明隊列,喚醒隊列,等待隊列):

static DECLARE_WAIT_QUEUE_HEAD(qname);      

聲明一個新的等待隊列類型的中斷

qname:就是中斷名字,被用來後面的喚醒中斷和等待中斷

wake_up_interruptible(*qname);   

喚醒一個中斷,會將這個中斷重新添加到runqueue隊列(將中斷置為TASK_RUNNING狀態)

qname:指向聲明的等待隊列類型中斷名字

wait_event_interruptible(qname, condition);  

等待事件中斷函數,用來將中斷放回等待隊列,

前提是condition要為0,然後將這個中斷從runqueue隊列中刪除(將中斷置為TASK_INTERRUPTIBLE狀態),然後會在函數裏一直for(; ;)判斷condition為真才退出

註意:此時的中斷屬於僵屍進程(既不在等待隊列,也不在運行隊列),當需要這個進程時,需要使用wake_up_interruptible(*qname)來喚醒中斷

qname: (wait queue):為聲明的等待隊列的中斷名字

condition:狀態,等於0時就是中斷進入休眠, 1:退出休眠

7.2 驅動程序步驟

(1)定義引腳描述結構體數組,每個結構體都保存按鍵引腳和初始狀態,然後在中斷服務函數中通過s3c2410_gpio_getpin()來獲取按鍵是松開還是按下(因為中斷是雙邊沿觸發),並保存在key_val裏(它會在.read函數發送給用戶層)

 /*
  *引腳描述結構體
  */
 struct pin_desc{
   unsigned int  pin;
   unsigned int  pin_status;
};
 /*
  *key初始狀態(沒有按下): 0x01,0x02,0x03,0x04
  *key狀態(按下):                       0x81,0x82,0x83,0x84
  */
struct pin_desc  pins_desc[4]={
        {S3C2410_GPF0,0x01 }, 
        {S3C2410_GPF2, 0x02 },
        {S3C2410_GPG3, 0x03 },
        {S3C2410_GPG11,0x04},} ;

(2)聲明等待隊列類型的中斷button_wait:

static DECLARE_WAIT_QUEUE_HEAD(button_ wait);        //聲明等待隊列類型的中斷

(3)定義全局變量even _press,用於中斷事件標誌:

static volatile int even _press = 0;

(4)在.read函數裏,將even _press置0放入等待事件中斷函數中,判斷even _press為真,才發送數據:

even_press = 0;                                

wait_event_interruptible(button_ wait, even _press);   //當even _press為真,表示有按鍵按下,退出等待隊列  

copy_to_user(buf, &key_val, 1);       //even _press為真,有數據了,發送給用戶層      

(5)在中斷服務函數裏,發生中斷時, 將even _press置1,並喚醒中斷button_wait:

even _press = 0;  
wake_up_interruptible(&button_wait);         //喚醒中斷

7.3 更改測試程序second_interrupt_text.c

最終修改如下:

#include <sys/types.h>    //調用sys目錄下types.h文件
#include <sys/stat.h>      //stat.h獲取文件屬性
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
/*secondtext            while一直獲取按鍵信息   */
int main(int argc,char **argv)
{
 int fd,ret;
 unsigned int val=0;               
 fd=open("/dev/buttons",O_RDWR);       
 if(fd<0)
     {printf("can‘t open!!!\n");
     return -1;}
while(1) { ret=read(fd,&val,1); //讀取一個值,(當在等待隊列時,本進程就會進入休眠狀態) if(ret<0) { printf("read err!\n"); continue; } printf("key_val=0X%x\r\n",val); } return 0; }

8.運行結果

insmod second_interrupt.ko //掛載驅動設備

./second_interrupt_text & //後臺運行測試程序

創建了4個中斷,如下圖:

技術分享

當沒有按鍵按下時,這個進程就處於靜止狀態staitc,如下圖所示:

技術分享

在等待隊列(休眠狀態)下,該進程占用了CPU0%資源,如下圖所示:

技術分享

當有按鍵按下時,便打印數據,如下圖所示:

技術分享

下節繼續改進按鍵程序—使用poll機制

本節驅動代碼如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>  
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>


static struct class *seconddrv_class;                 
static struct class_device   *seconddrv_class_devs; 

 
/*    聲明等待隊列類型中斷 button_wait      */
static DECLARE_WAIT_QUEUE_HEAD(button_wait);

 
 /*
  * 定義中斷事件標誌
  * 0:進入等待隊列        1:退出等待隊列
  */
static int even_press=0;                          

 /*
  * 定義全局變量key_val,保存key狀態
  */
static int key_val=0;                          

 /*
  *引腳描述結構體
  */
 struct pin_desc{
   unsigned int  pin;
   unsigned int  pin_status;
};

 
 /*
  *key初始狀態(沒有按下): 0x01,0x02,0x03,0x04
  *key狀態(按下):                       0x81,0x82,0x83,0x84
  */
struct pin_desc  pins_desc[4]={
                   {S3C2410_GPF0,0x01 },
                   {S3C2410_GPF2, 0x02 },
                   {S3C2410_GPG3, 0x03 },
                   {S3C2410_GPG11,0x04},} ;


int  second_drv_class(struct inode *inode, struct file  *file)  //卸載中斷
{
  free_irq(IRQ_EINT0,&pins_desc[0]);
  free_irq(IRQ_EINT2,&pins_desc[1]);
  free_irq(IRQ_EINT11,&pins_desc[2]);
  free_irq(IRQ_EINT19,&pins_desc[3]);
return 0; } /* 確定是上升沿還是下降沿 */ static irqreturn_t buttons_irq (int irq, void *dev_id) //中斷服務函數 { struct pin_desc *pindesc=(struct pin_desc *)dev_id; //獲取引腳描述結構體 unsigned int pin_val=0; pin_val=s3c2410_gpio_getpin(pindesc->pin); if(pin_val) { /*沒有按下 (下降沿),清除0x80*/ key_val=pindesc->pin_status&0xef; } else { /*按下(上升沿),加上0x80*/ key_val=pindesc->pin_status|0x80; } even_press=1; //退出等待隊列 wake_up_interruptible(&button_wait); //喚醒 中斷 return IRQ_HANDLED; } static int second_drv_open(struct inode *inode, struct file *file) { request_irq(IRQ_EINT0,buttons_irq,IRQT_BOTHEDGE,"S1",&pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq,IRQT_BOTHEDGE,
"S2", &pins_desc[1]); request_irq(IRQ_EINT11, buttons_irq,IRQT_BOTHEDGE, "S3", &pins_desc[2]); request_irq(IRQ_EINT19, buttons_irq,IRQT_BOTHEDGE, "S4", &pins_desc[3]); return 0; } static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { even_press=0; /*將中斷 進入等待隊列(休眠狀態)*/ wait_event_interruptible(button_wait, even_press); /*有按鍵按下,退出等待隊列,上傳key_val 給用戶層*/ if(copy_to_user(buf,&key_val,sizeof(key_val))) return EFAULT; return 0; } static struct file_operations second_drv_fops={ .owner = THIS_MODULE, .open = second_drv_open, .read = second_drv_read, .release=second_drv_class, //裏面添加free_irq函數,來釋放中斷服務函數 }; volatile int second_major; static int second_drv_init(void) { second_major=register_chrdev(0,"second_drv",&second_drv_fops); //創建驅動 seconddrv_class=class_create(THIS_MODULE,"second_dev"); //創建類名 seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons"); return 0; } static int second_drv_exit(void) { unregister_chrdev(second_major,"second_drv"); //卸載驅動 class_device_unregister(seconddrv_class_devs); //卸載類設備 class_destroy(seconddrv_class); //卸載類 return 0; } module_init(second_drv_init); module_exit(second_drv_exit); MODULE_LICENSE("GPL v2");

7.自己寫中斷方式按鍵驅動程序