
本文共 18161 字,大约阅读时间需要 60 分钟。
一 引入输入子系统的目的
二 输入子系统框架
三 输入子系统之核心层简述
四 输入子系统之事件处理层简述
五 输入子系统之驱动层简述
六 TP调试记录
一 引入输入子系统的目的
个人理解是为了 将输入设备的功能直接提供给用户空间,如果按照普通字符设备的方式编写输入设备,那么我们自己的驱动会生成我们自己命名的设备节点,只有我们自己知道设备节点名称,也就是只有我们自己可以打开这个设备节点,此时这种驱动程序只能自己使用。如果想使自己的驱动程序生成的节点成为“公共的”,即生成的节点可以直接被打开使用,各种应用程序都可以调用该节点,那么就引入 “输入子系统”
二 输入子系统框架
输入子系统分为三部分:
1:设备层 如matrix_key.c / gpio_keys.c等等 :和硬件相关的底层驱动(我们需要实现的部分),主要实现对硬件设备的读写访问,中断设置,把底层硬件的输入事件 上报给核心层。2:核心层 input.c :为 设备驱动层输入设备 以及 事件处理层 提供注册和操作的接口,并且传递设备层数据到时间处理层3:事件处理层 Evdev.c :则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。
三 输入子系统之核心层简述
3.1 核心层功能
1创建注册字符设备2为设备驱动层提供注册操作接口3为事件处理层提供注册操作接口4为 设备驱动层 与 事件处理层 提供匹配函数接口
3.2 关键函数举例
//创建字符设备结构体 : truct file_operations input_handlers_fileops//注册字符设备 : __init input_init//创建 proc 文件系统相关 : __init input_proc_init(void)//用于匹配 底层驱动 与 上层handler (匹配成功则调用 handler的connect函数) :input_attach_handler//进行 handler 与 device(输入设备) 的匹配 :struct input_device_id *input_match_device
3.3 关键函数说明
//注册字符设备 static int __init input_init(void){ int err;//创建设备类 err = class_register(&input_class); if (err) { pr_err("unable to register input_dev class\n"); return err; }//proc文件系统相关 err = input_proc_init(); if (err) goto fail1;//注册字符设备 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input"); if (err) { pr_err("unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err;}//创建 proc 文件系统相关static int __init input_proc_init(void){ struct proc_dir_entry *entry;//在 /proc/bus 目录下创建 input目录 proc_bus_input_dir = proc_mkdir("bus/input", NULL); if (!proc_bus_input_dir) return -ENOMEM;//在 /proc/bus/input 目录下创建 devices 文件 entry = proc_create("devices", 0, proc_bus_input_dir, &input_devices_fileops); if (!entry) goto fail1;//在 /proc/bus/input 目录下创建 handlers 文件 entry = proc_create("handlers", 0, proc_bus_input_dir, &input_handlers_fileops); if (!entry) goto fail2; return 0; fail2: remove_proc_entry("devices", proc_bus_input_dir); fail1: remove_proc_entry("bus/input", NULL); return -ENOMEM;}//创建字符设备结构体static const struct file_operations input_handlers_fileops = { .owner = THIS_MODULE, .open = input_proc_handlers_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release,};//用于匹配 底层驱动 与 上层handler (匹配成功则调用 handler的connect函数)static int input_attach_handler(struct input_dev *dev, struct input_handler *handler){const struct input_device_id *id;int error;//进行 handler 与 device(输入设备) 的匹配id = input_match_device(handler, dev);if (!id) return -ENODEV;//如果匹配成功 则调用 handler中的connect函数进行连接error = handler->connect(handler, dev, id);if (error && error != -ENODEV) pr_err("failed to attach handler %s to device %s, error: %d\n", handler->name, kobject_name(&dev->dev.kobj), error);return error;}static const struct input_device_id *input_match_device(struct input_handler *handler, struct input_dev *dev){const struct input_device_id *id;//遍历 handler->id_table 中的全部 input_device_id(输入设备ID)for (id = handler->id_table; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) if (id->bustype != dev->id.bustype) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) if (id->vendor != dev->id.vendor) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) if (id->product != dev->id.product) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) if (id->version != dev->id.version) continue; if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX)) continue; if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX)) continue; if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX)) continue; if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX)) continue; if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX)) continue; if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX)) continue; if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX)) continue; if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX)) continue; if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX)) continue; if (!handler->match || handler->match(handler, dev)) return id;}return NULL;}
3.4 小结:
核心层为 设备驱动层 提供的接口有:
//分配 input_dev 结构体input_allocate_device(void) ://设置 输入设备所上报的事件input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)//向 核心层 注册设备int input_register_device(struct input_dev *dev)
核心层为 事件层 提供的接口有:
input_register_handler :事件层向核心层注册 handler (handler 与 input_dev成对应关系)input_regiser_handle :事件层向核心层注册 handle (不同于 handler,handle是用于记录匹配成功的 handler与input_dev) handle是用于记录匹配成功的 handler与input_dev,可以通过 handle获得 input_dev 以及 handler的信息
核心层关键点1
输入系统注册设备时,设备注册 与 handler注册 后的匹配过过程 和 platform平台有些相似,设备与 handler 注册时都会各自把 dev 或 handler 挂在各自设备链表 或 事件链表上,然后去遍历对方的事件链表 或 设备链表,input_dev 与 handler是多对多的关系
核心层关键点2
input_dev_list 链表上的 input_dev设备节点input_handler_list 链表上的 handler 事件节点input_dev_list 链表 和 input_handler_list 链表 上对应的节点都会有一个 匹配记录结构体 handle,用于记录匹配信息,所以 两个链表上的节点 可以通过handle 互相访问;
核心层关键点3
核心层总结:1创建注册字符设备2为设备驱动层提供注册操作接口3为事件处理层提供注册操作接口4为 设备驱动层 与 事件处理层 提供匹配函数接口
四 输入子系统之事件处理层简述
注意 :此处 事件处理层 只做简单描述,主要是为了加强印象,日后有时间补充。
struct input_handler { void *private; void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle); void (*start)(struct input_handle *handle); const struct file_operations *fops; int minor; //次设备号 const char *name; const struct input_device_id *id_table; const struct input_device_id *blacklist; struct list_head h_list; //h_list是一个链表头,用来把handle挂载在这个上 struct list_head node; //这个node是用来连到input_handler_list上的 }; struct input_handle { void *private; int open; const char *name; struct input_dev *dev; //指向input_dev struct input_handler *handler; //指向input_handler struct list_head d_node; //连到input_dev的h_list上 struct list_head h_node; //连到input_handler的h_list上 };
1 事件处理层描述:
输入子系统上层其实是由多个事件(handler)组成的,各个事件之间的关系是平行关系,互不干扰,用的较多的是event,此处以event为例,事件处理层 属于 input输入子系统上层
2 事件处理层主要框架简述
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,.events = evdev_events,.connect = evdev_connect,//挂接函数.disconnect = evdev_disconnect,.legacy_minors = true,.minor = EVDEV_MINOR_BASE,.name = "evdev",.id_table = evdev_ids,};static int __init evdev_init(void){return input_register_handler(&evdev_handler);}static void __exit evdev_exit(void){input_unregister_handler(&evdev_handler);}module_init(evdev_init);module_exit(evdev_exit);
五 输入子系统之驱动层简述
struct input_dev { const char *name; //设备名 const char *phys; //设备在系统中的物理路径 const char *uniq; //设备唯一识别符 struct input_id id;//设备ID,包含总线ID(PCI、USB)、厂商ID,与input_handler匹配的时会用到 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //支持的所有事件类型 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //支持的键盘事件 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //支持的鼠标相对值事件 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //支持的鼠标绝对值事件 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; //支持的其它事件类型 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; //支持的LED灯事件 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; //支持的声效事件 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; //支持的力反馈事件 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; //支持的开关事件 unsigned int hint_events_per_packet; unsigned int keycodemax; //keycode表的大小 unsigned int keycodesize; //keycode表中元素个数 void *keycode; //设备的键盘表 //配置keycode表 int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int *old_keycode); //获取keycode表 int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke); struct ff_device *ff; unsigned int repeat_key;//保存上一个键值 struct timer_list timer; int rep[REP_CNT]; struct input_mt *mt; struct input_absinfo *absinfo; unsigned long key[BITS_TO_LONGS(KEY_CNT)];//按键有两种状态,按下和抬起,这个字段就是记录这两个状态。 unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //操作接口 int (*open)(struct input_dev *dev); void (*close)(struct input_dev *dev); int (*flush)(struct input_dev *dev, struct file *file); int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab; spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; struct device dev; struct list_head h_list; //h_list是一个链表头,用来把handle挂载在这个上 struct list_head node; //这个node是用来连到input_dev_list上的 unsigned int num_vals; unsigned int max_vals; struct input_value *vals; bool devres_managed;};// input_dev->evbit 表示设备支持的事件类型,可以是下列值的组合 #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 //声音输出,如蜂鸣器 #define EV_REP 0x14 //允许按键自重复 #define EV_FF 0x15 //力反馈 #define EV_PWR 0x16 //电源管理事件
说明:
在输入子系统框架下,我们一般的编写驱动也就是对device部分j即驱动层进行编写(分配input_dev并配置,驱动入口,出口,中断时进行中断判断,然后上报事件等),然后对该device的input_dev进行注册。
问题:我们在编写 输入子系统驱动的时候,是怎么将驱动层与事务管理层联系起来的,驱动层千篇一律的一个步骤代码中道理做了什么?
5.1 驱动层关键代码说明
//步骤一: 1. 分配一个 input_dev 设备结构体 2. 初始化 设置 input 输入设备结构体设备属性 3. 设置你的input设备支持的事件类型以及所支持的事件//步骤二: 1. 注册中断处理函数,在中断处理程序中上报事件//步骤三: 1. 注册输入设备到输入字系统注意:驱动层input输入子系统的工作很简单,主要在 prob函数 和 中断函数中,static int matrix_keypad_probe(struct platform_device *pdev){ //输入设备结构体 struct input_dev *input_dev; //将会分配一个 input_dev 设备结构体,并且在 /sys/class/input/input-n 下创建设备属性文件 input_dev = input_allocate_device(); //初始化 设置 input 输入设备结构体 input_dev->name = pdev->name; input_dev->id.bustype = BUS_HOST; input_dev->dev.parent = &pdev->dev; input_dev->open = matrix_keypad_start; input_dev->close = matrix_keypad_stop; //设置支持的按键事件类型,如设置支持按键类型 input_dev->evbit[0] = BIT_MASK(EV_KEY); //所支持的事件 如设置支持 KEY_F18/KEY_F19/KEY_F20/KEY_F21/KEY_F22按键事件 set_bit(KEY_F18, input_dev->keybit); set_bit(KEY_F19, input_dev->keybit); set_bit(KEY_F20, input_dev->keybit); set_bit(KEY_F21, input_dev->keybit); set_bit(KEY_F22, input_dev->keybit); //注册 input输入设备 err = input_register_device(keypad->input_dev);} //上报事件 XXXX_interrupt{ //提交输入事件 input_event(input_dev, EV_MSC, MSC_SCAN, code); //提交按键值 input_report_key(input_dev,keycodes[code],new_state[col] & (1 << row)); //同步 input_sync(input_dev);}
5.2 input_register_device关键信息分析
int input_register_device(struct input_dev *dev){ struct input_devres *devres = NULL; struct input_handler *handler; unsigned int packet_size; const char *path; int error; if (dev->devres_managed) { devres = devres_alloc(devm_input_device_unregister, sizeof(struct input_devres), GFP_KERNEL); if (!devres) return -ENOMEM; devres->input = dev; } /* 看注释 Every input device generates EV_SYN/SYN_REPORT events. */ __set_bit(EV_SYN, dev->evbit); /* KEY_RESERVED is not supposed to be transmitted to userspace. */ __clear_bit(KEY_RESERVED, dev->keybit); /* Make sure that bitmasks not mentioned in dev->evbit are clean. */ input_cleanse_bitmasks(dev); packet_size = input_estimate_events_per_packet(dev); if (dev->hint_events_per_packet < packet_size) dev->hint_events_per_packet = packet_size; dev->max_vals = dev->hint_events_per_packet + 2; dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL); if (!dev->vals) { error = -ENOMEM; goto err_devres_free; } /* * If delay and period are pre-set by the driver, then autorepeating * is handled by the driver itself and we don't do it in input.c. */ if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) input_enable_softrepeat(dev, 250, 33); if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode; error = device_add(&dev->dev); if (error) goto err_free_vals; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); pr_info("%s as %s\n", dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); kfree(path); error = mutex_lock_interruptible(&input_mutex); if (error) goto err_device_del; list_add_tail(&dev->node, &input_dev_list); / 将新分配的input设备连接到input_dev_list链表上 list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler);///遍历input_handler_list链表,配对 input_dev 和 input_handler, input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); if (dev->devres_managed) { dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", __func__, dev_name(&dev->dev)); devres_add(dev->dev.parent, devres); } return 0;err_device_del: device_del(&dev->dev);err_free_vals: kfree(dev->vals); dev->vals = NULL;err_devres_free: devres_free(devres); return error;}
输入系统大致流程: 设备驱动 --> 核心层 --> 事件处理层 --> 用户空间
六 输入子系统实例1
场景说明:
3288主板I2C4 连接外部单片机小板,外部小板按键板按键 发生变化时通过中断提示3288主板,3288主板通过i2c获取外部小板某寄存器内数值。将这按键变化的数值 与内核输入子系统中的 KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22 一一对应。并将键值上报。static int chensai_i2c_read( struct i2c_client* client,unsigned char reg,uint8_t *data, char device_addr){ int ret; struct i2c_msg msgs[] = { { .addr = device_addr, .flags = 0, // .len = 1, .len = sizeof(reg), .buf = ®,// 寄存器地址 }, { .addr = device_addr, // .flags = I2C_M_RD,0x01 .flags = I2C_M_RD, .len = sizeof(data), .buf = data,// 寄存器的值 }, }; ret = i2c_transfer(client->adapter, msgs, 2); if (ret < 0) { printk("i2c read error\n"); } return ret;}static irqreturn_t chensai_irq_handler(int irq, void *dev_id){ int gpio_val; int keys_val[5]; int i;//,j; disable_irq_nosync(chensai_irq_num); chensai_i2c_read(chensai_client, register_addr, key_status, chensai_iic_addr); key_status_val = key_status[0]; DBG("%s key_status_val=%d\n", __func__, key_status_val);for( i=0; i < 5; i++) { if(key_status_val & (1 << i)) { keys_val[i] = 1; }else{ keys_val[i] = 0; } } //上报 input_report_key(input_dev, KEY_F18, keys_val[0]); input_report_key(input_dev, KEY_F19, keys_val[1]); input_report_key(input_dev, KEY_F20, keys_val[2]); input_report_key(input_dev, KEY_F21, keys_val[3]); input_report_key(input_dev, KEY_F22, keys_val[4]); input_sync(input_dev); enable_irq(chensai_irq_num); return IRQ_HANDLED;}static int chensai_probe(struct i2c_client *client, const struct i2c_device_id *id){ int error; int ret; unsigned int gpio_num; struct device_node *np = client->dev.of_node; enum of_gpio_flags flags; static struct task_struct *task; DBG("%s : chensai_probe\n", __func__); //将会分配一个 input_dev 设备结构体,并且在 /sys/class/input/input-n 下创建设备属性文件 input_dev = input_allocate_device(); // input输入子系统设备属性 input_dev->name = "chensai_input_device"; input_dev->phys = "chensai-input"; input_dev->id.bustype = BUS_HOST; input_dev->id.vendor = 1; input_dev->id.product = 1; input_dev->id.version = 1; input_dev->evbit[0] = BIT_MASK(EV_KEY);//设置支持的按键事件类型 //设置支持 KEY_F18/KEY_F19/KEY_F20/KEY_F21/KEY_F22按键事件 set_bit(KEY_F18, input_dev->keybit); set_bit(KEY_F19, input_dev->keybit); set_bit(KEY_F20, input_dev->keybit); set_bit(KEY_F21, input_dev->keybit); set_bit(KEY_F22, input_dev->keybit); error = input_register_device(input_dev); if(error){ printk("Failed to register input_dev\n"); } gpio_num =of_get_named_gpio_flags(np, "irq-gpio", 0, &flags); DBG("%s gpio_num=%d\n", __func__, gpio_num); if (!gpio_is_valid(gpio_num)){ DBG("%s gpio_is_unvalid \n", __func__); } if (gpio_request(gpio_num, "irq-gpio")) { DBG("%s failed to request irq-gpio, gpio_num =%d\n", __func__, gpio_num); } gpio_direction_input(gpio_num); chensai_irq_num = gpio_to_irq(gpio_num); //将gpio转换成对应的中断号 DBG("%s chensai_irq_num=%d\n", __func__, chensai_irq_num); ret = request_irq(chensai_irq_num, chensai_irq_handler, IRQ_TYPE_EDGE_FALLING, "chensai_irq", NULL); if (ret) { printk("request_irq error\n"); } chensai_client = client; return 0;}static const struct i2c_device_id chensai_id[] = { {"chensai_keyboard", 0}, {}};MODULE_DEVICE_TABLE(i2c, chensai_id);static struct i2c_driver chensai_drv = { .driver = { .name = "chensai", .owner = THIS_MODULE, }, .probe = chensai_probe, .id_table = chensai_id,};static int chensai_init(void){ i2c_add_driver(&chensai_drv); return 0;}static void chensai_exit(void){ i2c_del_driver(&chensai_drv); input_unregister_device(input_dev); free_irq(chensai_irq_num, chensai_irq_handler);}module_init(chensai_init);module_exit(chensai_exit);MODULE_LICENSE("GPL");
六 TP调试记录
如果出现触摸屏出现触摸坐标偏移或者完全反向等现象,需要在驱动中调整
根据从设备树中 索引的 “tp-size”查找
mGtpChange_X2Y = FALSE;// X 轴 Y轴 坐标需要对调 mGtp_X_Reverse = FALSE; //X轴标是否需要对调 mGtp_Y_Reverse = FALSE; //Y轴是否需要对调三个参数
发表评论
最新留言
关于作者
