編寫按鍵中斷驅動——解決同步互斥阻塞
- 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
- 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
- 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
- 開發環境:Linux 2.6.22.6 內核、arm-linux-gcc-3.4.5-glibc-2.3.6工具鏈
目錄
一、前言
在上邊的應用程序中,存在一個問題:同一個時間中,可以打開兩個或以上的應用程序。
提問:那如果我們想要在同一時間中,只能打開一個應用程序該如何修改呢?
回答:在驅動程序中定義static int canopen = 1;
在button_drv_open
函數中添加:
/* canopen != 0說明文件已經被打開,返回-EBUSY */
if(--canopen != 0){
canopen++;
return -EBUSY;
}
在button_drv_close
函數中添加:
canopen++; /* 如果打開文件,關閉時canopen恢復 */
通過測試,發現這個程序可以達到上面的目的,但是還是有問題:
對於C語言,程序最終會被翻譯成彙編,一條--canopen
指令會被翻譯成3條彙編指令(讀出、修改、寫回),如果一個A應用程序任務在執行讀出時,此時切換到B應用程序,也執行讀出命令(A、B是同一個應用程序),那麼最終A、B都可以同時存在。
二、引入原子操作
1、設置原子變量
static atomic_t canopen = ATOMIC_INIT(1); //定義原子變量v並初始化爲1
2、在button_drv_open中採用原子操作
/* canopen != 0說明文件已經被打開,返回-EBUSY
* 自減操作後測試其是否爲0,爲0則返回true,否則返回false
*/
if(!atomic_dec_and_test(&canopen)){
atomic_inc(&canopen); //原子變量增加1
return -EBUSY;
}
所以此時就引出了一個互斥的話題
3、在button_drv_close中採用原子操作
atomic_inc(&canopen); //原子變量增加1
4、完整驅動代碼
#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/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
int major;
static struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static struct fasync_struct *button_async;
static atomic_t canopen = ATOMIC_INIT(1); //定義原子變量v並初始化爲1
/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04
* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84
*/
static unsigned char key_val;
/* 按鍵信息 */
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
/* 中斷註冊信息 */
struct buttonirq_decs{
unsigned int irq;
unsigned long flags;
const char *devname;
void *dev_id;
};
struct buttonirq_decs buttonirqs_decs[4] = {
{IRQ_EINT0, IRQT_BOTHEDGE, "S2", &pins_desc[0]},
{IRQ_EINT2, IRQT_BOTHEDGE, "S3", &pins_desc[1]},
{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};
/* 生成一個等待隊列的隊頭,名字爲button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中斷事件標誌, 中斷服務程序將它置1,button_drv_read將它清0 */
static volatile int ev_press = 0;
/* 中斷服務函數 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin); //讀取IO口電平
if(pinval){
/* 鬆開 */
key_val = 0x80 | pindesc->key_val;
}else{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; //發生中斷
wake_up_interruptible(&button_waitq); // 喚醒休眠的進程
kill_fasync(&button_async, SIGIO, POLL_IN); //發SIGIO信號
return IRQ_RETVAL(IRQ_HANDLED);
}
/* open驅動函數 */
static int button_drv_open(struct inode *inode, struct file *file)
{
unsigned int i;
int err;
/* canopen != 0說明文件已經被打開,返回-EBUSY
* 自減操作後測試其是否爲0,爲0則返回true,否則返回false
*/
if(!atomic_dec_and_test(&canopen)){
atomic_inc(&canopen); //原子變量增加1
return -EBUSY;
}
/* 註冊中斷 */
for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
if(err){
printk("func button_drv_open err: request_irq num:%d\n", i);
break;
}
}
/* 出現錯誤,釋放已經註冊的中斷 */
if(err){
i--;
for (; i >= 0; i--)
free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
return -EBUSY;
}
return 0;
}
/* read驅動函數 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
unsigned long ret = 0;
int err;
/* 如果沒有按鍵動作, 休眠 */
err = wait_event_interruptible(button_waitq, ev_press);
if (err){
printk("func button_drv_read() err: wait_event_interruptible\n");
return err;
}
/* 如果有按鍵動作, 返回鍵值 */
ret = copy_to_user(buf, &key_val, 1);
ev_press = 0; //清中斷標誌
if(ret < 0){
printk("func button_drv_read() err: copy_to_user\n");
return -EFAULT;
}
return sizeof(key_val);
}
/* close驅動函數 */
int button_drv_close (struct inode *inode, struct file *file)
{
int i;
/* 取消中斷 */
for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
atomic_inc(&canopen); //原子變量增加1
return 0;
}
unsigned int buton_drv_poll (struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不會立即休眠
/* 中斷已經發生 */
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask; //返回給do_poll函數中的mask
}
/* 調用fasync_helper根據on值是否button_async->fa_file=驅動文件file,裏面有PID */
static int button_drv_fasync(int fd, struct file *file, int on)
{
printk("driver: fifth_drv_fasync\n");
return fasync_helper(fd, file, on, &button_async);
}
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
.open = button_drv_open,
.read = button_drv_read,
.release = button_drv_close,
.poll = buton_drv_poll,
.fasync = button_drv_fasync,
};
/* 入口函數
* 執行”insmod button_drv.ko”命令時就會調用這個函數
*/
static int button_drv_init(void)
{
//註冊一個字符設備,名字爲button_drv
major = register_chrdev(0, "button_drv", &button_drv_fops);
//創建一個類,名字爲buttond_rv(/class/button_drv)
buttondrv_class = class_create(THIS_MODULE, "button_drv");
//創建一個設備節點,名爲button(/dev/button)
buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button");
/* 映射物理地址 */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
/* 出口函數
* 執行”rmmod button_drv.ko”命令時就會調用這個函數
*/
static void button_drv_exit(void)
{
unregister_chrdev(major, "button_drv"); // 卸載字符
class_device_unregister(buttondrv_class_dev); //刪除設備節點
class_destroy(buttondrv_class); //銷燬類
/* 取消映射 */
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
5、完整測試代碼
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
/* 信號處理函數 */
void my_signal_fun(int signum)
{
int ret;
unsigned char key_val;
ret = read(fd, &key_val, 1);
if(ret < 0)
printf(" func read() err\n");
else
printf("key_val = 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
int Oflags;
signal(SIGIO, my_signal_fun); //給SIGIO 信號註冊信號處理函數my_signal_fun
fd = open("/dev/button", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fcntl(fd, F_SETOWN, getpid()); //把進程 ID 告訴驅動
Oflags = fcntl(fd, F_GETFL); //取得文件描述符filedes的文件狀態標誌
fcntl(fd, F_SETFL, Oflags | FASYNC); //FASYNC位發生變化時,會導致驅動程序的fasync函數被調用
while (1)
{
sleep(1000);
}
return 0;
}
6、運行結果
二、引入信號量
信號量(semaphore)是用於保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。當獲取不到信號量時,進程進入休眠等待狀態。
1、定義信號量
static DECLARE_MUTEX(button_lock); //定義互斥鎖
2、在button_drv_open中獲取信號量
/* 獲取信號量
* 如果第一次獲取。則可以獲取一個信號量
* 如果第二次獲取。則在down()中陷入休眠
*/
down(&button_lock);
3、在button_drv_close中釋放信號量
up(&button_lock); //釋放信號量
4、完整驅動代碼
#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/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
int major;
static struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static struct fasync_struct *button_async;
//static atomic_t canopen = ATOMIC_INIT(1); //定義原子變量v並初始化爲1
static DECLARE_MUTEX(button_lock); //定義互斥鎖
/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04
* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84
*/
static unsigned char key_val;
/* 按鍵信息 */
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
/* 中斷註冊信息 */
struct buttonirq_decs{
unsigned int irq;
unsigned long flags;
const char *devname;
void *dev_id;
};
struct buttonirq_decs buttonirqs_decs[4] = {
{IRQ_EINT0, IRQT_BOTHEDGE, "S2", &pins_desc[0]},
{IRQ_EINT2, IRQT_BOTHEDGE, "S3", &pins_desc[1]},
{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};
/* 生成一個等待隊列的隊頭,名字爲button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中斷事件標誌, 中斷服務程序將它置1,button_drv_read將它清0 */
static volatile int ev_press = 0;
/* 中斷服務函數 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin); //讀取IO口電平
if(pinval){
/* 鬆開 */
key_val = 0x80 | pindesc->key_val;
}else{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; //發生中斷
wake_up_interruptible(&button_waitq); // 喚醒休眠的進程
kill_fasync(&button_async, SIGIO, POLL_IN); //發SIGIO信號
return IRQ_RETVAL(IRQ_HANDLED);
}
/* open驅動函數 */
static int button_drv_open(struct inode *inode, struct file *file)
{
unsigned int i;
int err;
#if 0
/* canopen != 0說明文件已經被打開,返回-EBUSY
* 自減操作後測試其是否爲0,爲0則返回true,否則返回false
*/
if(!atomic_dec_and_test(&canopen)){
atomic_inc(&canopen); //原子變量增加1
return -EBUSY;
}
#endif
/* 獲取信號量
* 如果第一次獲取。則可以獲取一個信號量
* 如果第二次獲取。則在down()中陷入休眠
*/
down(&button_lock);
/* 註冊中斷 */
for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
if(err){
printk("func button_drv_open err: request_irq num:%d\n", i);
break;
}
}
/* 出現錯誤,釋放已經註冊的中斷 */
if(err){
i--;
for (; i >= 0; i--)
free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
return -EBUSY;
}
return 0;
}
/* read驅動函數 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
unsigned long ret = 0;
int err;
/* 如果沒有按鍵動作, 休眠 */
err = wait_event_interruptible(button_waitq, ev_press);
if (err){
printk("func button_drv_read() err: wait_event_interruptible\n");
return err;
}
/* 如果有按鍵動作, 返回鍵值 */
ret = copy_to_user(buf, &key_val, 1);
ev_press = 0; //清中斷標誌
if(ret < 0){
printk("func button_drv_read() err: copy_to_user\n");
return -EFAULT;
}
return sizeof(key_val);
}
/* close驅動函數 */
int button_drv_close (struct inode *inode, struct file *file)
{
int i;
/* 取消中斷 */
for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
//atomic_inc(&canopen); //原子變量增加1
up(&button_lock); //釋放信號量
return 0;
}
unsigned int buton_drv_poll (struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不會立即休眠
/* 中斷已經發生 */
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask; //返回給do_poll函數中的mask
}
/* 調用fasync_helper根據on值是否button_async->fa_file=驅動文件file,裏面有PID */
static int button_drv_fasync(int fd, struct file *file, int on)
{
printk("driver: fifth_drv_fasync\n");
return fasync_helper(fd, file, on, &button_async);
}
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
.open = button_drv_open,
.read = button_drv_read,
.release = button_drv_close,
.poll = buton_drv_poll,
.fasync = button_drv_fasync,
};
/* 入口函數
* 執行”insmod button_drv.ko”命令時就會調用這個函數
*/
static int button_drv_init(void)
{
//註冊一個字符設備,名字爲button_drv
major = register_chrdev(0, "button_drv", &button_drv_fops);
//創建一個類,名字爲buttond_rv(/class/button_drv)
buttondrv_class = class_create(THIS_MODULE, "button_drv");
//創建一個設備節點,名爲button(/dev/button)
buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button");
/* 映射物理地址 */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
/* 出口函數
* 執行”rmmod button_drv.ko”命令時就會調用這個函數
*/
static void button_drv_exit(void)
{
unregister_chrdev(major, "button_drv"); // 卸載字符
class_device_unregister(buttondrv_class_dev); //刪除設備節點
class_destroy(buttondrv_class); //銷燬類
/* 取消映射 */
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
5、完整測試代碼
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
/* 信號處理函數 */
void my_signal_fun(int signum)
{
int ret;
unsigned char key_val;
ret = read(fd, &key_val, 1);
if(ret < 0)
printf(" func read() err\n");
else
printf("key_val = 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
int Oflags;
signal(SIGIO, my_signal_fun); //給SIGIO 信號註冊信號處理函數my_signal_fun
fd = open("/dev/button", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fcntl(fd, F_SETOWN, getpid()); //把進程 ID 告訴驅動
Oflags = fcntl(fd, F_GETFL); //取得文件描述符filedes的文件狀態標誌
fcntl(fd, F_SETFL, Oflags | FASYNC); //FASYNC位發生變化時,會導致驅動程序的fasync函數被調用
while (1)
{
sleep(1000);
}
return 0;
}
6、運行結果
四、阻塞與阻塞
- 阻塞
是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件後再進行操作。
被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。 - 非阻塞
進程在不能進行設備操作時並不掛起,它或者放棄,或者不停地查詢,直至可以進行操作爲止。
1、添加非阻塞標誌
fd = open("/dev/button", O_RDWR | O_NONBLOCK);
2、在button_drv_open中採用判斷採用何種方式
if(file->f_flags & O_NONBLOCK){ //非阻塞
if(down(&button_lock)) //申請信號量
return -EBUSY; //返回值給應用程序打印
}else
down(&button_lock); //阻塞方式,申請信號量
3、在button_drv_read中採用判斷採用何種方式
if(file->f_flags & O_NONBLOCK){ //非阻塞
if(!ev_press) //沒有按鍵按下
return -EAGAIN; //返回
}
4、完整驅動代碼
#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/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
int major;
static struct class *buttondrv_class;
static struct class_device *buttondrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static struct fasync_struct *button_async;
//static atomic_t canopen = ATOMIC_INIT(1); //定義原子變量v並初始化爲1
static DECLARE_MUTEX(button_lock); //定義互斥鎖
/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04
* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84
*/
static unsigned char key_val;
/* 按鍵信息 */
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
/* 中斷註冊信息 */
struct buttonirq_decs{
unsigned int irq;
unsigned long flags;
const char *devname;
void *dev_id;
};
struct buttonirq_decs buttonirqs_decs[4] = {
{IRQ_EINT0, IRQT_BOTHEDGE, "S2", &pins_desc[0]},
{IRQ_EINT2, IRQT_BOTHEDGE, "S3", &pins_desc[1]},
{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};
/* 生成一個等待隊列的隊頭,名字爲button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中斷事件標誌, 中斷服務程序將它置1,button_drv_read將它清0 */
static volatile int ev_press = 0;
/* 中斷服務函數 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin); //讀取IO口電平
if(pinval){
/* 鬆開 */
key_val = 0x80 | pindesc->key_val;
}else{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; //發生中斷
wake_up_interruptible(&button_waitq); // 喚醒休眠的進程
kill_fasync(&button_async, SIGIO, POLL_IN); //發SIGIO信號
return IRQ_RETVAL(IRQ_HANDLED);
}
/* open驅動函數 */
static int button_drv_open(struct inode *inode, struct file *file)
{
unsigned int i;
int err;
#if 0
/* canopen != 0說明文件已經被打開,返回-EBUSY
* 自減操作後測試其是否爲0,爲0則返回true,否則返回false
*/
if(!atomic_dec_and_test(&canopen)){
atomic_inc(&canopen); //原子變量增加1
return -EBUSY;
}
#endif
if(file->f_flags & O_NONBLOCK){ //非阻塞
if(down_trylock(&button_lock)) //申請信號量
return -EBUSY; //不成功,返回值給應用程序打印
}else
down(&button_lock); //阻塞方式,申請信號量
/* 註冊中斷 */
for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
if(err){
printk("func button_drv_open err: request_irq num:%d\n", i);
break;
}
}
/* 出現錯誤,釋放已經註冊的中斷 */
if(err){
i--;
for (; i >= 0; i--)
free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
return -EBUSY;
}
return 0;
}
/* read驅動函數 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
unsigned long ret = 0;
int err;
if(file->f_flags & O_NONBLOCK){ //非阻塞
if(!ev_press) //沒有按鍵按下
return -EAGAIN; //返回
}
/* 阻塞方式,如果沒有按鍵動作, 休眠 */
err = wait_event_interruptible(button_waitq, ev_press);
if (err){
printk("func button_drv_read() err: wait_event_interruptible\n");
return err;
}
/* 如果有按鍵動作, 返回鍵值 */
ret = copy_to_user(buf, &key_val, 1);
ev_press = 0; //清中斷標誌
if(ret < 0){
printk("func button_drv_read() err: copy_to_user\n");
return -EFAULT;
}
return sizeof(key_val);
}
/* close驅動函數 */
int button_drv_close (struct inode *inode, struct file *file)
{
int i;
/* 取消中斷 */
for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
//atomic_inc(&canopen); //原子變量增加1
up(&button_lock); //釋放信號量
return 0;
}
unsigned int buton_drv_poll (struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不會立即休眠
/* 中斷已經發生 */
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask; //返回給do_poll函數中的mask
}
/* 調用fasync_helper根據on值是否button_async->fa_file=驅動文件file,裏面有PID */
static int button_drv_fasync(int fd, struct file *file, int on)
{
printk("driver: fifth_drv_fasync\n");
return fasync_helper(fd, file, on, &button_async);
}
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
.open = button_drv_open,
.read = button_drv_read,
.release = button_drv_close,
.poll = buton_drv_poll,
.fasync = button_drv_fasync,
};
/* 入口函數
* 執行”insmod button_drv.ko”命令時就會調用這個函數
*/
static int button_drv_init(void)
{
//註冊一個字符設備,名字爲button_drv
major = register_chrdev(0, "button_drv", &button_drv_fops);
//創建一個類,名字爲buttond_rv(/class/button_drv)
buttondrv_class = class_create(THIS_MODULE, "button_drv");
//創建一個設備節點,名爲button(/dev/button)
buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button");
/* 映射物理地址 */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
/* 出口函數
* 執行”rmmod button_drv.ko”命令時就會調用這個函數
*/
static void button_drv_exit(void)
{
unregister_chrdev(major, "button_drv"); // 卸載字符
class_device_unregister(buttondrv_class_dev); //刪除設備節點
class_destroy(buttondrv_class); //銷燬類
/* 取消映射 */
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
5、完整測試代碼
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int ret;
unsigned char key_val;
int fd;
fd = open("/dev/button", O_RDWR | O_NONBLOCK);
if (fd < 0)
{
printf("can't open!\n");
}
while (1)
{
ret = read(fd, &key_val, 1);
printf("key_val = 0x%x ret = %d\n", key_val, ret);
}
return 0;
}