之前不用input_subsystem的步骤
自己写驱动程序
用户需要open,read,write
驱动也要完成对应的drv_open, drv_read, drv_write
- 主设备号
- 构造fops
.read
.open
.poll
.fasync - register_chrdev(major, name, fops)
- 入口
- 出口
- LICENSE
以前在一个文件里把所有写完,在输入子系统里被拆分为几个文件,我们需要做的是:
- 分配input_dev
- 设置
- 注册
- 硬件相关操作
在输入子系统下编程
主要参考例子,drivers\input\keyboard\gpio_keys.c
上报事件,看drivers\input\input.c
中的input_event函数的定义,最后几行,input_event会从input_dev的h_list中找到input_hanldle找到handler调用event函数,
/**
* input_event() - report new input event
* @dev: device that generated the event
* @type: type of the event
* @code: event code
* @value: value of the event
*
* This function should be used by drivers implementing various input devices
* See also input_inject_event()
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
// input_event会从input_dev的h_list中找到input_hanldle
// 找到handler调用event函数
...
else
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
}
有了input subsystem,我们只需要写硬件相关的代码,其他read,write,poll等已经被写好了,在drivers\input\evdev.c里面,
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
1. 编译make
2. 拷贝到网络文件系统cp xxx.ko /mnt/
3. 查看原来的事件,ls /dev/event*
4. 插入模块inmod buttons.ko
5. 再查看事件,ls /dev/event*
看到新的event主设备号为13,次设备号为65,
下面解释次设备号65:
input_register_device
函数会从右边handler
一个个取出与左边input_dev
比较,比较id_table
,在drivers\input\evdev.c
中看到evdev支持所有的输入设备/* Matches all devices */
,
当然也支持按键类输入设备,于是会调用connect
函数,在connect
函数里,
```c
drivers\input\evdev.c – id_table
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
MODULE_DEVICE_TABLE(input, evdev_ids);
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
drivers\input\evdev.c -- connect
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
// #define EVDEV_MINOR_BASE 64
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
// 在class下面创建设备,这样就文件系统中mdev就会自动创建设备节点
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200414210158602.png)
在drivers\input\input.c
中,创建了class,注册了字符设备,但并没有在class下面创建设备(在class下面创建设备,文件系统的mdev就会自动创建设备节点),所以创建设备应该是在connnet中完成,
(之前自己写代码。创建class后,下面就直接创建设备,输入子系统把他们分开了,在connect
和input_init
分别完成)
```c
drivers\input\input.c
static int __init input_init(void)
{
// 创建class
err = class_register(&input_class);
// 注册字符设备
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
}
drivers\input\evdev.c -- connect
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
// #define EVDEV_MINOR_BASE 64
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
// 在class下面创建设备,这样就文件系统中mdev就会自动创建设备节点,次设备号
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);
```
6. 打开终端,等待按键输入,cat /dev/tty1
,按键S2 S3 S4
出错,调试
调试方法一:hexdump /dev/event1
(打开 /dev/event1设备,读取设备[对应文件],以16进制显示)
打开哪个对应的文件,分析,在drivers\input\input.c
中input_init
需要用input_fops
注册字符设备,找到 input_fops
的.read = input_open_file
,打开,次设备号为65,右移5就是除以32等于2也就是input_table[2],这个东西在input_register_handler(&evdev_handler);
被赋值handler
也就是evdev_handler
,
drivers\input\input.c
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];
drivers\input\input.c
int input_register_handler(struct input_handler *handler)
{
input_table[handler->minor >> 5] = handler;
怎么读,evdev_read
,读环形缓冲区数据 头不等于尾,然后将读到的数据拷贝到用户空间evdev_event_to_user
,查看数据格式,就是前面那个hexdump /dev/event1
命令打开的内容了,就是input_event
它了,
include\linux\input.h
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
include\linux\input.h
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
include\linux\time.h
struct timeval {
time_t tv_sec; /* seconds */ 4个字节
suseconds_t tv_usec; /* microseconds */ 4个字节
};
现在就可以具体解析那个文件16进制内容,4 4 2 2 4字节,左边低字节右边高字节,
如 类 0001是EV_KEY
的值0x01,0000是EV_SYN的值0x00;
code 0026是KEY_L
的值38;
value 0001 0000是按下 1,0000 0000是松开 0;
第二行和第四行表示上报的同步类事件。
#define EV_SYN 0x00
#define EV_KEY 0x01
#define EV_REL 0x02
调试方法二:如果没有启动QT,cat /dev/tty1
,按键S2 S3 S4就是输入ls enter
。
如果没有启动QT,cat /dev/tty1
,按键S2 S3 S4就是输入ls enter
。
或者 exec 0</dev/tty1将标准输入改成
tty1
查看需要的、依赖的文件有没有被编译进内核,这里检查keyboard.c
,
ls drivers/char/keyboard.
存在对应.o就是被编译进去了。
调试方法三:如果启动了QT,打开记事本,新建文件,然后按S2 S3 S4,
如果启动了QT,打开记事本,新建文件,然后按S2 S3 S4,可以在文件中看到ls enter。
杀不掉QT,就inittab,vi /etc/inittab
,修改那个rcS
,vi /etc/init.d/rcS
,注释掉最后一行。
exec 0</dev/tty1
将标准输入改成tty1
,以前从串口得到输入现在改为按键输入。
分析连续键值 (重复事件修改定时器超时时间)
按着按键不松开并没有显示多个值(连续键值),需要产生重复类事件就可以了,实际是通过修改定时器的值实现,
// 2.1能产生哪类事件,按键类 重复类(修改定时器超时事件)
set_bit(EV_KEY, _buttons_dev->evbit);
set_bit(EV_REP, _buttons_dev->evbit);
分析,按键的重复事件,在input_event
中,如果能产生重复类事件就修改定时器超时时间,当然也需要时间处理函数就需要init_timer
跟着找,
drivers\input\input.c
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
case EV_KEY: ...
if (value == 2)
break;
change_bit(code, dev->key);
// 如果能产生重复类事件就修改定时器超时时间
if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
dev->repeat_key = code; // 记录需要重复的按键值
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
break;
drivers\input\input.c
int input_register_device(struct input_dev *dev)
{
...
init_timer(&dev->timer);
dev->timer.function = input_repeat_key;
drivers\input\input.c
static void input_repeat_key(unsigned long data)
{
// 上报时间 记录的那个重复键值,重复事件2,0松开1按下
input_event(dev, EV_KEY, dev->repeat_key, 2);
input_sync(dev);
if (dev->rep[REP_PERIOD])
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD]));
}
根据接受的数据显示或删除的程序是shell,进程号770,查看它打开了哪些文件,ls -l /proc/770/fd
,标准输入0输出1错误2都是串口, exec 0</dev/tty1
将标准输入改成tty1
源码
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h> // S3C2410_GPFx
#include <asm/gpio.h>
// 引脚描述结构体 中断号、名称、管脚、键值
static struct pin_desc {
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
// 定义一个引脚描述数组,并初始化,对应于4个按键的信息
#define NUMS_KEYS 4
struct pin_desc pins_desc[NUMS_KEYS] = {
{IRQ_EINT0, "EINT0_S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "EINT2_S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "EINT11_S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "EINT19_S5", S3C2410_GPG11, KEY_LEFTSHIFT}
}
// 定义一个输入设备结构
static struct input_dev *_buttons_dev;
// 定义一个定时器
static struct timer_list buttons_timer;
// 发生中断后,参数的在irq和timer_function之间传递
static struct pin_desc *irq_pd; // 在中断中的引脚描述pin_desc
// ISR 发生按键中断之后会调用这个函数, irq就是对应的中断号
// 按键中断发生后进入irq,修改定时器溢出值,修改按键的struct
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
irq_pd = (struct pin_desc *)dev_id;
// jiffies是全局变量,每隔10ms系统产生一个时钟中断, 10ms
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
// 时间处理函数,超时会被调用
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pin_status;
if (!pindesc)
return;
// 获取该引脚状态
pin_status = s3c2410_gpio_getpin(pindesc->pin);
// 上报事件
// 之前的代码是 确定按键值,唤醒用户程序或者发信号,
// 在input subsystem中,现在只需要上报事件就ok
// input_event会从input_dev的h_list中找到input_hanldle
// 找到handler调用event函数 在drivers\input\input.c中input_event的最后几行
if (pin_status) { // 松开
// 哪个设备、事件类型、哪个事件、值(0表示松开,1表示按下)
input_event(_buttons_dev, EV_KEY, pindesc->key_val, 0);
// 还有上报同步事件
input_sync(_buttons_dev);
} else { // 按下
input_event(_buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(_buttons_dev);
}
}
static int buttons_inSubSys_init(void)
{
int i=0, error;
/*1. 分配一个input_dev结构体 */
_buttons_dev = input_allocate_device();
if (!_buttons_dev)
return -ENOMEM;
/*2. 设置 */
// unsigned long evbit[NBITS(EV_MAX)]; // 表示能产生哪类事件
// unsigned long keybit[NBITS(KEY_MAX)];// 表示能产生哪些事件
// unsigned long relbit[NBITS(REL_MAX)];// 表示能产生哪些相对位移事件 (x y 滚轮)
// #define EV_SYN 0x00 // 同步类事件
// #define EV_KEY 0x01 // 按键类事件
// #define EV_REL 0x02 // 相对位移类事件 鼠标滑动
// #define EV_ABS 0x03 // 绝对位移类事件 点击屏幕
// 2.1能产生哪类事件,按键类 重复类(修改定时器超时事件)
set_bit(EV_KEY, _buttons_dev->evbit);
set_bit(EV_REP, _buttons_dev->evbit); // 只需要这一句就可以实现按下按键时重复按键值
// 2.2能产生这类事件里的哪些事件 (哪个按键呢 l s ENTER LEFTSHIFT)
set_bit(KEY_L, _buttons_dev->keybit); // 设置数组的某一位
set_bit(KEY_S, _buttons_dev->keybit);
set_bit(KEY_ENTER, _buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, _buttons_dev->keybit);
/*3. 注册 */
// 加入到某个管理输入子系统设备的链表,然后依次与右边的hanlder的id比较,
// 看是否支持此设备,支持则调用connect函数,建立input_handle,
// dev指向左,handler指向右边,再把input_handle放入两边的.h_list
error = input_register_device(_buttons_dev);
if (error){
printk(KERN_ERR "Unable to register _buttons_dev input device.\n");
goto fail_input_register_device;
}
/*4. 硬件相关操作 */
// 初始化定时器、定时器函数、start a timer,用于防抖动
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
// 注册中断函数
for (i=0; i<NUMS_KEYS; ++i){
// 应该需要判断返回值
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, \
pins_desc[i].name, &pins_desc[i]);
if (error) {
printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d.\n",
pins_desc[i].irq, error);
goto fail_request_irq;
}
return 0;
// 倒序释放
fail_request_irq:
// 对应于request_irq,注意i
for (int i=i-1; i>=0; --i)
free_irq(pins_desc[i].irq, &pins_desc[i]);
// 对应于add_timer
del_timer(&buttons_timer);
fail_input_register_device:
// 对应于input_register_device
input_unregister_device(_buttons_dev);
// input_allocate_device
input_free_device(_buttons_dev);
return error;
}
static void buttons_inSubSys_exit(void)
{
int i;
// 对应于request_irq
for (i=NUMS_KEYS-1; i>=; --i) {
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
// 对应于add_timer
del_timer(&buttons_timer);
// 对应于input_register_device
input_unregister_device(_buttons_dev);
// input_allocate_device
input_free_device(_buttons_dev);
}
module_init(buttons_inSubSys_init);
module_exit(buttons_inSubSys_exit);
MODULE_LICENSE("GPL");