linux驅動學習筆記---實現中斷下半部以及驅動編寫規範(七)【轉】

  • 2019 年 10 月 10 日
  • 筆記

轉自:https://blog.csdn.net/weixin_42471952/article/details/81609141

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。 本文鏈接:https://blog.csdn.net/weixin_42471952/article/details/81609141 中斷下半部: tasklet : struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); //下半部要執行的程式碼 unsigned long data; // 傳遞給func的參數 }; 1, 初始化tasklet tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data) 2, 在中斷上半部,將tasklet加入到內核執行緒 tasklet_schedule(struct tasklet_struct * t) 3, 模組卸載的時候,需要從內核執行緒中移除tasklet tasklet_kill(struct tasklet_struct * t) ------------------------------------------------------------------ typedef void (*work_func_t)(struct work_struct *work); struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func;//下半部要執行的程式碼 };

1, 初始化work INIT_WORK(struct work_struct * work, work_func_t func); work_func_t func為結構體struct work_struct中的函數指針; 2, 在中斷上半部,將work加入到內核執行緒 schedule_work(struct work_struct * work); 3, 模組卸載的時候,需要從內核執行緒中移除work cancel_work_sync(struct work_struct * work)

工作隊列(work queue)是另外一種將工作推後執行的形式,它和前面討論的tasklet有所不同。工作隊列可以把工作推後,交由一個內核執行緒去執行,也就是說,這個下半部分可以在進程上下文中執行。這樣,通過工作隊列執行的程式碼能佔盡進程上下文的所有優勢。最重要的就是工作隊列允許被重新調度甚至是睡眠。

那麼,什麼情況下使用工作隊列,什麼情況下使用tasklet。如果推後執行的任務需要睡眠,那麼就選擇工作隊列。如果推後執行的任務不需要睡眠,那麼就選擇tasklet。另外,如果需要用一個可以重新調度的實體來執行你的下半部處理,也應該使用工作隊列。它是唯一能在進程上下文運行的下半部實現的機制,也只有它才可以睡眠。這意味著在需要獲得大量的記憶體時、在需要獲取訊號量時,在需要執行阻塞式的I/O操作時,它都會非常有用。如果不需要用一個內核執行緒來推後執行工作,那麼就考慮使用tasklet。

引用: https://blog.csdn.net/av_geek/article/details/41278801

一個為 struct tasklet_struct *next任務鏈表,一個為struct list_head entry;內核鏈表

驅動編寫規範 設計一個對象描述所有的全局變數

//設計一個對象類型--描述所有的全局的變數 struct s5pv210_key{ __u8 have_data; //用於描述是否有數據 int major ; //記錄主設備號 struct class *cls; //用於創建 類 struct device *dev; //用來創建設備文件 wait_queue_head_t wq_head; //用於實現阻塞的等待隊列頭 struct key_event event; //用於存放數據的包 struct work_struct work;//用於實現中斷下半部 }; 聲明一個對象

//聲明一個對象 struct s5pv210_key *key_dev; 初始化時統一申請空間(這也是好處之一)

// 0-為全局設備/數據對象分配空間 //參數2--標誌--分配記憶體方式, GFP_KERNEL(如果當前記憶體不夠的時候會等待) key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL); 錯誤判斷

if(key_dev == NULL) { printk(KERN_ERR"kmalloc errorn"); return -ENOMEM; } 指針錯誤判斷

