linux input system 分析笔记

1 struct input_dev 和 struct input_handler

1.1 简介

struct input_dev表示一个设备驱动层的输入设备。

struct input_handler是处理struct input_dev上报的事件的事件处理器。

1.2 全局变量input_dev_list,input_handler_list

输入设备链表:input_dev_list

事件处理器链表:input_handler_list

在input_register_device()函数里会将注册的struct input_dev结构体对象加入全局变量input_dev_list中。

在input_register_handler()函数里会将注册的struct input_handler结构体对象加入全局变量input_handler_list中。

1.3 struct input_dev 和 struct input_handler的匹配

1.3.1 匹配

input_register_device()函数会遍历事件处理器链表input_handler_list,根据id号查找匹配的struct input_handler。

input_register_handler()函数会遍历输入设备链表input_dev_list,根据id号查找匹配的struct input_handler。

一个输入设备可以关联多个事件处理器。

如果匹配成功,调用handler->connect();

1.3.2 不同struct input_handler的connect()函数

1.3.2.1 evdev_handler的evdev_connect();

evdev_connect()函数做以下工作:

  1. 调用input_register_handle()函数注册事件处理句柄struct input_handle。
  2. 注册字符设备文件/dev/input/eventX

evdev_handler会和所有的struct input_dev 对象匹配成功,所以有多少个struct input_dev 对象,/dev/input/目录下就会有多少个eventX设备文件。

1.3.2.2 mousedev_handler的mousedev_connect();

mousedev_connect()函数做以下工作:

  1. 调用input_register_handle()函数注册事件处理句柄struct input_handle。
  2. 注册字符设备文件/dev/input/mouseX
1.3.2.3 input_leds_handler的input_leds_connect();

input_leds_connect()函数做以下工作:

  1. 调用input_register_handle()函数注册事件处理句柄struct input_handle。
  2. 调用led_classdev_register()函数register a new object of LED class,会在/sys/class/leds/目录下生成"inputXXX"的目录。
1.3.2.4 其他struct input_handler

sysrq_handler

kbd_handler

1.4 查看系统下的struct input_dev 和 struct input_handler信息

1.4.1 /proc/bus/input/devices

信息如下:

bash 复制代码
I: Bus=0019 Vendor=0000 Product=0003 Version=0000                                                                                                       
N: Name="Sleep Button"
P: Phys=PNP0C0E/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0E:00/input/input0
U: Uniq=
H: Handlers=kbd event0 
B: PROP=0
B: EV=3
B: KEY=4000 0 0 

I: Bus=0019 Vendor=0000 Product=0005 Version=0000
N: Name="Lid Switch"
P: Phys=PNP0C0D/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0D:00/input/input1
U: Uniq=
H: Handlers=event1 
B: PROP=0
B: EV=21
B: SW=1

I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=PNP0C0C/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0C:00/input/input2
U: Uniq=
H: Handlers=kbd event2 
B: PROP=0
B: EV=3
B: KEY=10000000000000 0

I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input3
U: Uniq=
H: Handlers=kbd event3 
B: PROP=0
B: EV=3
B: KEY=10000000000000 0

I: Bus=0011 Vendor=0001 Product=0001 Version=ab83
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input4
U: Uniq=
H: Handlers=sysrq kbd event4 leds 
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7

I: Bus=0011 Vendor=0002 Product=000a Version=0063
N: Name="TPPS/2 Elan TrackPoint"
P: Phys=isa0060/serio1/input0
S: Sysfs=/devices/platform/i8042/serio1/input/input6
U: Uniq=
H: Handlers=mouse2 event7 
B: PROP=21
B: EV=7
B: KEY=70000 0 0 0 0
B: REL=3

I: Bus=0003 Vendor=413c Product=301a Version=0111
N: Name="PixArt Dell MS116 USB Optical Mouse"
P: Phys=usb-0000:00:14.0-7/input0
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.0/0003:413C:301A.0002/input/input10
U: Uniq=
H: Handlers=mouse3 event8 
B: PROP=0
B: EV=17
B: KEY=70000 0 0 0 0
B: REL=903
B: MSC=10

