H01_Linux输入子系统(1):内核源码分析

H01_Linux输入子系统(1):内核源码分析

内核版本Linux4.4
作者huang liang

Linux内核input子系统分为三层:

  • 设备驱动层:包含各类输入设备驱动(如触摸屏、鼠标、键盘等等),获取输入事件并上报,各种输入设备驱动;
  • 核心层:核心层根据输入设备种类,分发事件至不同的事件处理器,主要是input.c;
  • 事件处理层:通用事件处理器(evdev)、鼠标事件处理器(mousedev)、摇杆事件处理器(joydev),缓存事件并提供接口等待用户获取。

各个关键结构体分析

input_dev:代表底层驱动,用于表示输入设备数据结构,在输入子系统的驱动中都会用到这个结构体。

struct input_dev {
    const char *name;//名字
    const char *phys;//设备在系统中路径
    const char *uniq;//指定唯一的ID号,就像MAC地址一样
    struct input_id id; //输入id
    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)];// 表示能产生哪些相对位移事件, x,y,滚轮
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];// 表示能产生哪些绝对位移事件, x,y  
    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)];
    int (*setkeycode)(struct input_dev *dev,
                      const struct input_keymap_entry *ke,
                      unsigned int *old_keycode);
    int (*getkeycode)(struct input_dev *dev,struct input_keymap_entry *ke);
    ...
	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)];
    struct device dev;
	struct list_head	h_list;//内核链表头
	struct list_head	node; //内核链表节点
    ...
}

input_handler:代表某个输入设备的处理方法,相当于一个上层的驱动。

struct input_handler {
    void *private;

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    void (*events)(struct input_handle *handle,
                   const struct input_value *vals, unsigned int count);
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *handler, struct input_dev *dev);
    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);
    bool legacy_minors;
    int minor;
    const char *name;
    const struct input_device_id *id_table;
    struct list_head	h_list;
    struct list_head	node;
};

input_handle:相当于是把输入设备和input_handler链接起来。

struct input_handle {
    void *private;
    int open;
    const char *name;
    struct input_dev *dev;
    struct input_handler *handler;
    struct list_head	d_node;/*用来将hanle链接到input_dev的链表上的handle*/
    struct list_head	h_node;/*用来将hanle链接到input_handler的链表上的handle*/
};

其实只需要弄懂input_dev和input_handler是如何关联的也就弄清楚了整个Linux子系统的框架结构了。

Linux输入子系统的流程可以归纳为:调用input_register_device注册到input系统。然后调用input_attach_handler,并最终调用handler->connect将input_dev 和connect链接起来。在connect函数中会调用input_register_handler注册,input_register_handler函数会调用input_register_handle,其函数定义如下:

int input_register_handle(struct input_handle *handle)
{
    struct input_handler *handler = handle->handler;
    struct input_dev *dev = handle->dev;
    /*
 * Filters go to the head of the list, normal handlers
 * to the tail.
 */
    if (handler->filter)
        list_add_rcu(&handle->d_node, &dev->h_list);
    else
        list_add_tail_rcu(&handle->d_node, &dev->h_list);
    /*
 * Since we are supposed to be called from ->connect()
 * which is mutually exclusive with ->disconnect()
 * we can't be racing with input_unregister_handle()
 * and so separate lock is not needed here.
 */
    list_add_tail_rcu(&handle->h_node, &handler->h_list);
    if (handler->start)
        handler->start(handle);
    return 0;
}

可以理解这个函数,主要就是将handle放到device’s list 和 handler list ,这样通过dev或者handler 就可以找到 handle ,结合之前的分析,通过handle 也可以找到dev和handler,这样他们就都关联起来了。

输入子系统注册函数关系

针对函数的调用关系可以参考如下:

ret = input_register_device(ts->input_dev);
	-->input_attach_handler(dev, handler);
		-->id = input_match_device(handler, dev);
			error = handler->connect(handler, dev, id);

各个关键函数分析

input_register_device函数
int input_register_device(struct input_dev *dev)
{
    __set_bit(EV_SYN, dev->evbit);
    __clear_bit(KEY_RESERVED, dev->keybit);
    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;
    }
    error = device_add(&dev->dev);
    list_add_tail(&dev->node, &input_dev_list); // 将输入设备加入内核链表 
    list_for_each_entry(handler, &input_handler_list, node)/*遍历整个事件处理器链接进行匹配*/
        input_attach_handler(dev, handler);
    input_wakeup_procfs_readers();
    mutex_unlock(&input_mutex);
    if (dev->devres_managed) {
        devres_add(dev->dev.parent, devres);
    }
    return 0;
}

可以看到上面有两个链表:

  • 输入设备链表 —— input_dev_list

  • 事件处理器链表 —— input_handler_list

    而这两个链表是在input.c中初始化的。

    static LIST_HEAD(input_dev_list);
    static LIST_HEAD(input_handler_list);
    
