一起分析Linux系统设计思想——05字符设备驱动之按键驱动
Posted 穿越临界点
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起分析Linux系统设计思想——05字符设备驱动之按键驱动相关的知识,希望对你有一定的参考价值。
在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
1 实现按键驱动的两种方式
按键驱动程序包括两次信息交互过程。一次是外围硬件(GPIO引脚)与内核代码的交互;一次是内核代码与应用层代码的交互。
外围硬件与内核代码的交互直接使用异步机制——硬件中断。
内核代码与应用层代码的交互可以使用同步机制——睡眠与唤醒(不使用轮循与退出);也可以使用异步机制——软件中断。据此,实现按键驱动可以有两种方式。
今天先介绍第一种方式。
2 使用同步机制实现按键驱动
下面我们就使用 睡眠与唤醒 同步机制实现按键驱动功能。
2.1 内核驱动程序
如下为按键内核驱动程序。程序在LED设备驱动的基础上进行改进而来。
最重要的改动就是在open函数中调用了 request_irq() 函数,来进行中断的申请和中断处理函数的注册。
另外一个改动就是在read函数中调用 wait_event_interruptible() 函数,在条件未满足时将调用read函数的线程 阻塞 ,并放入阻塞队列中等待条件满足时被唤醒。
中断处理函数中做的最主要的事情就是 唤醒 在阻塞队列中等待的线程。
Tips:这里留下了一个非常关键的问题——申请中断时中断号是如何确定的。这个问题涉及到了Linux系统的中断架构的内容,等到按键驱动例程完成之后专门进行介绍。
1 #include <linux/module.h>
2 #include <linux/kernel.h>
3 #include <linux/fs.h>
4 #include <linux/init.h>
5 #include <asm/io.h> /* ioremap() */
6 #include <linux/uaccess.h>
7 #include <linux/device.h>
8 #include <linux/interrupt.h>
9 #include <linux/irqreturn.h>
10
11 #define GPFCON_ADD_BASE 0x56000050
12
13 volatile unsigned int *pgpfcon = NULL;
14 volatile unsigned int *pgpfdata = NULL;
15 DECLARE_WAIT_QUEUE_HEAD(key_wait_q); /*定义等待队列*/
16 volatile int event_press = 0;
17
18 irqreturn_t keys_handler(int irq, void *dev_id) /*中断处理函数*/
19 {
20 printk("irq = %d is triggered. \\n", irq);
21 /* Wake up the blocked thread */
22 event_press = 1; /*修改事件标记*/
23 wake_up_interruptible(&key_wait_q); /*唤醒等待队列中的线程*/
24
25 return IRQ_HANDLED;
26 }
27 int cdriver_open(struct inode *inode, struct file *file)
28 {
29 int minor = MINOR(inode->i_rdev);
30
31 printk("cdriver open success!\\n");
38 /* 申请中断 */
39 request_irq(IRQ_EINT0, keys_handler, SA_TRIGGER_RISING, "key0", 1);
40
41 return 0;
42 }
43
44 int cdriver_release(struct inode *inode, struct file *file)
45 {
46 printk("cdriver released. \\n");
47
48 free_irq(IRQ_EINT0, 1); /*释放中断号*/
49
50 return 0;
51 }
52 ssize_t key_read(struct file *file, char __user *user_buff, size_t len, loff_t *offset)
53 {
54 /* Block current thread. */
55 event_press = 0;
56 wait_event_interruptible(key_wait_q, event_press); /*将当前线程阻塞并放入等待队列*/
57
58 return 0;
59 }
60
84 struct file_operations cdriver_fops = {
85 .owner = THIS_MODULE,
86 .open = cdriver_open,
88 .read = key_read,
89 .release = cdriver_release,
90 };
91
92 int major = 0;
93 struct class *led_class = NULL;
94 struct class_device *led_class_dev[3] = {NULL};
95
96 int __init cdriver_init(void)
97 {
98 int minor = 0;
99
100 major = register_chrdev(0, "key_driver", &cdriver_fops);
101
102 led_class = class_create(THIS_MODULE, "keys");
103 if (NULL == led_class)
104 return -EINVAL;
105 for (minor = 0; minor < 3; minor++) {
106 led_class_dev[minor] = class_device_create(led_class, NULL, MKDEV(major, minor), NULL, "key%d", minor);
107 if (NULL == led_class_dev[minor])
108 return -EINVAL;
109 }
116 return 0;
117 }
118
119 void __exit cdriver_exit(void)
120 {
121 int minor = 0;
122
124
125 unregister_chrdev(major, "key_driver");
126 for (minor = 0; minor < 3; minor++)
127 class_device_unregister(led_class_dev[minor]);
128 class_destroy(led_class);
129
130 }
131
132 module_init(cdriver_init);
133 module_exit(cdriver_exit);
134 MODULE_LICENSE("GPL");
2.2 应用程序实现
应用主要用来验证内核驱动程序的功能,所以比较简单。主要就是阻塞等待按键的到来。
1 #include <stdio.h>
2 #include <fcntl.h>
3
4 int main(void)
5 {
6 int fd = 0;
7 int val = 0xff;
8
13
14 fd = open("/dev/key0", O_RDWR);
15 if (fd < 0) {
16 printf("open /dev/key_dev failed!\\n");
17 return -1;
18 }
19
20 for(;;) {/*调用read时会阻塞等待,直到有按键中断到来*/
21 if (read(fd, &val, sizeof(val)) < 0) {
22 printf("read failed. \\n");
23 perror("read");
24 } else {
25 printf("key pressed \\n");
26 }
27 }
28
29 (void)close(fd);
30
31 return 0;
32 }
一个最简单的基于硬件中断的按键中断程序其实已经完成了。
但是到现在其实还有几个问题没有解决。按键消抖怎么做,在哪里做?内核驱动程序如何支持多个物理按键?如何选取申请的中断号?
为减小大家的阅读压力,上面的问题我们就且听下回分解啦~
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~
以上是关于一起分析Linux系统设计思想——05字符设备驱动之按键驱动的主要内容,如果未能解决你的问题,请参考以下文章
一起分析Linux系统设计思想——05字符设备驱动之按键驱动