I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="Intel HID events"
P: Phys=
S: Sysfs=/devices/platform/INTC1051:00/input/input11
U: Uniq=
H: Handlers=rfkill kbd event9 
B: PROP=0
B: EV=13
B: KEY=81000300000000 5000004000 1e294000000020 0
B: MSC=10

......

1.4.2 /proc/bus/input/handlers

bash 复制代码
N: Number=0 Name=rfkill                                                                                                                                 
N: Number=1 Name=kbd
N: Number=2 Name=sysrq (filter)
N: Number=3 Name=mousedev Minor=32
N: Number=4 Name=evdev Minor=64
N: Number=5 Name=leds
N: Number=6 Name=joydev Minor=0

2 struct input_event(输入事件数据结构体)

2.1 struct input_event的定义(kernel-5.4.18)

一个输入事件包括时间戳、输入事件类型,事件编码(code)和事件值(value),具体信息如下:

cpp 复制代码
struct input_event {
#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__)
    struct timeval time;
#define input_event_sec time.tv_sec
#define input_event_usec time.tv_usec
#else
    __kernel_ulong_t __sec;
#if defined(__sparc__) && defined(__arch64__)
    unsigned int __usec;
#else
    __kernel_ulong_t __usec;
#endif
#define input_event_sec  __sec
#define input_event_usec __usec
#endif
    __u16 type;
    __u16 code;
    __s32 value;
};

2.2 Event types

cpp 复制代码
/*
 * Event types
 */

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

EV_SYN、EV_KEY、EV_REL 和 EV_ABS网上资料很多,本文就不过多介绍了,后面会重点介绍EV_SW 和 EV_LED。

2.3 事件类型------EW_SW(0x05)

2.3.1 简介

EV_SW events describe stateful binary switches. For example, the SW_LID code is

used to denote when a laptop lid is closed.

Documentation/input/event-codes.rst

2.3.2 支持的事件编码(code)

cpp 复制代码
/*
 * Switch events
 */

#define SW_LID          0x00  /* set = lid shut */
#define SW_TABLET_MODE      0x01  /* set = tablet mode */
#define SW_HEADPHONE_INSERT 0x02  /* set = inserted */
#define SW_RFKILL_ALL       0x03  /* rfkill master switch, type "any"
                     set = radio enabled */
#define SW_RADIO        SW_RFKILL_ALL   /* deprecated */
#define SW_MICROPHONE_INSERT    0x04  /* set = inserted */
#define SW_DOCK         0x05  /* set = plugged into dock */
#define SW_LINEOUT_INSERT   0x06  /* set = inserted */
#define SW_JACK_PHYSICAL_INSERT 0x07  /* set = mechanical switch set */
#define SW_VIDEOOUT_INSERT  0x08  /* set = inserted */
#define SW_CAMERA_LENS_COVER    0x09  /* set = lens covered */
#define SW_KEYPAD_SLIDE     0x0a  /* set = keypad slide out */
#define SW_FRONT_PROXIMITY  0x0b  /* set = front proximity sensor active */
#define SW_ROTATE_LOCK      0x0c  /* set = rotate locked/disabled */
#define SW_LINEIN_INSERT    0x0d  /* set = inserted */
#define SW_MUTE_DEVICE      0x0e  /* set = device disabled */
#define SW_PEN_INSERTED     0x0f  /* set = pen inserted */
#define SW_MAX          0x0f
#define SW_CNT          (SW_MAX+1)

2.3.3 事件编码------SW_LID

笔记本电脑的屏幕上盖在合上 或者 掀开时会触发待机 或者 唤醒等操作,在linux内核里,驱动通过SW_LID事件编码向内核报告屏幕上盖是被合上 还是 被掀开。

事件值为1,表示笔记本电脑屏幕上盖被合上;

事件值为0,表示笔记本电脑屏幕上盖被掀开;