input_attach_handler函数

input_attach_handler函数的原型申明如下,当调用input_match_device输入设备与事件处理器匹配成功,则调用事件处理器的connect()方法进一步处理,

error = handler->connect(handler, dev, id);如果是一个通用的事件处理器(evdev),最终调用evdev_connect函数。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    const struct input_device_id *id;
    int error;
    id = input_match_device(handler, dev);
    if (!id)
        return -ENODEV;
    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;
}
input_match_device函数

input_match_device定义如下:为了代码简洁,我删掉了一些代码,其实都是类似的。

static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev)
{
    const struct input_device_id *id;
    for (id = handler->id_table; id->flags || id->driver_info; id++) {
        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 (!handler->match || handler->match(handler, dev))
            return id;
    }
    return NULL;
}

在上面的代码中,会去遍历整个input_device_id,而在evdev中evdev_ids定义如下:driver_info=1,并且没有match函数,因此会return id。

static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 },	/* Matches all devices */
	{ },			/* Terminating zero entry */
};

可以看出evdev的id==NULL,因此上面所有的bitmap_subset执行都会不成立,直接运行 if (!handler->match || handler->match(handler, dev))
并且没有math函数,这就是为什么evdev.c 中的 handler 可以适用于任何的 dev。

如果感兴趣的话也可以去分析keyboard.c的匹配。

关于evdev

在上面的函数中,最终会调用handler->connect函数,这个函数从何而来呢,我们来看一下evdev的相关说明。

vdev是一种仅限Linux的通用协议,内核使用该协议将有关输入设备的信息和事件转发给用户空间。包括鼠标和键盘,滑动轴,按键、按钮触摸屏、遥控装置等。每个设备都以/dev/input/event0的形式表示为设备节点, 随着更多的输入设备,尾随数字会增加。拔下设备后设备节点将会被其他设备重新使用,因此不要将设备节点硬编码到脚本中。设备节点也只能由root读取,因此您也需要以root身份运行任何调试工具。evdev是与Linux上的输入设备通信的主要方式。

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);
}

connect函数

对于输入子系统无论是注册设备input_register_device还是注册事件处理器input_register_handler最终都会调用到input_attach_handler进行匹配,当正确匹配的时候就会调用handler的connect方法,接下来我们就分析evdev.c中evdev_connect到底做了什么。整个函数定义如下:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
                         const struct input_device_id *id)
{
    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;
    /* Normalize device number if it falls into legacy range */
    if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
        dev_no -= EVDEV_MINOR_BASE;
    dev_set_name(&evdev->dev, "event%d", dev_no);

    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;

    evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
    evdev->dev.class = &input_class;
    evdev->dev.parent = &dev->dev;
    evdev->dev.release = evdev_free;
    device_initialize(&evdev->dev);
    error = input_register_handle(&evdev->handle);
    cdev_init(&evdev->cdev, &evdev_fops);
    evdev->cdev.kobj.parent = &evdev->dev.kobj;
    error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
    error = device_add(&evdev->dev);
    return 0;
}

上面函数最关键的就是input_register_handle,其中struct evdev中有一个很重要的成员struct input_handle handle,它是连接dev和handler的桥梁,

其他的像cdev_add、device_add就是创建一个设备节点,最后就会生成字符设备节点(/dev/input/eventN),这就是用户空间获取内核输入事件的接口。

如何编写一个input的驱动框架

配置i2c_driver

配置i2c_driver函数,并且调用 ret = i2c_add_driver(&ts_driver)注册,当name匹配的时候,就会运行probe函数。

配置probe函数

获取dts配置信息
gtp_int_gpio = of_get_named_gpio_flags(np, "int-gpio", 0, (enum of_gpio_flags *)&irq_flags);
gtp_rst_gpio = of_get_named_gpio_flags(np, "rst-gpio", 0, &rst_flags);

devm_gpio_request_one申请rst和int管脚信息,并且对int管脚注册中断管脚。

申请中断

申请中断有两种方式,一种是devm_request_threaded_irq

调用devm_request_threaded_irq注册中断,当调用之后就会像系统注册一个中断,有中断来就会响应中断函数。

另外一种方式就是采用定时器查询方式。

hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ts->timer.function = goodix_ts_timer_handler;
hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL);
注册input device

ret = input_register_device(ts->input_dev);

完善中断函数

一个中断函数如下:

static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
{
    struct goodix_ts_data *ts = dev_id;
    gtp_irq_disable(ts);
    queue_work(goodix_wq, &ts->work);
    return IRQ_HANDLED;
}

在这里有一个关于gt9xx在rk3399平台的驱动分析,具体可以参考这里。
rk3399平台gt9xx触摸屏驱动分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nineyole

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值