查看Mini2440原理图、S3C2440数据手册,了解如何读取按键的状态。这里粗略介绍一下Mini2440 K1~K6的接线方式,以及寄存器的设置,这里简单说一下,就不具体介绍了:
试想一下,如果我们想判断按键K1有没有按下或者松开,我们有几种方式:
中断就是CPU正常运行期间,由于内、外部事件引起的CPU暂时停止正在运行的程序,去执行该内部事件或外部事件的引起的服务中去,服务执行完毕后再返回断点处继续执行的情形。
中断不属于任何一个进程,因此不能在中断程序中休眠和调用schedule函数放弃CPU,实现中断处理函数有一个原则,就是尽可能的处理并返回。
linux操作系统是多个进程执行,宏观上达到并行运行的状态,外设中断则会打断内核中任务调度和运行,如果中断函数耗时过长则使得系统实时性和并发性降低。
为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的事务,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux系统提出了一个概念:把中断服务程序分为两部分-顶半部-底半部。
Linux中断顶-底部分区别:
一般来说,在中断顶半部执行完毕,底半部即在内核的调度下被执行,当然如果有其他更高优先级需处理的任务,会先处理该任务再调度处理底半部,或者在系统空闲时间进行处理。
对于Linux系统设备而言,一个完整的中断程序由顶半部和底半部分共同构成,在编写设备驱动程序前,就需考虑好顶半部和底半部的分配。很多时候顶半部与底半部并没有严格的区分界限,主要由程序员根据实际设计,如某些外设中断可以没有底半部。关于顶底半部的划分原则,就是主要事务、耗时事务划分在底半部处理。可以参考以下原则:
这里我们先动手跟着学习按键中断程序的编写,熟悉一下中断程序编写的流程。至于具体中断的实现原理,我们后面文章会进行介绍。
在/work/sambashare/drivers下新建3.button_dev文件夹,用来编写我们的按键中断程序。
GPG0、GPG3、GPG5、GPG6、GPG7、GPG11对应的外部中断依次为EINT8、EINT11、EINT13、EINT14、EINT15、EINT19。
这里通过request_irq函数注册GPG0、GPG3、GPG5、GPG6、GPG7、GPG11为外部中断,触发方式为双边沿。
/* GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置为中断功能 IRQ_EINTX 定义在 arch/arm/mach-s3c24xx/include/mach/irqs.h */ static int button_open(struct inode *inode, struct file *file) { /* 注册中断 */ request_irq(IRQ_EINT8,button_irq,IRQT_BOTHEDGE,"K1",&pins_desc[0]); request_irq(IRQ_EINT11,button_irq,IRQT_BOTHEDGE,"K2",&pins_desc[1]); request_irq(IRQ_EINT13,button_irq,IRQT_BOTHEDGE,"K3",&pins_desc[2]); request_irq(IRQ_EINT14,button_irq,IRQT_BOTHEDGE,"K4",&pins_desc[3]); request_irq(IRQ_EINT15,button_irq,IRQT_BOTHEDGE,"K5",&pins_desc[4]); request_irq(IRQ_EINT19,button_irq,IRQT_BOTHEDGE,"K6",&pins_desc[5]); return 0; }
函数request_irq原型:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
其中:
这里我们EINT8、EINT11、EINT13、EINT14、EINT15、EINT19中断共用一个中断处理程序,因此我们将dev_id字段设置为一个结构体,用来标识唯一设备。
struct pin_desc{ unsigned int pin; unsigned int key_val; }; /* * S3C2410_GPG(x) 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h */ static struct pin_desc pins_desc[4] = { {S3C2410_GPG(0), 0x01}, {S3C2410_GPG(3), 0x02}, {S3C2410_GPG(5), 0x03}, {S3C2410_GPG(6), 0x04}, {S3C2410_GPG(7), 0x05}, {S3C2410_GPG(11), 0x06}, };
pin_desc用来保存当前设备的引脚名称和初始值(并不是引脚电平值,这是一个虚拟的值,可以用来区分是哪个按键)。
我们在.close函数中通过free_irq函数进行释放中断资源:
/* * 释放中断资源 */ int button_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT8, &pins_desc[0]); free_irq(IRQ_EINT11, &pins_desc[1]); free_irq(IRQ_EINT13, &pins_desc[2]); free_irq(IRQ_EINT14, &pins_desc[3]); free_irq(IRQ_EINT15, &pins_desc[4]); free_irq(IRQ_EINT19, &pins_desc[5]); return 0; }
4.3 中断处理程序
中断发生时,系统调用这个函数,传入的参数包括硬件的中断号,dev_id。dev_id是request_ird函数传递给系统的参数的参数dev_id。
/* * 中断处理服务 */ static irqreturn_t button_irq(int irq, void *dev_id) { struct pin_desc *pindesc = (struct pin_desc *)dev_id; unsigned int pinval; pinval = gpio_get_value(pindesc->pin); if (pinval){ /* 松开 */ key_val = 0x80 | pindesc->key_val; } else{ /* 按下 */ key_val = pindesc->key_val; } ev_press = 1; /* 表示中断发生了,退出等待队列 */ wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */ return IRQ_RETVAL(IRQ_HANDLED); }
这里通过gpio_get_value获取引脚的电平。
s3c2410_gpio_getpin(unsigned int pin);
其中:
如果引脚为高电平,我们就将key_val设备值设置为0x8x,否则为初始值,key_val声明如下:
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 */ /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86 */ static unsigned char key_val;
此外,中断发生了,我们设置全局变量ev_press为1。然后从button_waitq队列中唤醒休眠的进程。等待队列的声明如下:
/* 定义并初始化等待队列 */ static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
然后调用wake_up_interruptible唤醒以button_waitq等待队列中的所有进程,并将进程的状态设置为TASK_RUNNING。
static ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int count; if (size != 1) // 需要读取长度 return -EINVAL; /* 如果没有按键动作, 休眠 */ wait_event_interruptible(button_waitq, ev_press); /* 如果有按键动作, 上传key_val给用户层 */ count = copy_to_user(buf, &key_val, 1); /* 数据发完后,立马设为休眠状态,避免误操作 */ ev_press = 0; return count; }
wait_event_interruptible函数第二个参数为condition:
当进程唤醒后,上传上传key_val给用户层。数据发完后,立马设为休眠状态,避免误操作。
static struct file_operations button_fops = { .owner = THIS_MODULE, .open = button_open, .read = button_read, .release = button_close, }; static dev_t devid; // 起始设备编号 static struct cdev button_cdev; // 保存操作结构体的字符设备 static struct class *button_cls; static int button_init(void) { /* 动态分配字符设备: (major,0) */ if(OK == alloc_chrdev_region(&devid, 0, 1,"button")){ // ls /proc/devices看到的名字 printk("register_chrdev_region ok\n"); }else { printk("register_chrdev_region error\n"); return ERROR; } cdev_init(&button_cdev, &button_fops); cdev_add(&button_cdev, devid, 1); /* 创建类,它会在sys目录下创建/sys/class/button这个类 */ button_cls = class_create(THIS_MODULE, "button"); if(IS_ERR(button_cls)){ printk("can't create class\n"); return ERROR; } /* 在/sys/class/button下创建buttons设备,然后mdev通过这个自动创建/dev/buttons这个设备节点 */ device_create(button_cls, NULL, devid, NULL, "buttons"); return 0; }
static void __exit button_exit(void) { printk("button driver exit\n"); /* 注销类、以及类设备 /sys/class/button会被移除*/ device_destroy(button_cls, devid); class_destroy(button_cls); cdev_del(&button_cdev); unregister_chrdev_region(devid, 1); return; }
4.7 完整代码
button_dev.c:
#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/io.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/irq.h> // 包含了mach/irqs.h #include <linux/interrupt.h> #include <linux/gpio/machine.h> #include <mach/gpio-samsung.h> /* 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用 */ #define OK (0) #define ERROR (-1) #define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING #define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING #define __IRQT_LOWLVL IRQ_TYPE_LEVEL_LOW #define __IRQT_HIGHLVL IRQ_TYPE_LEVEL_HIGH #define IRQT_NOEDGE (0) #define IRQT_RISING (__IRQT_RISEDGE) #define IRQT_FALLING (__IRQT_FALEDGE) #define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE) #define IRQT_LOW (__IRQT_LOWLVL) #define IRQT_HIGH (__IRQT_HIGHLVL) #define IRQT_PROBE IRQ_TYPE_PROBE /* 定义并初始化等待队列 */ static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /* * 定义中断事件标志 * 0:进入等待队列 1:退出等待队列 */ static int ev_press =0; /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 */ /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86 */ static unsigned char key_val; struct pin_desc{ unsigned int pin; unsigned int key_val; }; /* * S3C2410_GPG 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h */ static struct pin_desc pins_desc[4] = { {S3C2410_GPG(0), 0x01}, {S3C2410_GPG(3), 0x02}, {S3C2410_GPG(5), 0x03}, {S3C2410_GPG(6), 0x04}, {S3C2410_GPG(7), 0x05}, {S3C2410_GPG(11), 0x06}, }; /* * 中断处理服务 */ static irqreturn_t button_irq(int irq, void *dev_id) { struct pin_desc *pindesc = (struct pin_desc *)dev_id; unsigned int pinval; pinval = gpio_get_value(pindesc->pin); if (pinval){ /* 松开 */ key_val = 0x80 | pindesc->key_val; } else{ /* 按下 */ key_val = pindesc->key_val; } ev_press = 1; /* 表示中断发生了,退出等待队列 */ wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */ return IRQ_RETVAL(IRQ_HANDLED); } /* GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置为中断功能 IRQ_EINTX 定义在 arch/arm/mach-s3c24xx/include/mach/irqs.h */ static int button_open(struct inode *inode, struct file *file) { /* 注册中断 */ request_irq(IRQ_EINT8,button_irq,IRQT_BOTHEDGE,"K1",&pins_desc[0]); request_irq(IRQ_EINT11,button_irq,IRQT_BOTHEDGE,"K2",&pins_desc[1]); request_irq(IRQ_EINT13,button_irq,IRQT_BOTHEDGE,"K3",&pins_desc[2]); request_irq(IRQ_EINT14,button_irq,IRQT_BOTHEDGE,"K4",&pins_desc[3]); request_irq(IRQ_EINT15,button_irq,IRQT_BOTHEDGE,"K5",&pins_desc[4]); request_irq(IRQ_EINT19,button_irq,IRQT_BOTHEDGE,"K6",&pins_desc[5]); return 0; } static ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int count; if (size != 1) return -EINVAL; /* 如果没有按键动作, 休眠, */ wait_event_interruptible(button_waitq, ev_press); /* 如果有按键动作, 上传key_val给用户层 */ count = copy_to_user(buf, &key_val, 1); /* 数据发完后,立马设为休眠状态,避免误操作 */ ev_press = 0; return count; } /* * 释放中断资源 */ int button_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT8, &pins_desc[0]); free_irq(IRQ_EINT11, &pins_desc[1]); free_irq(IRQ_EINT13, &pins_desc[2]); free_irq(IRQ_EINT14, &pins_desc[3]); free_irq(IRQ_EINT15, &pins_desc[4]); free_irq(IRQ_EINT19, &pins_desc[5]); return 0; } static struct file_operations button_fops = { .owner = THIS_MODULE, .open = button_open, .read = button_read, .release = button_close, }; static dev_t devid; // 起始设备编号 static struct cdev button_cdev; // 保存操作结构体的字符设备 static struct class *button_cls; static int button_init(void) { /* 动态分配字符设备: (major,0) */ if(OK == alloc_chrdev_region(&devid, 0, 1,"button")){ // ls /proc/devices看到的名字 printk("register_chrdev_region ok\n"); }else { printk("register_chrdev_region error\n"); return ERROR; } cdev_init(&button_cdev, &button_fops); cdev_add(&button_cdev, devid, 1); /* 创建类,它会在sys目录下创建/sys/class/button这个类 */ button_cls = class_create(THIS_MODULE, "button"); if(IS_ERR(button_cls)){ printk("can't create class\n"); return ERROR; } /* 在/sys/class/button下创建buttons设备,然后mdev通过这个自动创建/dev/buttons这个设备节点 */ device_create(button_cls, NULL, devid, NULL, "buttons"); return 0; } static void __exit button_exit(void) { printk("button driver exit\n"); /* 注销类、以及类设备 /sys/class/button会被移除*/ device_destroy(button_cls, devid); class_destroy(button_cls); cdev_del(&button_cdev); unregister_chrdev_region(devid, 1); return; } module_init(button_init); module_exit(button_exit); MODULE_LICENSE("GPL");View Code
4.8 Makefile
KERN_DIR :=/work/sambashare/linux-5.2.8 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += button_dev.o
在3.button_dev下创建test文件夹,保存测试应用程序。
#include <sys/stat.h> #include <fcntl.h> #include <stdio.h> int main(int argc,char **argv) { int fd,ret; unsigned int key_val = 0; fd = open("/dev/buttons", O_RDWR); if (fd < 0) { printf("can't open!\n"); return -1; } while (1) { ret = read(fd, &key_val, 1); // 读取一个字节值,(当在等待队列时,本进程就会进入休眠状态) if(ret < 0){ printf("read error\n"); continue; } printf("key_val = 0x%x\n", key_val); } return 0; }
all: arm-linux-gcc -march=armv4t -o main main.c clean: rm -rf *.o main
按键驱动目录结构如下:
执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:
cd /work/sambashare/drivers/3.button_dev make cp /work/sambashare/drivers/3.button_dev/led_dev.ko /work/nfs_root/rootfs
安装驱动:
[root@zy:/]# insmod button_dev.ko led_dev: loading out-of-tree module taints kernel. register_chrdev_region ok
查看设备节点文件:
[root@zy:/]# ls /dev/buttons -l crw-rw---- 1 0 0 249, 0 Jan 1 00:00 /dev/buttons
运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:
[root@zy:/]# cat /proc/interrupts CPU0 29: 144431 s3c 13 Edge samsung_time_irq 32: 0 s3c 16 Edge s3c2410-lcd 42: 0 s3c 26 Edge ohci_hcd:usb1 43: 0 s3c 27 Edge s3c2440-i2c.0 55: 2897 s3c-ext 7 Edge eth0 56: 21 s3c-ext 8 Edge 59: 3 s3c-ext 11 Edge 61: 20 s3c-ext 13 Edge 62: 67 s3c-ext 14 Edge 63: 9 s3c-ext 15 Edge 67: 6 s3c-ext 19 Edge 74: 118 s3c-level 0 Edge s3c2440-uart 75: 780 s3c-level 1 Edge s3c2440-uart 87: 0 s3c-level 13 Edge s3c2410-wdt
可以看到我们注册的外部中断8、11、13、14、15、19.
执行make命令编译测试应用程序,并将测试应用程序拷贝到nfs文件系统:
cd test make cp ./main /work/nfs_root/rootfs
运行应用程序:
./main
按下按键K1:
[root@zy:/]# ./main key_val = 0x1 key_val = 0x81
按下按键K3:
key_val = 0x3 key_val = 0x83
通过用lsmod可以查看当前安装了哪些驱动:
[root@zy:/]# lsmod button_dev 2521 0 - Live 0xbf000000 (O)
卸载时直接运行:
rmmod button_dev
6.5 程序整体执行流程
参考文章:
[1]Linux中断上半部和下半部概念
[2]四、Linux驱动之使用中断
[3]Linux驱动中的 wait_event_interruptible 与 wake_up_interruptible 深度理解