2.3.4 事件编码------SW_HEADPHONE_INSERT、SW_LINEOUT_INSERT、SW_LINEIN_INSERT

在很多机器上都会有以下的音频接口,驱动里会通过事件编码SW_HEADPHONE_INSERT、SW_LINEOUT_INSERT、SW_LINEIN_INSERT 和 SW_MICROPHONE_INSERT来通知系统有无插头接入。

2.3.5 通过evtest命令获取笔记本电脑上耳机插孔的耳机插头接入事件

2.3.5.1 判断哪个/dev/input/eventX支持EV_SW事件类型中的SW_HEADPHONE_INSERT 和 SW_MICROPHONE_INSERT

在/proc/bus/input/devices文件内容中有以下信息。表示/dev/input/event14可以上报microphone是否接入,/dev/input/event15可以上报headphone是否接入。

bash 复制代码
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="sof-hda-dsp Mic"
P: Phys=ALSA
S: Sysfs=/devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input19
U: Uniq=
H: Handlers=event14 
B: PROP=0
B: EV=21
B: SW=10       //第4个bit为1,表示支持事件编码SW_MICROPHONE_INSERT

I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="sof-hda-dsp Headphone"
P: Phys=ALSA
S: Sysfs=/devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input20
U: Uniq=
H: Handlers=event15 
B: PROP=0
B: EV=21
B: SW=4        //第2个bit为1,表示支持事件编码SW_HEADPHONE_INSERT
2.3.5.2 通evtest抓取输入事件的数据
cpp 复制代码
# evtest /dev/input/event14
Input driver version is 1.0.1
Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0
Input device name: "sof-hda-dsp Mic"
Supported events:
  Event type 0 (EV_SYN)
  Event type 5 (EV_SW)
    Event code 4 (SW_MICROPHONE_INSERT) state 0
Properties:
Testing ... (interrupt to exit)
Event: time 1713156364.680074, type 5 (EV_SW), code 4 (SW_MICROPHONE_INSERT), value 1    //话筒插头接入
Event: time 1713156364.680074, -------------- SYN_REPORT ------------
Event: time 1713156369.338703, type 5 (EV_SW), code 4 (SW_MICROPHONE_INSERT), value 0    //拔掉话筒插头
Event: time 1713156369.338703, -------------- SYN_REPORT ------------
cpp 复制代码
# evtest /dev/input/event15
Input driver version is 1.0.1
Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0
Input device name: "sof-hda-dsp Headphone"
Supported events:
  Event type 0 (EV_SYN)
  Event type 5 (EV_SW)
    Event code 2 (SW_HEADPHONE_INSERT) state 0
Properties:
Testing ... (interrupt to exit)
Event: time 1713156221.813617, type 5 (EV_SW), code 2 (SW_HEADPHONE_INSERT), value 1    //耳机插头接入
Event: time 1713156221.813617, -------------- SYN_REPORT ------------
Event: time 1713156229.416541, type 5 (EV_SW), code 2 (SW_HEADPHONE_INSERT), value 0    //拔掉耳机插头
Event: time 1713156229.416541, -------------- SYN_REPORT ------------

2.4 事件类型------EV_LED

2.4.1 支持的事件编码(code)

cpp 复制代码
/*
 * LEDs
 */

#define LED_NUML        0x00
#define LED_CAPSL       0x01
#define LED_SCROLLL     0x02
#define LED_COMPOSE     0x03
#define LED_KANA        0x04
#define LED_SLEEP       0x05
#define LED_SUSPEND     0x06
#define LED_MUTE        0x07
#define LED_MISC        0x08
#define LED_MAIL        0x09
#define LED_CHARGING        0x0a
#define LED_MAX         0x0f
#define LED_CNT         (LED_MAX+1)

2.4.2 LED_NUML、LED_CAPSL 和 LED_SCROLLL

2.4.2.1 简介

普通键盘上有下面的3个LED灯

可以通过LED_NUML、LED_CAPSL 和 LED_SCROLLL 这几个事件编码来控制。

2.4.2.2 /sys/class/leds/

