在上一节我们介绍了linux系统的输入子系统的基本框架,并进行了源码分析。
这一节我们将尝试向input子系统注册设备驱动,这里我们编写按键驱动,通过MIni2440上的6个按键来模拟键盘中的A、B、C、D、E、F。
一、按键硬件资源
1.1 硬件接线
查看Mini2440原理图、S3C2440数据手册,了解如何读取按键的状态。这里粗略介绍一下Mini2440 K1~K6的接线方式,以及寄存器的设置,这里简单说一下,就不具体介绍了:
- K1~K6依次对应引脚GPG0、GPG3、GPG5、GPG6、GPG7、GPG11,以K1为例;
- 按键按下引脚输入低电平、按键松开引脚输入高电平;
- 配置控制寄存器GPGCON(0x56000060)的bit[1:0]=00,使GPB5引脚为输入模式;
- 读取配置数据寄存器GPGDAT(0x56000064)的bit0的电位;
二、input子系统设备驱动
2.1 input子系统设备驱动编写流程
在上一节我们简单介绍了向input子系统注册设备驱动过程,这里我们以按键驱动为例:
- 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;
- 通过input_set_capability设置input设备可以上报哪些输入事件;
- 初始化dev成员,设计所要实现的操作,比如 open、close、event、flush函数,;
- 然后调用input_register_device注册这个设备;
- 初始化定时器和中断,并编写中断处理函数,以及定时器超时函数;
- 在出口函数中卸载中断,删除定时器,卸载驱动;
2.2 相关结构声明
在/work/sambashare/drivers下新建9.input_button_dev文件夹,用来编写我们的按键中断程序。
#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> #include <linux/input.h> #include <linux/timer.h> /* 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用 */ #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 /* 引脚信息 */ struct pin_desc{ int irq; // 中断编号 unsigned int irq_ctl; //触发中断状态 char *name; // 引脚名称 unsigned int pin; // 引脚编号 unsigned int key_val; // 对应键盘的A,B,C,D,E,F }; /* * S3C2410_GPG 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h */ static struct pin_desc pins_desc[6] = { {IRQ_EINT8, IRQT_BOTHEDGE, "K1", S3C2410_GPG(0), KEY_A}, {IRQ_EINT11, IRQT_BOTHEDGE, "K2", S3C2410_GPG(3), KEY_B}, {IRQ_EINT13, IRQT_BOTHEDGE, "K3", S3C2410_GPG(5), KEY_C}, {IRQ_EINT14, IRQT_BOTHEDGE, "K4", S3C2410_GPG(6), KEY_D}, {IRQ_EINT15, IRQT_BOTHEDGE, "K5", S3C2410_GPG(7), KEY_E}, {IRQ_EINT19, IRQT_BOTHEDGE, "K6", S3C2410_GPG(11), KEY_F}, }; /* 定义一个input_dev结构体 */ struct input_dev *button_dev; /* 保存dev_id,在定时器中用 */ struct pin_desc *button_id; /* 定时器 */ static struct timer_list button_timer;
2.3 注册中断
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 input_dev *dev) { printk("register irq\n"); int i,j,err; /* 注册中断 */ for(i=0;i<6;i++){ err = request_irq(pins_desc[i].irq, button_irq, pins_desc[i].irq_ctl, pins_desc[i].name, &pins_desc[i]); if(err < 0){ for(j=0;j<i;j++){ free_irq(pins_desc[j].irq, &pins_desc[j]); } } } return err; }
这里我们EINT8、EINT11、EINT13、EINT14、EINT15、EINT19中断共用一个中断处理程序,因此我们将dev_id字段设置为一个结构体,用来标识唯一设备。
/* * 中断处理服务 */ static irqreturn_t button_irq(int irq, void *dev_id) { //保存当前的dev_id button_id =(struct ping_desc *)dev_id; //设置定时器值 10ms后执行,用于防止按键抖动 mod_timer(&button_timer, jiffies+HZ/100 ); return IRQ_RETVAL(IRQ_HANDLED); }
2.4 释放中断
我们在.close函数中通过free_irq函数进行释放中断资源:
/* * 卸载中断 */ int button_close(struct input_dev *dev) { printk("unregister irq\n"); int i=0; for(i=0;i<6;i++){ free_irq(pins_desc[i].irq, &pins_desc[i]); } return 0; }
2.5 定时器超时函数
这里为了防止按键抖动,加入了定时器,并设置定时器超时函数:
/* * 定时器超时函数 * 将输入转换为转换为统一事件形式 */ static void button_timer_timeout(struct timer_list *t) { //获取引脚电平 int val = gpio_get_value(button_id->pin); if(val) { /* 高电平,松开 上报事件*/ input_event(button_dev, EV_KEY, button_id->key_val, 0); //上报EV_KEY类型,按键值,0(没按下) input_sync(button_dev); // 上传同步事件,告诉系统有事件出现 } else { /* 低电平,按下 上报事件*/ input_event(button_dev, EV_KEY, button_id->key_val, 1); //上报EV_KEY类型,按键值,1(按下) input_sync(button_dev); // 上传同步事件,告诉系统有事件出现 } }
这里当有按键按下时,我们利用input_event函数将输入转换为转换为统一事件形式,向输入核心层汇报。关于input_event函数介绍可以参考:input_event 详解-Touch Screen。
2.6 注册button驱动程序
/* * 入口函数 */ static int button_init(void) { int err; printk("button driver init\n"); /* 向内核 申请input_dev结构体 */ button_dev = input_allocate_device(); /* 设置input_dev */ input_set_capability(button_dev,EV_KEY,KEY_A); //支持按键 A input_set_capability(button_dev,EV_KEY,KEY_B); //支持按键 B input_set_capability(button_dev,EV_KEY,KEY_C); //支持按键 C input_set_capability(button_dev,EV_KEY,KEY_D); //支持按键 D input_set_capability(button_dev,EV_KEY,KEY_E); //支持按键 E input_set_capability(button_dev,EV_KEY,KEY_F); //支持按键 F set_bit(EV_REP,button_dev->evbit); //支持键盘重复按事件 button_dev->open = button_open; // 注册中断 button_dev->close = button_close; // 卸载中断 /* 注册input_dev */ err = input_register_device(button_dev); if (err) { printk("input button driver registration failed\n"); /* 释放驱动结构体 */ input_free_device(button_dev); return err; } else { printk("input button driver registered successfully\n"); } /* 初始化定时器 */ timer_setup(&button_timer,button_timer_timeout,0); add_timer(&button_timer); return 0; }
2.7 卸载button驱动程序
/* * 出口函数 */ static void __exit button_exit(void) { printk("button driver exit\n"); /* 删除定时器 */ del_timer(&button_timer); /* 卸载类下的驱动设备 */ input_unregister_device(button_dev); /* 释放驱动结构体 */ input_free_device(button_dev); return; }
2.8 完整代码
#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> #include <linux/input.h> #include <linux/timer.h> /* 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用 */ #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 /* 引脚信息 */ struct pin_desc{ int irq; // 中断编号 unsigned int irq_ctl; //触发中断状态 char *name; // 引脚名称 unsigned int pin; // 引脚编号 unsigned int key_val; // 对应键盘的A,B,C,D,E,F }; /* * S3C2410_GPG 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h */ static struct pin_desc pins_desc[6] = { {IRQ_EINT8, IRQT_BOTHEDGE, "K1", S3C2410_GPG(0), KEY_A}, {IRQ_EINT11, IRQT_BOTHEDGE, "K2", S3C2410_GPG(3), KEY_B}, {IRQ_EINT13, IRQT_BOTHEDGE, "K3", S3C2410_GPG(5), KEY_C}, {IRQ_EINT14, IRQT_BOTHEDGE, "K4", S3C2410_GPG(6), KEY_D}, {IRQ_EINT15, IRQT_BOTHEDGE, "K5", S3C2410_GPG(7), KEY_E}, {IRQ_EINT19, IRQT_BOTHEDGE, "K6", S3C2410_GPG(11), KEY_F}, }; /* 定义一个input_dev结构体 */ struct input_dev *button_dev; /* 保存dev_id,在定时器中用 */ struct pin_desc *button_id; /* 定时器 */ static struct timer_list button_timer; /* * 中断处理服务 */ static irqreturn_t button_irq(int irq, void *dev_id) { //保存当前的dev_id button_id =(struct ping_desc *)dev_id; //设置定时器值 10ms后执行,用于防止按键抖动 mod_timer(&button_timer, jiffies+HZ/100 ); return IRQ_RETVAL(IRQ_HANDLED); } /* * 定时器超时函数 * 将输入转换为转换为统一事件形式 */ static void button_timer_timeout(struct timer_list *t) { //获取引脚电平 int val = gpio_get_value(button_id->pin); if(val) { /* 高电平,松开 上报事件*/ input_event(button_dev, EV_KEY, button_id->key_val, 0); //上报EV_KEY类型,按键值,0(没按下) input_sync(button_dev); // 上传同步事件,告诉系统有事件出现 } else { /* 低电平,按下 上报事件*/ input_event(button_dev, EV_KEY, button_id->key_val, 1); //上报EV_KEY类型,按键值,1(按下) input_sync(button_dev); // 上传同步事件,告诉系统有事件出现 } } /* GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置为中断功能 IRQ_EINTX 定义在 arch/arm/mach-s3c24xx/include/mach/irqs.h */ static int button_open(struct input_dev *dev) { printk("register irq\n"); int i,j,err; /* 注册中断 */ for(i=0;i<6;i++){ err = request_irq(pins_desc[i].irq, button_irq, pins_desc[i].irq_ctl, pins_desc[i].name, &pins_desc[i]); if(err < 0){ for(j=0;j<i;j++){ free_irq(pins_desc[j].irq, &pins_desc[j]); } } } return err; } /* * 卸载中断 */ int button_close(struct input_dev *dev) { printk("unregister irq\n"); int i=0; for(i=0;i<6;i++){ free_irq(pins_desc[i].irq, &pins_desc[i]); } return 0; } /* * 入口函数 */ static int button_init(void) { int err; printk("button driver init\n"); /* 向内核 申请input_dev结构体 */ button_dev = input_allocate_device(); /* 设置input_dev */ input_set_capability(button_dev,EV_KEY,KEY_A); //支持按键 A input_set_capability(button_dev,EV_KEY,KEY_B); //支持按键 B input_set_capability(button_dev,EV_KEY,KEY_C); //支持按键 C input_set_capability(button_dev,EV_KEY,KEY_D); //支持按键 D input_set_capability(button_dev,EV_KEY,KEY_E); //支持按键 E input_set_capability(button_dev,EV_KEY,KEY_F); //支持按键 F set_bit(EV_REP,button_dev->evbit); //支持键盘重复按事件 button_dev->open = button_open; // 注册中断 button_dev->close = button_close; // 卸载中断 /* 注册input_dev */ err = input_register_device(button_dev); if (err) { printk("input button driver registration failed\n"); /* 释放驱动结构体 */ input_free_device(button_dev); return err; } else { printk("input button driver registered successfully\n"); } /* 初始化定时器 */ timer_setup(&button_timer,button_timer_timeout,0); add_timer(&button_timer); return 0; } /* * 出口函数 */ static void __exit button_exit(void) { printk("button driver exit\n"); /* 删除定时器 */ del_timer(&button_timer); /* 卸载类下的驱动设备 */ input_unregister_device(button_dev); /* 释放驱动结构体 */ input_free_device(button_dev); return; } module_init(button_init); module_exit(button_exit); MODULE_LICENSE("GPL");
2.9 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
三、input子系统设备驱动测试
3.1 编译驱动
执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:
cd /work/sambashare/drivers/9.input_button_dev make cp /work/sambashare/drivers/9.input_button_dev/button_dev.ko /work/nfs_root/rootfs
安装驱动:
[root@zy:/]# insmod button_dev.ko button_dev: loading out-of-tree module taints kernel. button driver init input: Unspecified device as /devices/virtual/input/input0 register irq
查看设备节点文件:
[root@zy:/]# ls /dev/input -l total 0 crw-rw---- 1 0 0 13, 64 Jan 1 00:00 1 /dev/input/event0
可以看到设备节点的的主设备号为13,次设备号为64,和我们上一节介绍的一致。
3.2 中断查看
运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:
[root@zy:/]# cat /proc/interrupts CPU0 29: 16393 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: 1607 s3c-ext 7 Edge eth0 56: 0 s3c-ext 8 Edge K1 59: 0 s3c-ext 11 Edge K2 61: 0 s3c-ext 13 Edge K3 62: 0 s3c-ext 14 Edge K4 63: 0 s3c-ext 15 Edge K5 67: 0 s3c-ext 19 Edge K6 74: 48 s3c-level 0 Edge s3c2440-uart 75: 192 s3c-level 1 Edge s3c2440-uart 87: 0 s3c-level 13 Edge s3c2410-wdt Err: 0
可以看到我们注册的外部中断8、11、13、14、15、19.
3.3 测试驱动
在开发版上执行:
[root@zy:/]# cat /dev/tty1
此命令表示显示输出tty1。按下开发板的K1~K6,此时串口终端结果如下:
[root@zy:/]# cat /dev/tty1 accdef
按键虚拟键盘键值正确被系统接收到了,说明我们的驱动成功了。
3.4 卸载驱动
通过用lsmod可以查看当前安装了哪些驱动:
[root@zy:/]# lsmod button_dev 2098 0 - Live 0xbf000000 (O)
卸载时直接运行:
rmmod button_dev
四、总结
4.1 程序执行流程
这里简单介绍一下程序的执行流程:
- 在button_init初始化函数中分配一个input_dev结构体,设置并注册它;
- 当按键按下或松开时,中断发生,调用中断函数button_irq;
- 该函数里在按键事件10ms后调用button_timer_timeout定时器函数;
- 在该函数里面针对中断获取到的不同键值调用input_event函数上报。
而我们的程序里并没有创建设备节点那一套操作,是由input_register_device替我们完成的。
五、代码下载
Young / s3c2440_project[drivers]
参考文章