Linux中的input子系统浅析

本文详细解析了Linux内核的Input子系统,包括输入设备驱动(input_dev)、核心层(inputcore)、事件处理器(eventhandler)以及关键结构体。介绍了设备注册、事件上报、处理流程和关键函数,如input_register_device和input_event的用法。

一、input子系统框架

系统框架主要分为三部分:

        Input driver:该部分是对应实际的输入设备驱动,用于处理硬件设备,将数据转化成统一的数据,方便和handler层交互。

        input core:字面意思就是input系统的核心层,提供各种处理函数input_register_device、input_register_handler、input_allocate_device、input_event等。

        Event handler:对应某个输入事件的处理,将数据发送到上层。

二、Input子系统中重要的一些结构体

2.1 input_dev

struct input_dev {//对应实际的输入设备
	const char *name;//设备名称
	const char *phys;//设备在系统中的路径
	const char *uniq;//设备唯一id
	struct input_id id;//input设备id号

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//支持哪类事件?key/rel/abs ?
	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;//支持最大按键数
	unsigned int keycodesize;//每个键对值字节数
	void *keycode;//存储按键值的数组的首地址

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

	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)];//反应设备当前led状态
	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);//事件处理函数,主要是接收用户下发的命令,如点亮led

	struct input_handle __rcu *grab;//当前占有设备的input_handle

	spinlock_t event_lock;//事件锁
	struct mutex mutex;//互斥体

	unsigned int users;//打开该设备的用户数量
	bool going_away;//标记正在销毁的设备

	struct device dev;//一般设备

	struct list_head	h_list;//input_handle链表,里面还有支持该设备的input_handler
	struct list_head	node;//input_handle != input_handler
	

	unsigned int num_vals;
	unsigned int max_vals;
	struct input_value *vals;

	bool devres_managed;
};

2.2 Input_handler

struct input_handler {//对应的事件处理器,注册成功之后会存入到对应的input_handler_list链表中

	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//如果没有events,就用event一个一个处理
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);//如果没被过滤,调用events处理(可以处理多个事件,更高效)
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//硬件产生出数据时优先调用filter函数(过滤)
	bool (*match)(struct input_handler *handler, struct input_dev *dev);//用来判断能否支持某个input_dev
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//若支持,调用此函数给input_dev/input_handler建立联系
	void (*disconnect)(struct input_handle *handle);//断开连接
	void (*start)(struct input_handle *handle);//建立联系后 start

	bool legacy_minors;//connect函数根据legacy_minors来决定是否使用minor创建设备节点
	int minor;
	const char *name;

	const struct input_device_id *id_table;//用来判断能否支持某个input_dev

	struct list_head	h_list;//对应的input_handle链表
	struct list_head	node;//input_handler_list链表
};

注:每个处理器都会实例化一个具体的handler,例如evdev.c会实例化一个evdev_handler,在这个handler中会实现具体的时间上报,建立联系等函数。

2.3 input_handle

struct input_handle {//对应事件处理,也就是处理器和输入设备之间联系的桥梁,当input_dev和input_handler匹配成功后,会将该结构体存入到一个h_list双向链表中

	void *private;//数据

	int open;//打开标志,每个inpu_handle打开后才能操作
	const char *name;//设备名称

	//以下两个就是匹配的双方
	struct input_dev *dev;
	struct input_handler *handler;

	
	struct list_head	d_node;//用于链入所指向的input_dev的handle链表
	struct list_head	h_node;//用于链入所指向的input_handler的handle链表
};

2.3 input_event

  struct input_event {//该结构体就是上报的数据
      struct timeval time;//时间
      __u16 type;//事件类型。事件类型可以是按键事件、鼠标事件、触摸事件等等。每种类型的事件都有对应的 type 值,例如按键事件的 type 值为 EV_KEY。
      __u16 code;//事件的代码。事件代码用于指定特定的按键、鼠标按钮或者其他输入设备的操作。例如,在按键事件中,code 值表示按下或释放的按键键码(key code)。
      __s32 value;//事件的值。事件值通常是 0 或 1,表示按键的按下或释放状态,或者是鼠标移动的距离等等。有些事件可能还会使用其他的值,例如在触摸事件中,value 值表示触摸点的坐标。
  };

struct timeval {
	__kernel_time_t		tv_sec;		/* seconds */
	__kernel_suseconds_t	tv_usec;	/* microseconds */
};

三、Iput子系统核心层解析

3.1 函数解析

struct input_dev *input_allocate_device(void)//分配输入设备结构体

int input_register_device(struct input_dev *dev)//输入设备注册
注:用于把输入设备挂到设备链表input_dev_lsit中

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)//报告输入事件,也就是将底层数据上报到上层

void input_sync()//报告结束,同步用于告诉input core子系统报告结束

int input_register_handler(struct input_handler *handler)//注册一个事件处理器
注:用于事件处理器挂到input_dev_list中

int input_register_handle(struct input_handle *)//向内核注册一个handle结构
注:当input_dev和input_handler匹配成功后会调用该函数

从代码层面分析:

int input_register_device(struct input_dev *dev)
{
	...
    list_add_tail(&dev->node, &input_dev_list);

    list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);
	...
}
int input_register_handler(struct input_handler *handler)                                                                                     
{
	...
    INIT_LIST_HEAD(&handler->h_list);
    list_add_tail(&handler->node, &input_handler_list);
	
	list_for_each_entry(dev, &input_dev_list, node)
	input_attach_handler(dev, handler);
}

可以看出两个函数极为相似,下面重点来分析input_attach_handler()这个函数:

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这个函数:

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 (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 (!handler->match || handler->match(handler, dev))
            return id;
    }
                                                                                                                                              
    return NULL;
}

        以上函数会将新注册到input core中的input_dev或者input_handler,遍历input_dev_list或者inpu_handler进行匹配

3.2 关键流程

        执行的时候会注册设备号,然后在handler层注册input_handler,也就是evdev_handler会注册到核心层维护的链表中。
    然后进行硬件初始化获取数据,而且需要将设备注册到链表中。注册进来就就会遍历input_handler_list链表,找到对应的handler,匹配成功后会调用connect方法。
connect分配evdev,evdev就记录了input_handler和input_device之间的关系,同时创建设备节点,还会注册cdev从而可以让应用调用。
    当应用程序调用open,read等接口的时候就会调用input_handler层实现的xxx_open,那么open就会分配好evdev_client,最终在input_dev层上报数据的时候会自动调用input_handler,
input_handler就会调用events填充上报的数据到缓冲区client,此时如果没有唤醒队列的话应用read的时候会阻塞,而唤醒队列后最终使用copy_to_user来给应用数据。 
    设备驱动程序上报事件的函数有:

	input_report_key //上报按键事件
	input_report_rel //上报相对坐标事件
	input_report_abs //上报绝对坐标事件
	input_report_ff_status
	input_report_switch
	input_sync //上报完成后需要调用这些函数来通知系统处理完整事件 
	input_mt_sync //上报完成后需要调用这些函数来通知系统处理完整事件


    这些函数其实是input_event函数的封装,调用的都是input_event函数,在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;
核心层将事件数据(type、code、value)打包、分发至事件处理器;调用关系为:input_event->input_handle_event->input_pass_values,这一函数都在input.c实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哈~~哈~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值