1,输入子系统的作用和框架
什么是输入设备: 1,按键/keyboard 2, mouse 3, touchscreen :gt811, ft56xx 4, joystick 游戏杆 有多个输入设备需要驱动的时候,假如不考虑输入子系统 a, gt811 设备号,创建文件,硬件初始化,实现fop,阻塞 硬件初始化 b, ft56xx 设备号,创建文件,硬件初始化,实现fop,阻塞 硬件初始化 多个输入设备有共同点: 获取到数据(操作硬件),上报给用户(xxx_read, copy_to_user, 阻塞) 差异化 通用 多个输入设备,有部分差异,也有部分通用 内核就会考虑,将通用代码编写好,将差异化的代码留给驱动工程师 设计成输入子系统:使得应用编程人员和驱动编程人员编程的时候变得简单统一 1, 兼容所有的输入设备 2, 统一的编程驱动方法(实现差异化硬件操作) 3, 统一的应用操作接口:/dev/input/event0,event1 open("/dev/input/event0"), read(fd, struct input_event): struct input_event buff可以认为是一个统一的数据包 框架:驱动分成三层 应用层 --------------------------------- input handler层:数据处理者 完成fop:实现xxx_read(), xxx_open 将数据交给用户:数据从input device层 不知道具体数据是什么,只知道把数据给用户 ---------------------------------------------------------- input 核心层:管理层 ---------------------------------------------------------- input device设备层: 抽象出一个对象,描述输入设备信息 初始化输入设备硬件,获取到数据 知道具体的数据是什么,但是不知道数据如何给用户 --------------------------------- 硬件层:mouse ts, keybaord,joystick 编程: 主要在input device层
2,输入子系统的编程方式–学会最简单的输入子系统的开发方式
前提:input 核心层代码和input handler层需要在内核中必须有: drivers/input/evdev.c // event handler drivers/input/input.c // 核心层 make menuconfig Device Drivers ---> Input device support ---> -*- Generic input layer (needed for keyboard, mouse, ...) // input.c <*> Event interface //input handler层--evdev.c 编写步骤: 1,分配一个input device对象 2, 初始化input device对象 3,注册input device对象 上报数据:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) 参数1:当前的input device上报数据 参数2:上报的是那种数据类型 EV_KEY,EV_ABS 参数3:具体数据是什么:KEY_POWER 参数4:值是是什么
用户空间读到的数据:统一的数据包 • 1
struct input_event { struct timeval time; //时间戳 __u16 type; //数据类型 __u16 code;//具体数据是什么 __s32 value;//值是是什么 };
3, 初始化input device
struct input_dev {//表示的是一个具体的输入设备,描述设备能够产生什么数据 const char *name; // sysfs中给用户看的信息 const char *phys; const char *uniq; struct input_id id; //evbit实际是一个位表,描述输入设备能够产生什么类型数据 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // EV_KEY,EV_ABS, EV_REL //表示能够产生哪种按键 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//KEY_POWER.. 能够表示768bit,直接用24个long来表示 // KEY_CNT == 768 BITS_TO_LONGS== nr/32 = 768/32==24 //表示能够产生哪种相对坐标数据 unsigned long relbit[BITS_TO_LONGS(REL_CNT)];// REL_X //表示能够产生哪种绝对坐标数据 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //ABS_X unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; struct device dev; // 继承device对象 struct list_head h_list; struct list_head node; //表示节点 }
不同输入设备能够产生不同的数据:
1,按键/keyboard: 产生键值,实际是一个数字
#define KEY_VOLUMEDOWN 114 #define KEY_VOLUMEUP 115 #define KEY_POWER 116 /* SC System Power Down */
2,ts/gsensor:产生坐标,绝对坐标, 有一个明确坐标系,并且原点(0,0),最大值(800,480)
#define ABS_X 0x00 #define ABS_Y 0x01 #define ABS_PRESSURE 0x18 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ #define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ #define ABS_MT_POSITION_X 0x35 /* Center X touch position */ #define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
3,mouse:产生坐标,相对坐标,坐标值是相对于之前一个点坐标 • 1
#define REL_X 0x00 #define REL_Y 0x01 #define REL_WHEEL 0x08
如何表示不同数据类型:
#define EV_SYN 0x00 //表示同步数据类型 #define EV_KEY 0x01 //表示按键数据类型 #define EV_REL 0x02 //表示相对坐标数据类型 #define EV_ABS 0x03 //表示绝对坐标数据类型 #define EV_MSC 0x04 //表示杂项 #define EV_SW 0x05 //开关 #define EV_LED 0x11 //led指示数据 #define EV_SND 0x12 //声音数据
另外一种设置bit的方法
inputdev->keybit[BIT_WORD(KEY_POWER)] |= BIT_MASK(KEY_POWER);// 116%32 inputdev->keybit[116/32] |= 1 << 116%32;// 116%32
上报数据的时候: 方法1:通用方法
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
方法2:封装:
input_report_key(struct input_dev * dev, unsigned int code, int value) | input_event(dev, EV_KEY, code, !!value); //上报按键的时候一定0或者1
4, 驱动多个按键
一个按键有多个与其相关的元素: a, 中断号码 b, 按键的状态--gpio的数据寄存器获取到 c, 按键的值--code 在设备树文件中设置这几个元素:
key_int_node{ compatible = "test_key"; #address-cells = <1>; #size-cells = <1>; key_int@0 { key_name = "key2_power_eint"; key_code = <116>; gpio = <&gpx1 1 0>; reg = <0x11000C20 0x18>; interrupt-parent = <&gpx1>; interrupts = <1 0>; }; key_int@1 { key_name = "key3_vup_eint"; key_code = <115>; gpio = <&gpx1 2 0>; reg = <0x11000C20 0x18>; interrupt-parent = <&gpx1>; interrupts = <2 0>; }; key_int@2 { key_name = "key4_vdown_eint"; key_code = <114>; gpio = <&gpx3 2 0>; reg = <0x11000C60 0x18>; interrupt-parent = <&gpx3>; interrupts = <2 0>; }; };
make dtbs 更新设备树文件: cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
在代码中也会设计这几个元素 在代码中获取节点:
of_get_next_child(const struct device_node * node, struct device_node * prev) 参数1:表示节点 参数2:之前的节点,如果是第一个节点,设置成NULL
// 通过节点去获取到中断号码 irqno = irq_of_parse_and_map(cnp, 0); //获取key name of_property_read_string(cnp, "key_name", &key_name); //获取key code of_property_read_u32(cnp, "key_code", &code); gpionum = of_get_named_gpio(cnp, "gpio", 0); printk("name = %s, code = %d, gpionum = %d,irqno = %d\n", key_name, code, gpionum,irqno);
//设计一个对象出来 struct key_desc{ char *name; int irqno; int key_code; int gpionum; void *reg_base; struct device_node *cnp;// 可以随时去获取节点各个信息 };
5, 输入子系统的工作原理和代码分析
目的: a,学会如何分析内核中子系统的代码,从而可以举一反三 b,整体把握框架思想,理解分层中各层的配合方式 c,掌握子系统,增强排错能力
分层分析:
input handler层:evdev.c
module_init(evdev_init);
module_exit(evdev_exit);
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = “evdev”,
.id_table = evdev_ids,
};
evdev_init(void) | input_register_handler(&evdev_handler); | //初始化h_list INIT_LIST_HEAD(&handler->h_list); //将当前的handler加入到一个input_handler_list list_add_tail(&handler->node, &input_handler_list); //遍历链表input_dev_list list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); | // 将当前的hanler和input dev进行匹配, event handler能够匹配所有的input dev id = input_match_device(handler, dev); //匹配成功,之后要调用handler中connect方法 // 实际就是event handler,实际调用了evdev_connect error = handler->connect(handler, dev, id); //将当前的handler加入到/proc/bus/input/handlers文件中 input_wakeup_procfs_readers(); 总结: 1,注册了evdev_handler 2, 遍历input dev list,并行匹配,恒匹配成功,自动会 调用handler中connect方法--- evdev_connect
input核心层:input.c
subsys_initcall(input_init);
module_exit(input_exit);
input_init() | //注册类,类似于class_create(); err = class_register(&input_class); //在/proc创建 bus/input/devices handlers err = input_proc_init(); //申请设备号 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input"); 总结: 1,注册了主设备号 2,注册input class
simple_input.c
input_register_device(inputdev);
|
//将input dev加入到链表input_dev_list
list_add_tail(&dev->node, &input_dev_list);
//遍历input handler链表,进行匹配 list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); | //匹配成功,之后要调用handler中connect方法 // 实际就是event handler,实际调用了evdev_connect error = handler->connect(handler, dev, id);
分析: evdev.c中evdev_connect()— 属于input handler层
evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
|
//找到一个没有被使用的次设备号, 从64开始, 65,66
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
// 实例化 一个evdev对象 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //初始化evdev对象 INIT_LIST_HEAD(&evdev->client_list); spin_lock_init(&evdev->client_lock); mutex_init(&evdev->mutex); //等待队列是完成阻塞 init_waitqueue_head(&evdev->wait); evdev->exist = true; dev_no = minor; dev_no -= EVDEV_MINOR_BASE; //减去了64 // 创建设备文件/dev/event0,1,2 dev_set_name(&evdev->dev, "event%d", dev_no); evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);// 12, 64 evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev) device_add(&evdev->dev); //以上代码和device_create是一样 //利用handle记录input device和input handler evdev->handle.dev = input_get_device(dev); evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; //你中有有我,我中有你 evdev->handle.private = evdev; //将儿子关联到父亲(input handler)和母亲(input dev) error = input_register_handle(&evdev->handle); | list_add_tail_rcu(&handle->d_node, &dev->h_list); list_add_tail_rcu(&handle->h_node, &handler->h_list); //初始化了cdev,完成了fops, 为用户提供文件io cdev_init(&evdev->cdev, &evdev_fops); evdev->cdev.kobj.parent = &evdev->dev.kobj; error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); 总结: 1,分配evdev,并初始化,记录input device和handler的关系 2,创建设备节点/dev/event0 3, 注册cdev,并实现fops 4,关系: 多个input device可以对应一个event handler 一个input device对应一个 evdev,对应于一个设备节点/dev/event0,1,2 5, 所有的设备节点调用open,read,write文件io的时候 实际是调用cdev中fops中各个接口,最终都调用了 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, #ifdef CONFIG_COMPAT .compat_ioctl = evdev_ioctl_compat, #endif .fasync = evdev_fasync, .flush = evdev_flush, .llseek = no_llseek, };
device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, …)
|
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
|
device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, args);
|
struct device *dev = NULL;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
device_initialize(dev); dev->devt = devt; dev->class = class; dev->parent = parent; dev->groups = groups; dev->release = device_create_release; dev_set_drvdata(dev, drvdata); kobject_set_name_vargs(&dev->kobj, fmt, args);//设置名字 device_add(dev);//注册到系统
应用程序中调用了输入子系统的代码,数据是如何传递给用户层的?
open(“/dev/event1”, O_RDWR);
vfs
sys_open();
struct file file->f_ops = cdev->ops;
file->f_ops->open();
设备驱动层:输入子系统
input handler 层:evdev.c
cdev;
xxx_ops = {
.open = xxx_open,
.write = xxx_write,
}
static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, .write = evdev_write, .poll = evdev_poll, .open = evdev_open, } 实际最终调用了evdev_open(); | // 实际cdev是谁,就是evdev_connect注册的那个 struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev); // 通过儿子,找到老母input device unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev); // size就包含了很多input_event unsigned int size = sizeof(struct evdev_client) + bufsize * sizeof(struct input_event); struct evdev_client *client; // 分配一个client对像,描述一个缓冲队列,存放的就是input_event client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN); // client中有一个缓冲区 client->bufsize = bufsize; spin_lock_init(&client->buffer_lock); //在client中记录evdev client->evdev = evdev; // 将client加入到evdev中一个小链表 evdev_attach_client(evdev, client); | list_add_tail_rcu(&client->node, &evdev->client_list); // 将client记录到file,方面其他的接口使用 file->private_data = client; 总结: 1,为输入设备分配一个缓冲区evdev_client,用户存放input device层上报的数据 2,evdev_client记录到evdev中 3,evdev_client记录到file中,方面其他的接口使用
应用程序中read,是如何获取到数据的
read(fd, &event, sizeof(struct input_event));
---------------------------------------------
vfs
sys_read();
file->f_ops->read();
-------------------------------------------
evdev.c
static const struct file_operations evdev_fops = {
.read = evdev_read,
evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
|
// 获取到open中分配的缓冲区对象
struct evdev_client *client = file->private_data;
//获取到evdev
struct evdev *evdev = client->evdev;
//表示一个数据包,要给用户
struct input_event event;
for (;;) { // 实现非阻塞 if (client->packet_head == client->tail && (file->f_flags & O_NONBLOCK)) return -EAGAIN; while (read + input_event_size() <= count && // 1从缓冲区获取数据,存放在 input_event数据包 evdev_fetch_next_event(client, &event)) { | *event = client->buffer[client->tail++]; // 2, 将数据上报给用户 if (input_event_to_user(buffer + read, &event)) | copy_to_user(buffer, event, sizeof(struct input_event) // 3,统计上报多少数据 read += input_event_size(); } // 如果当前是阻塞模式 if (!(file->f_flags & O_NONBLOCK)) { //等待---休眠,需要被唤醒,有数据时候唤醒 error = wait_event_interruptible(evdev->wait, client->packet_head != client->tail || !evdev->exist || client->revoked); } 总结: 1,如果没有数据,就会休眠等待 2,如果有数据,就从缓冲区client->buffer[client->tail++]拿数据 通过copy_to_user上报给用户 疑问: 数据到底是如何存放在缓冲区的 等待队列是谁唤醒的 input_report_key(inputdev, pdesc->key_code, 0); input_sync(inputdev);//上报数据结束 input_report_key(inputdev, pdesc->key_code, 0); input_sync(inputdev);//上报数据结束 |//input_event(dev, EV_KEY, code, !!value); input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);// 上报数据 | input_handle_event(dev, type, code, value); | // 如果将数据交给input handler去处理 if (disposition & INPUT_PASS_TO_HANDLERS) { struct input_value *v; //将input device获取到数据暂存一下input value v = &dev->vals[dev->num_vals++]; v->type = type; v->code = code; v->value = value; input_pass_values(dev, dev->vals, dev->num_vals) | // 从input device中获取到input handle else { list_for_each_entry_rcu(handle, &dev->h_list, d_node) if (handle->open) count = input_to_handler(handle, vals, count); | // 通过handle儿子找到handler父亲 struct input_handler *handler = handle->handler; // 如果有events函数指针,那就调用 if (handler->events) handler->events(handle, vals, count); else if (handler->event)//否则就调用event(); for (v = vals; v != end; v++) handler->event(handle, v->type, v->code, v->value); }
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = “evdev”,
.id_table = evdev_ids,
};
总结: 如果将数据上报,最终是调用handler中events()或者event()
实际是evdev.c
.event = evdev_event,
.events = evdev_events,
|
// 拿到evdev,肯定要拿到缓冲区
struct evdev *evdev = handle->private;
struct evdev_client *client;
// 获取到缓冲evdev_client else list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_values(client, vals, count, time_mono, time_real); | // 通过client获取到evdev struct evdev *evdev = client->evdev; const struct input_value *v; struct input_event event; event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real); for (v = vals; v != vals + count; v++) { // 将input device上报的数据获取到,并且封装成input event对象 event.type = v->type; event.code = v->code; event.value = v->value; // 将input event数据存放到缓冲区中 __pass_event(client, &event); | client->buffer[client->head++] = *event; client->head &= client->bufsize - 1; for (v = vals; v != vals + count; v++) { // 将input device上报的数据获取到,并且封装成input event对象 event.type = v->type; event.code = v->code; event.value = v->value; // 将input event数据存放到缓冲区中 __pass_event(client, &event); client->buffer[client->head++] = *event; client->head &= client->bufsize - 1; // 如果调用 input_sync() if (v->type == EV_SYN && v->code == SYN_REPORT) wakeup = true; } // 唤醒等待队列 if (wakeup) wake_up_interruptible(&evdev->wait); 总结: input_report_key(inputdev, pdesc->key_code, 0); //将输入设备产生数据交给input handler,调用events();将数据存放在 // 缓冲区client->buffer[client->head++] = *event; input_sync(inputdev);//上报数据结束 // 唤醒等待队列,表示输入设备上报数据完毕