嵌入式linux | 输入子系统分析

一、导读写文章

linux内核的输入子系统是一个“一对多”的驱动程序,对上层,为应用程序提供统一的事件接口;对下层,统一形态各一的输入设备的处理功能,例如:各种鼠标,不管是PS/2、USB还是蓝牙的,都进行相同的处理。

👉相关源码文件:

  • drivers/input/input-core.c
  • drivers/input/input.c      输入子系统的核心
  • drivers/input/input-compat.c 输入子系统的32位兼容性包装器
  • drivers/input/input-mt.c   输入多点触控库
  • drivers/input/input-poller.c   支持输入设备的轮询模式。
  • drivers/input/ff-core.c   支持Linux输入子系统的强制反馈
  • drivers/input/touchscreen.c 用于触摸屏和其他二维指向设备的通用助手函数

二、重要数据数据结构

(2-1)struct input_dev

input_dev代表一个input设备,定义如下(include/linux/input.h):

struct input_dev {
 const char *name;  //输入设备的名称
 const char *phys;  //输入设备的物理地址
 const char *uniq;  //输入设备的唯一标识符
 struct input_id 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)];      //相对坐标的位图,表示设备支持的相对坐标事件。
 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 相关的位图,表示设备支持的 LED 灯。
 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];      //sound 有关的位图,表示设备支持的声音事件。
 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); //事件处理函数的指针。

 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; //连接到输入处理程序链表的链表头。
 struct list_head node;   //连接到全局输入设备链表的链表头。

 unsigned int num_vals;  //输入值的数量。
 unsigned int max_vals;  //输入值的最大数量。
 struct input_value *vals; //输入值数组的指针。
 
 bool devres_managed;  //标志,指示设备是否由设备资源管理器管理。
};

input_dev结构中定义了几个用于描述具体输入行为的类型,其中evbit表示输入事件类型,在linux内核中为许多常见的事件类型都进行了定义(/include/uapi/linux/input.h):

#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
#define EV_SND   0x12
#define EV_REP   0x14
#define EV_FF   0x15
#define EV_PWR   0x16
#define EV_FF_STATUS  0x17
#define EV_MAX   0x1f
#define EV_CNT   (EV_MAX+1)

在/include/uapi/linux/input.h文件中也能找到输入子系统下许多的宏定义,这些宏定义可以在基于输入子系统的驱动程序中使用。

(2-2)input_dev_list和input_handler_list

在input输入子系统中定义了两个重要的全局链表:

static LIST_HEAD(input_dev_list);  //dev链表
static LIST_HEAD(input_handler_list);  //handler链表

input_dev_list表示input输入子系统中的所有设备,当使用input_register_device()这次input设备时,所注册的设备会被添加到input_dev_list链表中:

list_add_tail(&dev->node, &input_dev_list);

input_handler_list表示input输入子系统的handler,当使用input_register_handler()注册一个handler时,会将该handler添加到input_handler_list链表中:

list_add_tail(&handler->node, &input_handler_list);

(2-3)struct input_handler

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_handler结构体用于描述输入事件的处理程序,结构中各元素功能如下:

  • 1、void *private;:指向驱动特殊数据的指针,通常用于存储与该输入处理程序相关的私有数据。
  • 2、void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);:指向事件处理函数的指针。当输入事件发生时,此函数将被调用以处理事件。
  • 3、void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);:指向批量事件处理函数的指针。类似于 event 函数,但允许处理多个输入值。
  • 4、bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);:指向事件过滤器函数的指针。该函数用于确定是否应该处理特定的输入事件。
  • 5、bool (*match)(struct input_handler *handler, struct input_dev *dev);:指向匹配函数的指针。用于确定输入设备是否与此输入处理程序匹配。
  • 6、int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);:指向连接函数的指针。用于将输入设备与输入处理程序连接起来。
  • 7、void (*disconnect)(struct input_handle *handle);:指向断开连接函数的指针。用于在输入设备断开连接时执行清理操作。
  • 8、void (*start)(struct input_handle *handle);:指向启动函数的指针。用于启动输入处理程序的某些操作。
  • 9、bool legacy_minors;:指示是否使用传统的设备编号(例如 /dev/input/eventX 中的 X)。
  • 10、int minor;:输入处理程序的次设备号,用于确定其在系统中的唯一标识符。
  • 11、const char *name;:输入处理程序的名称。
  • 12、const struct input_device_id *id_table;:指向输入设备标识符表的指针。用于描述输入设备与输入处理程序之间的匹配关系。
  • 13、struct list_head h_list;:用于将输入处理程序连接到输入设备的处理程序链表。
  • 14、struct list_head node;:用于将输入处理程序连接到全局输入处理程序链表。

