// 在Linux下的中斷方式讀取按鍵驅動程序 //包含外部中斷 休眠 加入poll機制 // 采用異步通知的方式 // 驅動程序發 ---> app接收 (通過kill_fasync()發送) // 為了使設備支持異步通知機制,驅動程序中涉及以下3項工作: // 1. 支持F_SETOWN命令,能在這個控制命令處理中設置filp->f_owner為對應進程ID。 // 不過此項工作已由內核完成,設備驅動無須處理。 // 2. 支持F_SETFL命令的處理,每當FASYNC標志改變時,驅動程序中的fasync()函數將得以執行。 // 驅動中應該實現fasync()函數。 // 3. 在設備資源可獲得時,調用kill_fasync()函數激發相應的信號 // 應用程序: // fcntl(fd, F_SETOWN, getpid()); // 告訴內核,發給誰 // Oflags = fcntl(fd, F_GETFL); // fcntl(fd, F_SETFL, Oflags | FASYNC); // 改變fasync標記,最終會調用到驅動的faync > fasync_helper:初始化/釋放fasync_struct // 外部中斷測試程序 包含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/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <linux/poll.h> #define usingatomic (0) // 0使用信號量 1使用的是原子操作 //設備類 static struct class *Eint_class; // 設備節點 static struct class_device *Eint_class_devs; // 地址映射 volatile unsigned long *gpfcon; volatile unsigned long *gpfdat; volatile unsigned long *gpgcon; volatile unsigned long *gpgdat; // 全局變量 存放中斷讀出的鍵值 static unsigned int key_val; //創建一個休眠隊列 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /* 中斷事件標志, 中斷服務程序將它置1,third_drv_read將它清0 */ static volatile int ev_press = 0; //信號量初始化結構體 static struct fasync_struct *button_async_queue; // 定時器結構體 struct timer_list buttons_timer; // 存儲外部中斷號和鍵值結構體變量 static struct pin_desc *irq_pd; //定義結構體 存放按鍵 pin 端口 key_val鍵值 struct pin_desc { unsigned int pin; unsigned int key_val; }; #if usingatomic //定義原子變量v並初始化為1 atomic_t canopen = ATOMIC_INIT(1); #else //定義互斥鎖 信號量 static DECLARE_MUTEX(button_lock); #endif //定義結構體數組 存放中斷端口和鍵值 struct pin_desc pins_desc[4]={ {S3C2410_GPF0,0x01}, {S3C2410_GPF2,0x02}, {S3C2410_GPG3,0x03}, {S3C2410_GPG11,0x04}}; //中斷服務程序 //讀取鍵值 static irqreturn_t buttons_irq(int irq, void *ignored) { irq_pd = ( struct pin_desc *)ignored; mod_timer(&buttons_timer, jiffies+HZ/100); //10ms 產生中斷 // return IRQ_RETVAL(IRQ_HANDLED); return IRQ_HANDLED; } //定時器中斷函數 static void buttons_timer_function(unsigned long data) { struct pin_desc *pins_desc= irq_pd; unsigned int pinval; if(!pins_desc) return; pinval=s3c2410_gpio_getpin(pins_desc->pin); //讀取IO的值 if(pinval) { key_val =0x80|pins_desc->key_val; } else { key_val =pins_desc->key_val; } ev_press = 1; /* 表示中斷發生了 */ wake_up_interruptible(&button_waitq); /* 喚醒休眠的進程 */ kill_fasync (&button_async_queue, SIGIO, POLL_IN);//發送信號給app } //打開設備調用 //初始化IO端口 配置為輸入模式 //GPF0-->S2 GPF2-->S3 GPG3-->S4 GPG11-->S5 static int Eint_drv_open(struct inode *inode, struct file *file) { // *gpfcon &=~((3<<2*0)|(3<<2*2)); // *gpgcon &=~((3<<3*2)|(3<<11*2)); #if usingatomic if(!atomic_dec_and_test(&canopen))// 原子操作 { atomic_inc(&canopen);//自加1 printk("this a user in the use of\n"); return -EBUSY;//返回忙 } #else if (file->f_flags & O_NONBLOCK) { //非阻塞 立馬返回 if (down_trylock(&button_lock)) return -EBUSY; } else { down(&button_lock); } #endif printk("Eint_drv_open successed!\n"); request_irq(IRQ_EINT0,buttons_irq, IRQT_BOTHEDGE, "s2", &pins_desc[0]);//EINT0邊沿觸發方式 request_irq(IRQ_EINT2,buttons_irq, IRQT_BOTHEDGE, "s3", &pins_desc[1]);//EINT2邊沿觸發方式 request_irq(IRQ_EINT11,buttons_irq, IRQT_BOTHEDGE, "s4", &pins_desc[2]);//EINT11邊沿觸發方式 request_irq(IRQ_EINT19,buttons_irq, IRQT_BOTHEDGE, "s5", &pins_desc[3]);//EINT19邊沿觸發方式 return 0; } //write時候調用 static ssize_t Eint_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { } //read時候調用 //讀取按鍵值 ssize_t Eint_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { if(size != 1) return -EINVAL; if (file->f_flags & O_NONBLOCK) { //非阻塞 立馬返回 if (!ev_press) return -EAGAIN; } else { //如果沒有按鍵按下 則休眠 wait_event_interruptible(button_waitq,ev_press); } //如果有按鍵動作發生的話 則返回 copy_to_user(buf, &key_val, 1); ev_press = 0;//清中斷標志位 // printk("key_val = 0x%x\n", key_val); } //關閉驅動時候調用 static int Eint_drv_colse(struct inode *inode, struct file *file) { #if usingatomic atomic_inc(&canopen);//自加1 #else up(&button_lock); #endif free_irq(IRQ_EINT0, &pins_desc[0]);//EINT0釋放中斷 free_irq(IRQ_EINT2, &pins_desc[1]);//EINT2釋放中斷 free_irq(IRQ_EINT11, &pins_desc[2]);//EINT11釋放中斷 free_irq(IRQ_EINT19, &pins_desc[3]);//EINT19釋放中斷 printk("Eint_drv_colse successed!\n"); } //poll時候調用 // 在規定時間內沒有按下按鍵 就返回超時 //中斷沒有發生 就return 0,在do_sys_poll中就會讓系統休眠,喚醒休眠是chedule_timeout(__timeou)超時 //中斷發生 return POLLIN | POLLRDNORM,在do_sys_poll退出休眠,喚醒進程 static unsigned int Eint_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; } //在應用程序中使用fcnt() 時候調用 static int Eint_drvl_fasync (int fd, struct file *filp, int on) { printk("\ndrivec:signal_fasync successed !\n"); return fasync_helper (fd, filp, on, &button_async_queue); } //告訴內核 static struct file_operations Eint_drv_fops = { .owner = THIS_MODULE, // 這是一個宏,推向編譯模塊時自動創建的__this_module變量 .open = Eint_drv_open, .write = Eint_drv_write, .read = Eint_drv_read, .release= Eint_drv_colse, .poll = Eint_drv_poll, .fasync = Eint_drvl_fasync, }; int major;//自動分配主設備號 //安裝驅動的時候調用 //注冊驅動 創建設備類 創建設備節點 創建虛擬地址 創建定時器任務 int Eint_drv_init(void) { // 創建一個定時器 init_timer(&buttons_timer); buttons_timer.function = buttons_timer_function; add_timer(&buttons_timer); major=register_chrdev( 0, "key_drv",&Eint_drv_fops);//告訴內核 注冊驅動 Eint_class = class_create(THIS_MODULE, "key_drv");//獲取一個設備信息類 if (IS_ERR(Eint_class)) return PTR_ERR(Eint_class); Eint_class_devs = class_device_create(Eint_class, NULL, MKDEV(major, 0), NULL, "buttons"); if (unlikely(IS_ERR(Eint_class_devs))) return PTR_ERR(Eint_class_devs); //轉換虛擬地址 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon+1; gpgcon =(volatile unsigned long *)ioremap(0x56000060,16); gpgdat =gpgcon+1; printk("Eint_drv_init successed!\n"); return 0; } //卸載驅動程序的時候調用 //卸載驅動 刪除設備 刪除設備節點 刪除地址映射 void Eint_drv_exit(void) { unregister_chrdev( major, "key_drv");//卸載驅動 class_device_unregister(Eint_class_devs);//刪除設備 class_destroy(Eint_class);//刪除設備節點 iounmap(gpgcon);//刪除地址映射 iounmap(gpfcon); printk("\nEint_drv_exit successed!\n"); } module_init(Eint_drv_init); module_exit(Eint_drv_exit); MODULE_LICENSE("GPL");