按键驱动:中断方式
中断属于异常的一种。异常,就是可以打断CPU正常运行流程,转而去处理异常事件,然后再继续执行被打断的程序。
异常向量的代码很简单,就是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码。
Arm架构的cpu异常向量基址可以是0x00000000,也可以是0xffff0000,linux内核使用后者。这个地址并不代表实际的内存,是虚拟地址。当建立了虚拟地址与物理地址的映射后,需要将异常向量的入口拷贝到0xffff0000。
这个过程是在trap_init()中完成的。以JZ2440 Linux2.6为例,异常向量将被复制到0xffff0000处,跳转的目的代码将被复制到0xffff0000+0x200处。
这样,一个异常发生后,执行流程如下:
1. CPU自动跳转到异常向量入口处(即0xffff0000)执行。
2. 而此处的代码是一些跳转指令(注意这些指令都加有一个偏移值,因为异常向量已经重新定位了,若以链接地址去跳转将无法正确跳转,链接地址+偏移量 才是正确的中断服务程序),这样CPU又跳转到别的地方运行了。
3. 这个新地址的代码要做的事情就是计算返回地址、保存现场,然后进入管理模式,最后根据被中断的工作模式再次跳转。再次跳转到哪里去?看源码:在这个新地址处是一个宏,它定义了上述的计算返回地址等的工作,在这个宏的后边紧跟着的是一段常量的定义,而这段常量就指明了接下来要跳转到哪里去。
4. 不同的跳转分支只在它们的入口处稍有差别,后续的处理大体相同,都是调用相应的C函数。以中断处理函数为例,它最终将调用asm_do_IRQ(这是一个c函数)。
综上:一个异常的发生,将会有3次跳转。首先,cpu自动跳转到异常向量入口。然后,根据异常向量指令跳转到对应的异常服务函数。最后,经过异常服务函数计算返回地址等的操作后,再次跳转到异常处理函数(此处的差别:一个服务函数,一个是处理函数),然后调用c函数做复杂的处理。
内核中断框架
asm_do_IRQ()
1. 根据传入的中断号irq在全局数组irq_desc[]中找到以irq为下标的项。
2. 调用irq_desc[irq].handle_irq
3. handle_irq会使用chip成员中的函数来设置硬件,如清中断、禁止中断、重新使能中断等。
4. handle_irq逐个调用用户在action链表中注册的处理函数。
由此可见,用户注册中断时就是构造action链表;用户卸载中断就是从action链表中去除不需要的项。
request_irq()
首先使用传入的参数构造一个irqaction结构,然后调用setup_irq函数将它链入链表。
setup_irq()实现的主要功能:
1. 将新建的irqaction结构链入irq_desc[irq]结构中的action链表中。这有2中情况:
a. 如果action链表为空,则直接链入
b. 否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致:即是否都声明为“可共享的”(IRQF_SHARED)、是否都使用相同的触发方式(电平、边沿、极性),如果一致则将新建的irqaction结构链入。
2. 设置irq_desc[irq]结构中chip成员的还没有设置的指针,让它们指向一些默认函数。
3. 设置中断触发方式。由request_irq()传入的irqflags表明是:高电平触发、低电平触发、上升沿还是下降沿,然后调用irq_desc[irq]结构中的chip->set_type函数来设置。注意,若链表非空,表示之前已经设置过了,此处不再设置。
4. 启动中断。
须知:执行request_irq()后,中断就可以发生并能够处理了。
free_irq()
1. 根据irq、dev_id从action链表中找到表项,将它移除。
2. 如果它是唯一的表项,还要调用irq_desc[irq].chip->shutdown或irq_desc[irq].chip->disable来关闭中断。
程序实例:
在读键值函数中使用:
wait_event_interruptible(buttons_waitq, evpress);
在中断处理函数中使用:
evpress = 1; wake_up_interruptible(&buttons_waitq);
这样,就可以在没有按键按下时应用程序休眠,有按键按下时立即唤醒应用程序。
需要注意的几点:
1. 如何确定外部中断号?
调用gpio_to_irq(NUC970_PI0) 即可返回外部中断号,即request_irq需要的第一个参数值。
2. request_irq()会通过全局数组irq_desc[]对应项中的chip自动配置相应的引脚,故本驱动中不涉及对GPIO寄存器的操作。
3. 在open函数中注册中断,那么相应的要在release函数中释放中断,应用程序close(fd)时,会调用release。实测,不可以在出口函数中释放,会报错:释放无效的中断。
源码
驱动:
/* * 引脚:PI0,1,2 */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/leds.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/module.h> #include <linux/pinctrl/consumer.h> #include <linux/err.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <asm/io.h> #include <linux/wait.h> #include <linux/sched.h> struct pin_desc{ int pin; int val; }; /* * 按下时,返回:0x81, 0x82, 0x83 * */ static struct pin_desc pins_desc[3] = { {NUC970_PI0, 0x1}, {NUC970_PI1, 0x2}, {NUC970_PI2, 0x3}, }; static unsigned char val; static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq); int evpress = 0; static irqreturn_t buttons_handler(int irq, void *dev_id) { struct pin_desc *pin = (struct pin_desc *)dev_id; //int res; //res = gpio_get_value(pin->pin); val = 0x80 | pin->val; evpress = 1; wake_up_interruptible(&buttons_waitq); return IRQ_HANDLED; } static int buttons_open(struct inode *inode, struct file *filp) { // request_irq会自动设置引脚,此处不再配置 request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]); request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]); request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]); return 0; } static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos) { if (count != 1) { return -EINVAL; } wait_event_interruptible(buttons_waitq, evpress); evpress = 0; copy_to_user(buf, &val, 1); return 1; } int buttons_release (struct inode *inode, struct file *filp) { free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]); free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]); free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]); return 0; } static struct file_operations buttons_fops = { .owner = THIS_MODULE, .open = buttons_open, .read = buttons_read, .release = buttons_release, }; static int major; static struct class *buttons_class; static struct device *button_device; static int buttons_init(void) { major = register_chrdev(0, "buttons", &buttons_fops); buttons_class = class_create(THIS_MODULE, "buttons"); button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons"); return 0; } static void buttons_exit(void) { device_destroy(buttons_class, MKDEV(major, 0)); class_destroy(buttons_class); unregister_chrdev(major, "buttons"); } module_init(buttons_init); module_exit(buttons_exit); MODULE_LICENSE("GPL");
测试程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main(void) { int fd; unsigned char key_val; fd = open("/dev/buttons", O_RDONLY); if (fd < 0) { printf("Can't open /dev/buttons\n"); return -1; } while (1) { read(fd, &key_val, 1); printf("key_val = 0x%x\n", key_val); } close(fd); return 0; }