上面的"1.3.2.3"小节中描述了,led_classdev_register()函数中会register a new object of LED class,会在/sys/class/leds/目录下生成"inputXXX"的目录,如下:

cpp 复制代码
/sys/class/leds/input4::capslock
/sys/class/leds/input4::numlock
/sys/class/leds/input4::scrolllock

每个目录下都有一个brightness文件,可以通过这个文件来控制LED灯的亮 和 灭。

bash 复制代码
echo 0 > /sys/class/leds/input4\:\:capslock/brightness    //熄灭键盘上的capslock灯
echo 1 > /sys/class/leds/input4\:\:capslock/brightness    //点亮键盘上的capslock灯

2.5 事件类型------EV_SND

蜂鸣器可以使用这个事件类型

3 输入事件的报告流程

3.1 简介

绝大多数的输入事件是通过驱动程序里调用input_event()函数来上报的,另外一种方法是应用程序通过/dev/input/eventX文件接口(evdev_write())来上报输入事件。

3.2 应用程序通过evdev_write()模拟输⼊事件

3.2.1 模拟输入SW_LID事件,让系统进入休眠状态

cpp 复制代码
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/input.h>

int main()
{
    int fd, i, ret;
    struct input_event event;
    if((fd = open("/dev/input/event1", O_RDWR)) < 0){ 
        return 1;
    }   

    event.type = EV_SW;
    event.code = SW_LID;
    event.value = 1;
    ret = write(fd, &event, sizeof(struct input_event));

    event.type = EV_SYN;
    event.code = SYN_REPORT;
    event.value = 0;
    ret = write(fd, &event, sizeof(struct input_event));

    close(fd);

    return 0;
}

3.2.2 模拟输入LED_CAPSL事件,让键盘上的capslock灯闪烁

cpp 复制代码
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/input.h>

int main()
{
    int fd, i, ret;
    struct input_event event;
    if((fd = open("/dev/input/event19", O_RDWR)) < 0){ 
        return 1;
    }   

    for( i=0; i <= 7; i++){
        event.type = EV_LED;
        event.code = LED_CAPSL;
        event.value = 1;
        ret = write(fd, &event, sizeof(struct input_event));

        event.type = EV_SYN;
        event.code = SYN_REPORT;
        event.value = 0;
        ret = write(fd, &event, sizeof(struct input_event));
        sleep(1);

        event.type = EV_LED;
        event.code = LED_CAPSL;
        event.value = 0;
        ret = write(fd, &event, sizeof(struct input_event));

        event.type = EV_SYN;
        event.code = SYN_REPORT;
        event.value = 0;
        ret = write(fd, &event, sizeof(struct input_event));

        sleep(1);
    }   

    close(fd);
    return 0;
}

4 扩展知识

4.1 ACPI中的相关设备

PNP0C0D: Lid Device

PNP0C0E: Sleep Button Device

PNP0C0C: Power Button Device

4.2 相关命令

evtest

xinput

相关推荐
Dontla22 分钟前
Rust泛型系统类型推导原理(Rust类型推导、泛型类型推导、泛型推导)为什么在某些情况必须手动添加泛型特征约束?(泛型trait约束)
开发语言·算法·rust
Ttang2328 分钟前
Leetcode:118. 杨辉三角——Java数学法求解
算法·leetcode
喜欢打篮球的普通人29 分钟前
rust模式和匹配
java·算法·rust
java小吕布43 分钟前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
杜若南星1 小时前
保研考研机试攻略(满分篇):第二章——满分之路上(1)
数据结构·c++·经验分享·笔记·考研·算法·贪心算法
路遇晚风1 小时前
力扣=Mysql-3322- 英超积分榜排名 III(中等)
mysql·算法·leetcode·职场和发展
Neophyte06081 小时前
C++算法练习-day40——617.合并二叉树
开发语言·c++·算法
木向1 小时前
leetcode104:二叉树的最大深度
算法·leetcode
一个不喜欢and不会代码的码农1 小时前
力扣113:路径总和II
算法·leetcode