总而言之,struct input_handler结构体描述了输入事件处理程序的各种属性和操作函数指针,用于与输入设备交互并处理输入事件。

三、input核心的初始化

input输入子系统的核心由/drivers/input/input.c文件实现,该文件中内容基于linux内核驱动模型而设计,模块出口是input_init(),由subsys_initcall(input_init)语句导出,如下代码:

static int __init input_init(void)
{
 int err;
  
  //注册input类
 err = class_register(&input_class);
 if (err) {
  pr_err("unable to register input_dev classn");
  return err;
 }
  
  //在proc文件系统中创建与input相关目录
 err = input_proc_init();
 if (err)
  goto fail1;
  
  //为input输入子系统注册主设备号
 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;
}

//模块导出
subsys_initcall(input_init);

input_proc_init() 定义如下:

static int __init input_proc_init(void)
{
 struct proc_dir_entry *entry;

 proc_bus_input_dir = proc_mkdir("bus/input", NULL);
 if (!proc_bus_input_dir)
  return -ENOMEM;

 entry = proc_create("devices", 0, proc_bus_input_dir,
       &input_devices_fileops);
 if (!entry)
  goto fail1;

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

上述代码将在 /proc/bus/input 目录下创建两个文件 “devices” 和 “handlers”,用于显示输入设备和输入事件处理程序的信息。例如:

图片

四、常用API

//申请input_dev结构变量
struct input_dev *input_allocate_device(void)

//注销input_dev设备
void input_free_device(struct input_dev *dev)

//向linux内核注册input设备
int input_register_device(struct input_dev *dev)

//从linux内核注销input设备
void input_unregister_device(struct input_dev *dev)

//上报一个输入事件,包括事件类型 (type)、事件代码 (code) 和事件值 (value)。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    
//上报一个相对坐标事件,即相对于上一个位置的变化。
void input_report_rel(struct input_dev *dev, unsigned int code, int value)

//上报一个绝对坐标事件,即绝对位置的变化。
void input_report_abs(struct input_dev *dev, unsigned int code, int value)

//上报一个力反馈状态事件,通常用于通知应用程序力反馈设备的状态。
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)

//上报一个开关状态事件,通常用于通知应用程序开关设备的状态。
void input_report_switch(struct input_dev *dev, unsigned int code, int value)

//上报一个多点触摸同步事件,用于通知输入子系统多点触摸操作的结束
void input_mt_sync(struct input_dev *dev)

//上报一个同步事件,用于通知linux内核input子系统上报操作结束
void input_sync(struct input_dev *dev)
/*##############################################################*/

五、输入设备驱动开发总结

(1)查看输入事件

很多时候,我们需要获取系统目前存在哪些输入事件,这时候可以使用:

ls /dev/input/

该目录导出的信息是输入事件对应的文件节点。

(2)查看输入设备信息

获取到输入事件,在开发中可能还不够,还想更进一步获取到更详细的关于输入事件和输入设备的信息,这时候可以使用:

cat /proc/bus/input/devices

将会输出如下格式的数据:

I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/platform/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="iMX6UL TouchScreen Controller"
P: Phys=
S: Sysfs=/devices/platform/soc/2000000.aips-bus/2040000.tsc/input/input1
U: Uniq=
H: Handlers=mouse0 event1 
B: PROP=0
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=3

(3)使用evtest查看输入事件

通过上述两种方式查看输入事件获取到的信息是关于具体的输入事件和输入设备,如果想要获取到输入事件更为详细的信息(数据层),则可以使用evtest工具进行查看。

在发行版的linux系统中,例如(ubuntu),可以使用如下命令:

sudo apt install evtest

安装evtest,在安装完evtest后,就可以使用了(必需以root运行):

sudo evtest

图片将会列出目前系统中可用的输入设备,我们可以选择一个对应的事件号进行监听,上图中可选的事件为0-4,对应五个输入设备。

在嵌入式linux中,可能就没有evtest工具的直接二进制文件,这时候我们可以自行编译源码得到。

  • 源码URL:https://github.com/freedesktop-unofficial-mirror/evtest/tree/master

图片例如上图,笔者自行编译了一个运行在ARM32上的evtest,下图为监测hid鼠标输入设备的一个应用场景:首先会打印出输入hid设备的相关信息,和支持的事件:

图片

在evtest运行的情况下,如果点击鼠标左键/右键、滑动鼠标滚轮和移动鼠标,evtest都会将监测到的事件打印出(下图为点击鼠标左键/右键输出的信息):

图片

(4)使用hexdump直接查看输入事件内容

如果需要获取到更为直接的输入设备而数据,则可以使用hexdump命令直接查看/dev/input/eventx:

hexdump /dev/input/event2

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/89777.html

Like (0)
guozi的头像guozi
Previous 2024年6月5日
Next 2024年6月5日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注