我是靠谱客的博主 酷酷金针菇,这篇文章主要介绍input输入子系统什么是输入子系统 输入子系统基本框架编写一个简单的按键输入子系统驱动框架  驱动开发用到的结构体和函数代码分析:,现在分享给大家,希望可以做个参考。

什么是输入子系统

linux 系统中按键、 键盘、 鼠标、 触摸屏等 输入设备 的支持越倾向于用内核的 input 输入子系统来完成(linux  中的事件输入驱动都被集成到了  input  子系统中)。 包括  android  设备中事件设备( 游戏杆、 键盘鼠标等, 采用事件机制实现, 典型虚拟键盘就是事件设备)简单讲就是 针对输入设备的驱动框架。
按产生的数据的类型可分为:
按键数据,例如:字母 A 键表示按键事件 键码 ‘A’ 键值 97/ 按下/松开;
绝对坐标数据, 例如:触摸屏操作点 x 坐标和  y 坐标压力值;
相对坐标数据, 例如鼠标操作坐标值;
作用: input 子系统完成文件操作接口, 写驱动 只做 input 接口留出来的部分, 很好的实现分层目的。 也就是能更高效的开发输入设备的驱动。
好处: 为输入设备驱动定义了一个标准的编程方式( 框架),将用户空间到内核空间的数据格式统一起来。

输入子系统基本框架

三层架构
事件处理层 :drivers/input/evdev.c
核心层: drivers/input/input.c
ubuntu@xubuntu:~/share/rk3399/kernel/drivers$ cd input
ubuntu@xubuntu:~/share/rk3399/kernel/drivers/input$ ls evdev.*
evdev.c evdev.o # 编译过了
ubuntu@xubuntu:~/share/rk3399/kernel/drivers/input$ ls input.*
input.c input.o # 编译过了
ubuntu@xubuntu:~/share/rk3399/kernel/drivers/input$

设备驱动层:自己写驱动。

编写一个简单的按键输入子系统驱动框架

 1.确保 input 子系统中的 handler 层和 core 层的源代码被编译进了内核镜像中。

make menuconfig
        Device Drivers --->
                Input device support --->         //核心层: drivers/input/input.c
                        <*> Event interface         // handler 层: drivers/input/evdev.c

 ubuntu@xubuntu:~/share/rk3399/kernel/drivers/input$ ls input*

        input.c input-compat.h input-core.o input-leds.o input-mt.o input-polldev.c
        input-compat.c input-compat.o input-leds.c input-mt.c input.o input-polldev.o
ubuntu@xubuntu:~/share/rk3399/kernel/drivers/input$ ls evdev.*
        evdev.c evdev.o

默认已编译进内核了

2.编写驱动程序 

1). 声明一个输入设备对象;
2). 分配一个 input device 对象的空间;
3). 初始化 input device 对象;
4). 注册 input device 对象到系统中;
5). 硬件的初始化, 申请中断;
6). 实现中断处理函数, 上报数据 ( );
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// button_input_drv.c /* pwr_button { compatible = "rk3399,power-button"; gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; interrupt-parent = <&gpio0>;//对应的上级中断控制器是 gpio0 interrupts = <5 IRQ_TYPE_EDGE_BOTH>; //中断号 触发方式 GPIO0 A5 }; */ #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> // of #include <linux/of_gpio.h> //gpio #include <linux/gpio.h> #include <dt-bindings/gpio/gpio.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/input.h> static int gpio_button;//引脚号 static int irqno;//中断号 //1.声明一个输入设备对象 static struct input_dev *button_dev; static irqreturn_t button_handler(int irq, void *dev) { int value; value = gpio_get_value(gpio_button); if(value) { input_report_key(button_dev, KEY_A, 0);//上报 键码 松开 } else { input_report_key(button_dev,KEY_A, 1);//上报 键码 按下 } input_sync(button_dev); //上报完毕 printk("call %s() @ %dn ", __func__, __LINE__); return IRQ_HANDLED; } //当驱动和设备树节点匹配时调用 参数pdev就是设备层定义的pdev对象 static int button_drv_probe(struct platform_device *pdev) { int ret; enum of_gpio_flags flags; //获得匹配的device_node对象 struct device_node *button_ctrl_node = pdev->dev.of_node; //2.分配一个 input_device 对象的空间 button_dev = input_allocate_device(); if (!button_dev) { printk("input_allocate_device failn"); return -ENOMEM; } //3.初始化 input device 对象 //set_bit(EV_KEY, button_dev->evbit); //设置输入设备产生的事件类型:EV_KEY 按键事件 button_dev->evbit[0]=BIT_MASK(EV_KEY); //支持按键 //set_bit(KEY_A, button_dev->keybit); //设置输入设备产生的按键键码:'A' button_dev->keybit[BIT_WORD(KEY_A)] = BIT_MASK(KEY_A); // 键 码A //设置调试信息 button_dev->name = "xyd power key button"; button_dev->phys = "rk3399 power key"; button_dev->id.vendor= 0x20; button_dev->id.product = 48; button_dev->id.version =0x100; //4.注册 input device 对象到系统中 ret = input_register_device(button_dev); if(ret) { printk("input_register_device failn"); goto err_input_register_device; } //5.硬件的初始化,申请中断 //获取引脚号,有效电平 gpio_button = of_get_named_gpio_flags(button_ctrl_node, "gpios", 0, &flags); if (gpio_is_valid(gpio_button)) { //引脚号验证是否有效 printk("gpio_button=%dn", gpio_button); } else { printk("of_get_named_gpio_flags failn"); ret= -EFAULT; goto err_request_irq; } //获取中断号 irqno = gpio_to_irq(gpio_button); //申请中断 ret = request_irq(irqno, button_handler,IRQ_TYPE_EDGE_BOTH , "power_key_irq", NULL); if (ret) { printk("request_irq failn"); goto err_request_irq; } printk("call %s() @ %dn ", __FUNCTION__, __LINE__); return 0; err_request_irq: input_unregister_device(button_dev); err_input_register_device: input_free_device(button_dev); return ret; } //驱动和设备分离时调用 static int button_drv_remove(struct platform_device *pdev) { free_irq(irqno, NULL); input_unregister_device(button_dev); input_free_device(button_dev); printk("<kernel> call %s()n", __func__); return 0; } static const struct platform_device_id button_platform_ids[] = { { .name = "rk3288_button", .driver_data = 0x1, },{ .name = "rk3399_button", .driver_data = 0x2, } }; static const struct of_device_id button_ctrl_match_table[] = { { .compatible = "rk3399,power-button", }, //非常关键 {} }; static struct platform_driver rk_button_driver = { .probe = button_drv_probe, .remove = button_drv_remove, .driver = { .name = "rk_button", //驱动名字 .of_match_table = button_ctrl_match_table, }, .id_table = button_platform_ids,//驱动可操作的设备 }; static int __init driver_mod_init(void) { printk("<kernel> call %s()n", __func__); return platform_driver_register(&rk_button_driver); } static void __exit driver_mod_exit(void) { printk("<kernel> call %s()n", __func__); platform_driver_unregister(&rk_button_driver); } module_init(driver_mod_init); module_exit(driver_mod_exit); MODULE_LICENSE("GPL");
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// button_input_test.c /* 用来测试button_drv驱动的应用程序 设备文件名: /dev/input/event5 读取power key的键值 键码: A B 键值: 按下、松开 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/ioctl.h> #include <linux/input.h> #include <errno.h> struct input_event ev_button; //创建接收用的输入事件对象 int main(int argc, char **argv) { int fd; int on=0; int ret; if(argc!=2) { printf("usage: %s /dev/input/eventx n",argv[0]); exit(1); } fd = open(argv[1], O_RDWR); if(fd < 0) { perror("open"); exit(1); } #if 1 while (1) { bzero(&ev_button, sizeof(ev_button)); ret = read(fd, &ev_button, sizeof(ev_button)); if(ret<0) { perror("read"); exit(1); } if (ev_button.type == EV_KEY) { //判断事件类型 if (ev_button.code == KEY_A) { if(ev_button.value) { printf("power key pressedn"); } else { printf("power key releasen"); } } } } #endif close(fd); return 0; }
查看内核文档 :
ron@ubuntu:~/work/kernel-rockchip-nanopi4-linux-v4.4.y/Documentation/input$ vi input-programming.txt
3.运行测试
root@SOM-RK3399v2:/drv_module# ls
button_input_drv.ko button_input_test
root@SOM-RK3399v2:/drv_module# insmod button_input_drv.ko
[ 1276.300800] input: Unspecified device as /devices/virtual/input/input7
[ 1276.302922] <ron> call button_drvier_init()
[rootrk3399:/drv_code]#ls
button_drv.ko button_test
[root@rk3399:/drv_code]# ./button_test/dev/input/event3
^C # 无结果 , 没找对设备节点 , 查看
[root@rk3399:/drv_code]# cat /proc/bus/input/devices
I: Bus=0000 Vendor=1234 Product=5678 Version=0011
N: Name="xskj input button"
P: Phys=gpio-key
S: Sysfs=/devices/virtual/input/input0
U: Uniq=
H: Handlers=kbd event5
B: PROP=0
B: EV=3
B: KEY=8000000000 0
I:Bus=0019Vendor=0001Product=0001Version=0100
N:Name="ff420030.pwm"
P:Phys=gpio-keys/remotectl
S:Sysfs=/devices/platform/ff420030.pwm/input/input1
U:Uniq=
H:Handlers=kbdevent1
root@SOM-RK3399v2:/drv_module# ls /dev/input/event5 -al # 自动创建设备节点
crw-rw---- 1 root input 13, 69 May 28 01:38 /dev/input/event5
root@SOM-RK3399v2:/drv_module# ./button_input_test
open: Bad address
root@SOM-RK3399v2:/drv_module# ./button_input_test /dev/input/event5
[ 1325.655662] <robin> call button_handler()
power key pressed

 驱动开发用到的结构体和函数

 1.struct input_dev 结构体

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct input_dev ----->struct input_dev 用来描述一个输入设备 { const char *name; /*设备名字*/ const char *phys; struct input_id id; /*用来匹配事件处理层*/ unsigned long evbit[NBITS(EV_MAX)]; /*表示能产生哪类事件*/ unsigned long keybit[NBITS(KEY_MAX)]; /*表示能产生哪些按键事件*/ unsigned long relbit[NBITS(REL_MAX)]; /*表示能产生哪些相对位移事件,一般鼠标不采用绝对坐标系统*/ unsigned long absbit[NBITS(ABS_MAX)]; /*表示能产生哪些绝对位移事件,用于摇杆,数字仪*/ unsigned long mscbit[NBITS(MSC_MAX)]; /*其他事件*/ unsigned long ledbit[NBITS(LED_MAX)]; /*LEDs 事件*/ unsigned long sndbit[NBITS(SND_MAX)]; /*音频输入事件*/ unsigned long ffbit[NBITS(FF_MAX)]; /*Force feedback 事件*/ unsigned long swbit[NBITS(SW_MAX)]; unsigned int keycodemax; /*支持按键值得个数*/ unsigned int keycodesize; /*每个键值的字节数*/ void *keycode; /*存储按键值的数组首地址*/ /*修改键值的函数,可选*/ int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); /*获取扫描码的键值,可选*/ int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); unsigned int repeat_key; /*最近一次按键值,用于连击*/ struct timer_list timer; /*自动连击计时器*/ int sync; /*最后一次同步后没有新的事件置 1*/ int abs[ABS_MAX + 1]; /*当前各个坐标的值*/ int rep[REP_MAX + 1]; /*自动连击的参数*/ struct list_head h_list; /*handle 链表*/ struct list_head node; /*input_dev 链表*/ .... .... }
例如:
复制代码
1
2
3
4
5
6
7
8
unsigned long evbit[NBITS(EV_MAX)]; // 能产生哪类事件 每一种类型的事件都 在 input_dev.evbit 中用一个位来表示,构成 //一个位图,某位为 1 表示该输入设备支持这类事件,否则不支持. 可查看 EV_MAX 宏可知 支持 10 多种类型 unsigned long keybit[NBITS(KEY_MAX)]; // 能产生哪些按键 unsigned long relbit[NBITS(REL_MAX)]; // 能产生哪些相对位移: x,y,z unsigned long absbit[NBITS(ABS_MAX)]; // 能产生哪些绝对位移: x,y,z buttons_dev->evbit[0] |= BIT(EV_KEY); /* 能产生按键类事件 */
2. struct input_dev 操作函数
复制代码
1
2
3
4
struct input_dev *input_allocate_device(void) 分配输入设备 void input_free_device(struct input_dev *dev) 释放输入设备 int input_register_device(struct input_dev *dev) 注册输入设备,返回值为 0 成功 void input_unregister_device(struct input_dev *dev) 注销输入设备
3. struct input_event 结构体
实现输入设备 (input_dev) 驱动 : 向系统报告按键、触摸屏等输入事件( event ,通过
input_event 结构描述),不再需要关心文件操作接口 , 驱动报告事件经过核心层和事件处理层
到达用户空间。
struct input_event 是事件传送的载体,输入子系统的事件都是包装成 struct input_event
传给用户空间。 各个成员如下所示: /* include/linux/input.h */
struct input_event {
        struct timeval time; //时间戳
        __u16 type; //事件类型
        __u16 code; //事件代码
        __s32 value; //事件值,如坐标的偏移值 如果是按键 0 松开 ,1 按下
};
4. input_event 上报事件函数
        input_report_key(rk3399_input, KEY_UP, 0); //上报事件
        input_sync(rk3399_input); //同步才真正上报
        void input_sync(struct input_dev *dev);
        /*报告同步事件 ,input_sync() 用于高速 input core 此次报告已经结束,能够根据上报
的信息往后面处理了 */
具体说明 :
        void input_event(struct input_dev *dev,unsigned int type,unsigned int
code,int value);
// 报告指定 type,code,value 的输入事件
其他变种
        void input_report_key(struct input_dev *dev,unsigned int code,int value);
5. 设置事件函数
能产生哪些事件类型 , 事件代码 , 事件值
如何设置 input_dev 中的事件:
以下是使用 set_bit() 的范例:
        set_bit (EV_KEY,button_dev.evbit)
        //Set_bit 告诉 input 子系统它支持哪些事件
        //Struct input_dev 中有两个成员,一个是 evbit ;一个是 keybit ;分别用来表示设备
所支持的事件类型和按键类型。
        unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//表示一个位图 long 类型
事件类型 :
Linux 中输入设备的事件类型有 ( 这里只列出了常用的一些,更多请看 linux/input.h )
EV_SYN 0x00 同步事件 默认所有输入设备都支持
EV_KEY 0x01 按键事件 如键盘或按钮
EV_REL 0x02 相对坐标 如鼠标
EV_ABS 0x03 绝对坐标 如鼠标
EV_MSC 0x04 其它
EV_LED 0x11 LED 或 指示设备
EV_SND 0x12 声音
EV_REP 0x14 Repeat 重复类
EV_FF 0x15 Force feedback 力反馈事件
事件代码 :
当事件类型为 EV_KEY 时,还需指明按键代码:
BTN_LEFT 鼠标左键
BTN_RIGHT 鼠标右键
BTN_MIDDLE 鼠标中键
BTN_0 数字 0
BTN_1 数字 1
上述 set_bit 函数实则完成了把 EV_KEY 赋值到 button_dev.evbit
使用 BIT 巨集宏也可以:
例如位图宏
        button_dev->evbit[0]=BIT_MASK(EV_KEY); //支持按键
例如 :
        //初始化 input device 对象
        set_bit(EV_KEY,rk3399_input->evbit);
        //设置事件类型为 type=EV_KEY 放到 evbit 位图数组中
        set_bit(KEY_UP,rk3399_input->keybit);
        //设置按键事件的键码 , 放到 keybit 位图数组中
使用位图宏
        rk3399_input->evbit[0]=BIT_MASK(EV_KEY);//事件类型 EV_KEY
        rk3399_input->keybit[BIT_WORD(KEY_DOWN)] = BIT_MASK(KEY_DOWN); // 键 码
        KEY_DOWN

代码分析:


原则
1.两条主线
2.见名之意  函数名  宏名 猜是什么功能
3.搜索引擎

===================================
1.看分层

1.1看自己写的驱动层

ret = input_register_device(button_dev);
    |
    |
    list_add_tail(&dev->node, &input_dev_list);//输入设备dev加入链表input_dev_list
    list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);//搜索input_handler_list链表,每搜索一个handler
        //调用input_attach_handler
            |
            |
            id = input_match_device(handler, dev);//输入设备dev和handler事件处理器匹配
            error = handler->connect(handler, dev, id);//匹配成功调用connect函数指针
            //谁来定义这个函数???
            
1.2看事件处理层 evdev.c

input_register_handler(&evdev_handler);
    |
    |
    list_add_tail(&handler->node, &input_handler_list);//handler事件处理对象加入链表input_handler_list
    list_for_each_entry(dev, &input_dev_list, node)//搜索input_dev_list链表,每搜索一个dev
        input_attach_handler(dev, handler);
            |
            |
            id = input_match_device(handler, dev);//输入设备dev和handler事件处理器匹配
            error = handler->connect(handler, dev, id);//匹配成功调用connect函数指针
            
            
小结:
dev和handler匹配最终会调用connect函数
这个connect函数的定义是在evdev.c evdev_connect函数中实现


//evdev_handler对象是evdev.c创建的
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,
};

//本地结构体  
struct evdev {
    int open;
    struct input_handle handle;//保存 dev和handler相关信息
    wait_queue_head_t wait;
    ...
    struct list_head client_list;
    ...
    struct device dev;
    struct cdev cdev;    
};


.connect    = evdev_connect,
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
        |
        |
        minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);//申请次设备号
        //从64开始分配 最多32个 minor=0----31
        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
        init_waitqueue_head(&evdev->wait); //初始化等待队列
        dev_no = minor;
        dev_set_name(&evdev->dev, "event%d", dev_no); //设备文件名  eventx
        
        //初始化handle对象
        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);
        
        //初始化handle对象
        error = input_register_handle(&evdev->handle);
        
        //字符设备驱动注册
        cdev_init(&evdev->cdev, &evdev_fops);
        error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
        // 创建设备文件
        error = device_add(&evdev->dev);
        
        /*
        参考
        device_create
            device_initialize(dev)
            retval = device_add(dev);
        */
        
1.3看核心层  input.c
 __init input_init(void)
    |
    |
    err = class_register(&input_class);  // sys/class
    err = input_proc_init();  //cat /proc/bus/input/devices
    
    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
                     INPUT_MAX_CHAR_DEVICES, "input");//申请主设备号
                     

2.应用和驱动调用关系
应用===》驱动

open(argv[1], O_RDWR)
    |
    |
    sys_open
        |
        |
        evdev_open
            |
            |
            struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);//注册cdev找到本地对象evdev
            struct evdev_client *client;
            client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
            client->evdev = evdev;//保存evdev到client对象中
            file->private_data = client;//将client保存私有数据对象中
            

static const struct file_operations evdev_fops = {
    .owner        = THIS_MODULE,
    .read        = evdev_read,
    .write        = evdev_write,
    .poll        = evdev_poll,
    .open        = evdev_open,
    .release    = evdev_release,
    .unlocked_ioctl    = evdev_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl    = evdev_ioctl_compat,
#endif
    .fasync        = evdev_fasync,
    .flush        = evdev_flush,
    .llseek        = no_llseek,
};

//读输入设备的函数
static ssize_t evdev_read(struct file *file, char __user *buffer,
              size_t count, loff_t *ppos)
    |
    |
    struct evdev_client *client = file->private_data;//读私有数据的client对象
    struct evdev *evdev = client->evdev;
    input_event_to_user(buffer + read, &event); //拷贝数据到用户空间
    if (!(file->f_flags & O_NONBLOCK)) {
        error = wait_event_interruptible(evdev->wait, //休眠
                client->packet_head != client->tail ||
                !evdev->exist || client->revoked);
        if (error)
            return error;
    }
    
    

//硬件产生中断

input_report_key(button_dev, KEY_A, 0);//上报 键码 松开
    |
    |
    input_event
        |
        |
        if (handler->events)
            handler->events(handle, vals, count);
        else if (handler->event)
            for (v = vals; v != vals + count; v++)
                handler->event(handle, v->type, v->code, v->value);


实际上是调用
static struct input_handler evdev_handler = {
    .event        = evdev_event,
    .events        = evdev_events,        

}
    |
    |
    if (wakeup)
        wake_up_interruptible(&evdev->wait);

最后

以上就是酷酷金针菇最近收集整理的关于input输入子系统什么是输入子系统 输入子系统基本框架编写一个简单的按键输入子系统驱动框架  驱动开发用到的结构体和函数代码分析:的全部内容,更多相关input输入子系统什么是输入子系统内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(50)

评论列表共有 0 条评论

立即
投稿
返回
顶部