if(IS_ERR(key_dev->cls)) { ret = PTR_ERR(key_dev->cls); //自動根據指針轉換一個對應的出錯碼 goto err_unregister; } 統一下函數的最後做處理

static int __init key_drv_init(void) { int ret; // 0-為全局設備/數據對象分配空間 //參數2--標誌--分配記憶體方式, GFP_KERNEL(如果當前記憶體不夠的時候會等待) key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL); if(key_dev == NULL) { printk(KERN_ERR"kmalloc errorn"); return -ENOMEM; } // 1, 申請主設備號--動態申請主設備號 key_dev->major = register_chrdev(0, "key_dev", &key_fops); if(key_dev->major < 0) { printk(KERN_ERR"register_chrdev errorn"); ret = key_dev->major; goto err_free; } // 2, 創建設備節點 key_dev->cls = class_create(THIS_MODULE, "key_cls"); if(IS_ERR(key_dev->cls)) { ret = PTR_ERR(key_dev->cls); //自動根據指針轉換一個對應的出錯碼 goto err_unregister; } key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0"); if(IS_ERR(key_dev->dev)) { ret = PTR_ERR(key_dev->dev); goto err_destory_cls; } // 3, 硬體初始化-- 映射地址或者中斷申請 //參數1--中斷號碼 //獲取中斷號碼的方法: 1,外部中斷IRQ_EINT(x) // 2, 頭文件 #include <mach/irqs.h> #include <plat/irqs.h> //參數2--中斷的處理方法 //參數3--中斷觸發方式: 高電平,低電平,上升沿,下降沿,雙邊沿 /* #define IRQF_TRIGGER_NONE 0x00000000 #define IRQF_TRIGGER_RISING 0x00000001 #define IRQF_TRIGGER_FALLING 0x00000002 #define IRQF_TRIGGER_HIGH 0x00000004 #define IRQF_TRIGGER_LOW 0x00000008 */ //參數4--中斷的描述-自定義字元串 //參數5--傳遞個參數2的任意數據 //返回值0表示正確 int i; int irqno; int flags; char *name; for(i=0; i<ARRAY_SIZE(all_keys); i++) { name = all_keys[i].name; irqno = all_keys[i].irqno; flags = all_keys[i].flags; ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]); if(ret != 0) { printk("request_irq errorn"); goto err_destroy_dev; } } // 定義一個等待隊列頭,並且初始化 init_waitqueue_head(&key_dev->wq_head); //初始化work INIT_WORK(&key_dev->work, work_key_irq); return 0; err_destroy_dev: device_destroy(key_dev->cls, MKDEV(key_dev->major, 0)); err_destory_cls: class_destroy(key_dev->cls); err_unregister: unregister_chrdev(key_dev->major, "key_dev"); err_free: kfree(key_dev); return ret; } 完整驅動程式碼 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/gpio.h> #include <linux/input.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/slab.h> #include <asm/uaccess.h> // 設計一個對象--描述按鍵產生的數據包 struct key_event{ int code; //什麼按鍵: 左鍵, 回車鍵 int value; // 按鍵的狀態: 按下/抬起 (1/0) }; //設計一個描述按鍵的對象: 名字, irqno, gpio, 按鍵值,觸發方式 struct key_desc{ char *name; int irqno; int gpio; int code; int flags;// 觸發方式 }; //設計一個對象類型--描述所有的全局的變數 struct s5pv210_key{ __u8 have_data; //用於描述是否有數據 int major ; //記錄主設備號 struct class *cls; //用於創建 類 struct device *dev; //用來創建設備文件 wait_queue_head_t wq_head; //用於實現阻塞的等待隊列頭 struct key_event event; //用於存放數據的包 struct work_struct work;//用於實現中斷下半部 }; //聲明一個對象 struct s5pv210_key *key_dev; struct key_desc all_keys[] = { [0] = { .name = "key1_up_eint0", .irqno = IRQ_EINT(0), .gpio = S5PV210_GPH0(0), .code = KEY_UP, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [1] = { .name = "key2_down_eint1", .irqno = IRQ_EINT(1), .gpio = S5PV210_GPH0(1), .code = KEY_DOWN, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [2] = { .name = "key3_left_eint2", .irqno = IRQ_EINT(2), .gpio = S5PV210_GPH0(2), .code = KEY_LEFT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [3] = { .name = "key4_right_eint3", .irqno = IRQ_EINT(3), .gpio = S5PV210_GPH0(3), .code = KEY_LEFT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, }; int key_drv_open (struct inode *inode, struct file *filp) { printk("--------^_* %s-------n", __FUNCTION__); // 通過文件路徑可以得到inode // 通過得到次設備號可以區分不同的設備 //int major = MAJOR(filp->f_path.dentry->d_inode->i_rdev); int major = imajor(filp->f_path.dentry->d_inode); int major2 = imajor(inode); int minor = iminor(filp->f_path.dentry->d_inode); int minor2 = iminor(inode); printk("major = %d, minor = %dn", major, minor); printk("major2 = %d, minor2 = %dn", major2, minor2); memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data= 0; //為假--一開始都沒有按鍵按下或者抬起 return 0; } ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos) { int ret; //區分當前是阻塞還是非阻塞 if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data) { return -EAGAIN; } // 在資源不可達的時候,進行休眠 // 參數1---當前驅動中的等待隊列頭 // 參數2--休眠的條件: 假的話就休眠,真就不休眠 wait_event_interruptible(key_dev->wq_head, key_dev->have_data); ret = copy_to_user(buf, &key_dev->event, count); if(ret > 0) { printk("copy_to_user errorn"); return -EFAULT; } //清零,以備下次充值 memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data = 0; //沒有數據了,等待下一次數據 return count; } int key_drv_close(struct inode *inode, struct file *filp) { printk("--------^_* %s-------n", __FUNCTION__); return 0; } unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts) { //返回一個整數: 沒有數據的時候返回0, 有數據的時候,返回POLLIN unsigned int mask = 0; // 將當前的等待隊列頭,註冊到vfs層, 注意: poll_wait該函數不會導致休眠 //參數1-文件對象--當前file //參數2--當前的等待隊列頭 //參數3--當前傳遞過來的第三個參數 poll_wait(filp, &key_dev->wq_head, pts); if(key_dev->have_data) mask |= POLLIN; return mask; } // 4, 實現fops const struct file_operations key_fops = { .owner = THIS_MODULE, .open = key_drv_open, .read = key_drv_read, .poll = key_drv_poll, .release =key_drv_close, }; void work_key_irq(struct work_struct *work) { printk("-----------%s-------n", __FUNCTION__); key_dev->have_data = 1;//表示有數據了 wake_up_interruptible(&key_dev->wq_head); } //中斷處理方法 //參數1--當前產生的中斷的號碼 //參數2--request_irq傳遞過來的參數 irqreturn_t key_irq_handler(int irqno, void *dev_id) { int *p = (int *)dev_id; //printk("-----------%s-------0x%x-----n", __FUNCTION__, *p); //區分當前是哪個按鍵 struct key_desc *pdesc = (struct key_desc *)dev_id; //區分按下還是抬起 int value = gpio_get_value(pdesc->gpio); if(value) { //抬起 printk("<kernel>--%s : releasen", pdesc->name); //填充值 key_dev->event.code = pdesc->code; key_dev->event.value = 0; }else { //按下 printk("<kernel>--%s : pressedn", pdesc->name); key_dev->event.code = pdesc->code; key_dev->event.value = 1; } //在中斷上半部,將work加入到內核執行緒 schedule_work(&key_dev->work); return IRQ_HANDLED; } static int __init key_drv_init(void) { int ret; // 0-為全局設備/數據對象分配空間 //參數2--標誌--分配記憶體方式, GFP_KERNEL(如果當前記憶體不夠的時候會等待) key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL); if(key_dev == NULL) { printk(KERN_ERR"kmalloc errorn"); return -ENOMEM; } // 1, 申請主設備號--動態申請主設備號 key_dev->major = register_chrdev(0, "key_dev", &key_fops); if(key_dev->major < 0) { printk(KERN_ERR"register_chrdev errorn"); ret = key_dev->major; goto err_free; } // 2, 創建設備節點 key_dev->cls = class_create(THIS_MODULE, "key_cls"); if(IS_ERR(key_dev->cls)) { ret = PTR_ERR(key_dev->cls); //自動根據指針轉換一個對應的出錯碼 goto err_unregister; } key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0"); if(IS_ERR(key_dev->dev)) { ret = PTR_ERR(key_dev->dev); goto err_destory_cls; } // 3, 硬體初始化-- 映射地址或者中斷申請 //參數1--中斷號碼 //獲取中斷號碼的方法: 1,外部中斷IRQ_EINT(x) // 2, 頭文件 #include <mach/irqs.h> #include <plat/irqs.h> //參數2--中斷的處理方法 //參數3--中斷觸發方式: 高電平,低電平,上升沿,下降沿,雙邊沿 /* #define IRQF_TRIGGER_NONE 0x00000000 #define IRQF_TRIGGER_RISING 0x00000001 #define IRQF_TRIGGER_FALLING 0x00000002 #define IRQF_TRIGGER_HIGH 0x00000004 #define IRQF_TRIGGER_LOW 0x00000008 */ //參數4--中斷的描述-自定義字元串 //參數5--傳遞個參數2的任意數據 //返回值0表示正確 int i; int irqno; int flags; char *name; for(i=0; i<ARRAY_SIZE(all_keys); i++) { name = all_keys[i].name; irqno = all_keys[i].irqno; flags = all_keys[i].flags; ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]); if(ret != 0) { printk("request_irq errorn"); goto err_destroy_dev; } } // 定義一個等待隊列頭,並且初始化 init_waitqueue_head(&key_dev->wq_head); //初始化work INIT_WORK(&key_dev->work, work_key_irq); return 0; err_destroy_dev: device_destroy(key_dev->cls, MKDEV(key_dev->major, 0)); err_destory_cls: class_destroy(key_dev->cls); err_unregister: unregister_chrdev(key_dev->major, "key_dev"); err_free: kfree(key_dev); return ret; } static void __exit key_drv_exit(void) { //移除work cancel_work_sync(&key_dev->work); // 釋放中斷 //參數1--中斷號碼 //參數5--和request_irq第5個參數保持一致 int i; int irqno; for(i=0; i<ARRAY_SIZE(all_keys); i++) { irqno = all_keys[i].irqno; free_irq(irqno, &all_keys[i]); } device_destroy(key_dev->cls, MKDEV(key_dev->major, 0)); class_destroy(key_dev->cls); unregister_chrdev(key_dev->major, "key_dev"); kfree(key_dev); } module_init(key_drv_init); module_exit(key_drv_exit); MODULE_LICENSE("GPL"); 應用層app程式碼 #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <poll.h> #include <linux/input.h> // 設計一個對象--描述按鍵產生的數據包 struct key_event{ int code; //什麼按鍵: 左鍵, 回車鍵 int value; // 按鍵的狀態: 按下/抬起 (1/0) }; int main(int argc, char *argv[]) { int on; int ret; char kbd_buf[128]; struct key_event data; //直接將驅動模組當做文件來操作 int fd = open("/dev/key0", O_RDWR); if(fd < 0) { perror("open"); exit(1); } struct pollfd pfd[2]; pfd[0].fd = fd; //自己寫的按鍵設備 pfd[0].events = POLLIN; //監控讀 (POLLIN|POLLOUT|POLLERR) pfd[1].fd = 0; //監控標準輸入 pfd[1].events = POLLIN; while(1) { //參數1--你需要監控的文件描述符的集合 //參數2--監控的文件的個數 //參數3--監控的時間--毫秒為單位,如果是負數,永久監控 ret = poll(pfd, 2, -1); if(ret < 0) { perror("poll"); exit(1); } if(ret > 0) { //判斷是哪個有數據 if(pfd[1].revents & POLLIN) { //表示鍵盤是輸入 ret = read(0, kbd_buf, 128); //fgets(kbd_buf, 128, stdin); kbd_buf[ret] = ''; printf("kbd_buf = %sn", kbd_buf); } if(pfd[0].revents & POLLIN) { //獲取數據--不會阻塞 ret = read(pfd[0].fd, &data, sizeof(struct key_event)); //解析包 switch(data.code) { case KEY_UP: if(data.value) { printf("<app>---KEY_UP pressedn"); }else { printf("<app>---KEY_UP releasen"); } break; case KEY_DOWN: if(data.value) { printf("<app>---KEY_DOWN pressedn"); }else { printf("<app>---KEY_DOWN releasen"); } break; case KEY_LEFT: if(data.value) { printf("<app>---KEY_LEFT pressedn"); }else { printf("<app>---KEY_LEFT releasen"); } break; case KEY_RIGHT: if(data.value) { printf("<app>---KEY_RIGHT pressedn"); }else { printf("<app>---KEY_RIGHT releasen"); } break; } } } } close(fd); }

———————————————— 版權聲明:本文為CSDN部落客「Moonright」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/weixin_42471952/article/details/81609141

Tags: