7.自己寫中斷方式按鍵驅動程序
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成員函數,用來釋放中斷
staticstruct 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.自己寫中斷方式按鍵驅動程序