目录
一、什么是input子系统
linux中管理所有的输入类设备的体系。
1、何为输入设备
例如鼠标、触摸屏、按键、键盘等等。
2、linux中输入设备的编程模型
struct input_event(kernel/include/linux/input.h)
struct input_event {
struct timeval time;//时间(时刻)
__u16 type;//类型的编号(以键盘为例)
__u16 code;//编码值(那个按键)
__s32 value;//操作值(谈起还是按下)
};
用这个结构体描述一个输入类事件,例如按一下按键或者动一下鼠标就是一个事件
3、input子系统简介
(1)linux的input子系统解决了什么问题
将各种不同类型的输入设备、不同的寄存器、不同操作方法囊括起来
(2)input子系统分4个部分
应用层 + input event (生成一个struct input_event变量并从驱动层传输到应用层)+ input core(驱动框架) + 硬件驱动(驱动工程师编写的硬件操作部分)
(3)input子系统如何工作
以鼠标为例:在未有任何事件发生时,这部分代码处于静止状态,等待中断,鼠标按下触发中断,将事件上报到input core,将数据生成一个input event结构体变量,将这个传输到应用层(信息包括时间、鼠标、左键、按下)
(4)事件驱动型 GUI框架,如QT、VC等,输入类设备的事件是其的主要的动力来源,如按一下鼠标或者键盘。
二、input设备应用层编程实践
1、确定设备文件名
(1)应用层操作驱动有2条路:/dev目录下的设备文件,/sys目录下的属性文件
(2)input子系统用的/dev目录下的设备文件,具体一般都是在 /dev/input/eventn
(3)用cat命令来确认某个设备文件名对应哪个具体设备。有人ubuntu中实测的键盘是event1,而鼠标是event3.(我的键盘是event1,而鼠标是event2)
cat event0 :代表读取设备文件,未触发事件时,处于一个阻塞的状态(驱动设计的),若动鼠标或者键盘无反应,则表明event0不是二者中的一个。
2、标准接口打开并读取文件
int fd = -1;
fd = open(DEVICE, O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
3、读取struct input_event
struct input_event dev;
memset(&dev, 0, sizeof(struct input_event));
ret = read(fd, &dev, sizeof(struct input_event));
4、解析键盘或鼠标事件数据
printf("------------------------------------\n");
printf("type:%d\n", dev.type);
printf("code:%d\n", dev.code);
printf("value:%d\n", dev.value);
printf("\n");
/*kernel/include/linux/input.h
* Event types
*/
#define EV_SYN 0x00 //同步类型,若struct input_event 结构体中的type值为0,表示这个数据
是一个同步数据包,用于在应用层和驱动层数据同步不同输入设备的事件,在一次数据上报完成
后会发一个同步数据包,同步数据包之后就是下一个事件,两个同步数据包之间为一次事件。
#define EV_KEY 0x01//按键,键盘一定是按键,按键不一定是键盘
#define EV_REL 0x02//relative,相对的,对应鼠标的移动使用相对坐标
#define EV_ABS 0x03//absolute,绝对的,对应触摸屏(使用全局坐标,绝对坐标系)
#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#define DEVICE_KEY "/dev/input/event1"
#define DEVICE_MOUSE "/dev/input/event3"
int main(int argc, char *argv[])
{
int fd = -1, ret = -1;
struct input_event dev;
//第一步:打开设备文件
//fd = open(DEVICE_KEY, O_RDONLY);
fd = open(DEVICE_MOUSE, O_RDONLY);
if (fd < 0)
{
perror("open failure.");
return -1;
}
while(1)
{
//第二步:读取一个event事件包
memset(&dev, 0, sizeof(struct input_event));
ret = read(fd, &dev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read failure.");
break;
}
//第三步:解析event包,知晓发生了什么样的输入事件
printf("------------------------------------\n");
printf("type:%d\n", dev.type);
printf("code:%d\n", dev.code);
printf("value:%d\n", dev.value);
printf("\n");
}
//第四步:关闭设备文件
close(fd);
return 0;
}
程序执行结果:
操作键盘:
type:4//键盘
code:4//实验发现按键时不论哪个键,这个值一直是4
value:6 //代表按下的是哪个键
------------------------------------
type:1//按键
code:6
value:0
------------------------------------
type:0//同步数据包
code:0
value:0
------------------------------------
type:4
code:4
value:20
------------------------------------
type:1
code:20
value:0
------------------------------------
type:0
code:0
value:0
操作鼠标:
type:0
code:0
value:0
------------------------------------
type:1//按键,操作鼠标上的按键时
code:272
value:1//按下
------------------------------------
type:0
code:0
value:0
------------------------------------
type:1
code:272
value:0//弹起
------------------------------------
type:3//移动鼠标
code:0
value:39677
------------------------------------
type:0
code:0
value:0
------------------------------------
type:3
code:0//0代表REL_X水平方向移动,1代表REL_Y竖直方向,REL:相对坐标
value:39718
------------------------------------
type:0
code:0
value:0
三、input子系统架构总览
内核源码:drivers/input/下
参考学习:https://www.cnblogs.com/smartjourneys/p/6763400.html
1、input子系统分为三层
(1)最上层:输入事件驱动层,evdev.c和mousedev.c和joydev.c属于这一层
(2)中间层:输入核心层,input.c属于这一层
(3)最下层:输入设备驱动层,drivers/input/xxx 文件夹
gameport:游戏相关
joystick:游戏机摇杆
keyboard:键盘
misc:杂散类设备
mouse:鼠标
serio:串口类输入设备
tablet:手写板
touchscreen:触摸屏
最下层、最上层都调用了中间层实现的一些函数。事件驱动层,event接口逐渐取代了别的接口,作为一个通用的接口模型,可包括鼠标、键盘、触摸屏等各种不同的设备,使得这些设备有了两条路可以走,event接口对应evdev.c。事件驱动层向应用层提供struct input_event结构体。
输入事件驱动层和输入核心层属于驱动框架层,设备驱动层属于真正驱动实现的部分。
2、input类设备驱动开发方法
(1)输入事件驱动层和输入核心层不需要动,只需要编写设备驱动层
(2)设备驱动层编写的接口和调用模式已定义好,驱动工程师的核心工作量是对具体输入设备硬件的操作和性能调优。
(3)input子系统不算复杂,学习时要注意“标准模式”四个字。
四、输入核心层源码分析
kernel/drivers/input/input.c(从后向前看进行分析)
1、核心模块注册input_init
输入核心层被当做一个模块加载到内核中。
static int __init input_init(void)
{
int err;
input_init_abs_bypass();
err = class_register(&input_class);
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
(1)class_register
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
err = class_register(&input_class);
(2)input_proc_init
err = 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;
}
(3)register_chrdev:注册字符设备
输入类设备的主设备号都是13,不同的输入类设备通过次设备号进行区分。
#define INPUT_MAJOR 13//kernel/include/linux/major.h
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
2、设备驱动层的接口函数
input_allocate_device、input_set_capability、input_register_device这三个函数在驱动注册过程中依次执行。
(1)input_allocate_device:申请动态内存定义一个数据结构并初始化(初始化一部分,进行一些泛型的、数据结构相关的初始化)
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev;
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
if (dev) {
dev->dev.type = &input_dev_type;
dev->dev.class = &input_class;
device_initialize(&dev->dev);
mutex_init(&dev->mutex);
spin_lock_init(&dev->event_lock);
INIT_LIST_HEAD(&dev->h_list);
INIT_LIST_HEAD(&dev->node);
__module_get(THIS_MODULE);
}
return dev;
}
(2)input_set_capability:进一步进行初始化,进行设备相关的细节的初始化。调用该函数通过传参从而上报某个设备相关的功能、信息,相当于在内核驱动声明了某个设备的能力,以鼠标为例需上报多次:
input_set_capability(dev, EV_KEY, BTN_LEFT);
input_set_capability(dev, EV_KEY, BTN_RIGHT);
input_set_capability(dev, EV_KEY, REL_X);
input_set_capability(dev, EV_KEY, REL_Y);
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{ //这里的type,code就是struct input_event
switch (type) {
case EV_KEY:
__set_bit(code, dev->keybit);//code不同值,可表示鼠标的左键和右键
break;
case EV_REL:
__set_bit(code, dev->relbit);
break;
case EV_ABS://触摸屏所对应的
__set_bit(code, dev->absbit);
break;
case EV_MSC:
__set_bit(code, dev->mscbit);
break;
case EV_SW:
__set_bit(code, dev->swbit);
break;
case EV_LED:
__set_bit(code, dev->ledbit);
break;
case EV_SND:
__set_bit(code, dev->sndbit);
break;
case EV_FF:
__set_bit(code, dev->ffbit);
break;
case EV_PWR:
/* do nothing */
break;
default:
printk(KERN_ERR
"input_set_capability: unknown type %u (code %u)\n",
type, code);
dump_stack();
return;
}
__set_bit(type, dev->evbit);
}
(3)input_register_device:注册设备
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit);//EV_SYN是所有输入类设备都要有的,所以在
//input_register_device函数中统一实现了
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev);
/*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1);
error = device_add(&dev->dev);
if (error)
return error;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
}
list_add_tail(&dev->node, &input_dev_list);//添加到链表中
list_for_each_entry(handler, &input_handler_list, node)//遍历链表
input_attach_handler(dev, handler);//hander对应上边input子系统那个图(三、1)的Handlers那层,包括event,
//keyboard等,遍历这个链表与handler进行匹配,若与两个
//handler都匹配,则都建立连接,相当于有了两条途径
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
3、handler和device的匹配
kernel/drivers/input/input.c 774行
(1)input_attach_handler(dev, 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)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i;
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;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
只有当iput device和input handler的id成员在evbit, keybit,… swbit项相同才会匹//配成功。
而且匹配的顺序是从evbit, keybit到swbit.只要有一项不同,就会循环到id中的下一项进行比较。
#define MATCH_BIT(bit, max) \
for (i = 0; i < BITS_TO_LONGS(max); i++) \
if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
break; \
if (i != BITS_TO_LONGS(max)) \
continue;
简而言之,注册input device的过程就是为input device设置默认值,并将其挂以input_dev_list.与挂载在input_handler_list中的handler相匹配。如果匹配成功,就会调用handler的connect函数.
kernel/drivers/input/evdev.c
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices ,即可以和所有的设备匹配*/
{ }, /* Terminating zero entry */
};
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,//与匹配有关的
};
kernel/drivers/input/mousedev.c
static struct input_handler mousedev_handler = {
.event = mousedev_event,
.connect = mousedev_connect,
.disconnect = mousedev_disconnect,
.fops = &mousedev_fops,
.minor = MOUSEDEV_MINOR_BASE,
.name = "mousedev",
.id_table = mousedev_ids,
};
static const struct input_device_id mousedev_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT |
INPUT_DEVICE_ID_MATCH_RELBIT,
.evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
.keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
.relbit = { BIT_MASK(REL_X) | BIT_MASK(REL_Y) },
}, /* A mouse like device, at least one button,
two relative axes */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_RELBIT,
.evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
.relbit = { BIT_MASK(REL_WHEEL) },
}, /* A separate scrollwheel */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
.absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
}, /* A tablet like device, at least touch detection,
two absolute axes */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
.keybit = { [BIT_WORD(BTN_TOOL_FINGER)] =
BIT_MASK(BTN_TOOL_FINGER) },
.absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
BIT_MASK(ABS_PRESSURE) |
BIT_MASK(ABS_TOOL_WIDTH) },
}, /* A touchpad */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
.keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
.absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
}, /* Mouse-like device with absolute X and Y but ordinary
clicks, like hp ILO2 High Performance mouse */
{ }, /* Terminating entry */
};
鼠标这个设备对于evdev.c和mousedev.c这两条路都可以走。
4、事件驱动层的接口函数
(1)input_register_handler(input.c)
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);//将当前节点插入到链表中
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
static struct input_handler *input_table[8];表明内核最多支持8个input_handler,但根本不会超过八个
#define JOYDEV_MINOR_BASE 0
#define MOUSEDEV_MINOR_BASE 32
#define EVDEV_MINOR_BASE 64
这8个的.minor = MOUSEDEV_MINOR_BASE,分别是0,32,64…,所有输入类设备的主设备号都是13,一个鼠标可能有两个不同的次设备号,因为其可通过两条的途径实现,通过匹配建立了两条连接。
(2)input_register_handle
用来处理device和handler之间的关系,用于记录一些信息,比如一个设备和handler匹配上了,在这个结构体记录
kernel/include/linux/input.h
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
/*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
/*
* 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);
mutex_unlock(&dev->mutex);
/*
* 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;
}
五、输入事件驱动层源码分析(需自己花时间追踪分析代码,方可深入理解)
kernel/drivers/input/evdev.c
1、struct input_handler evdev_handler;
struct input_handler {
void *private;
//负责将底层硬件驱动层上报的硬件数据加工成一个struct input_event结构体,放到缓冲区备用
//供应用层读取
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
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);//handler和dev匹配上之后进行连接
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
const struct file_operations *fops;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
}
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,//与匹配有关的
};
2、evdev_connect
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error;
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;
if (minor == EVDEV_MINORS) {
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
dev_set_name(&evdev->dev, "event%d", minor);//所以生成的设备文件名为event+数字
evdev->exist = 1;
evdev->minor = minor;
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, EVDEV_MINOR_BASE + minor);//次设备号
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);//1
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);//2,1、2其实就是device_register的内部实现
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
3、evdev_event
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;
struct timespec ts;
ktime_get_ts(&ts);//获取内核的时间的
event.time.tv_sec = ts.tv_sec;
event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
event.type = type;
event.code = code;
event.value = value;
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);
rcu_read_unlock();
wake_up_interruptible(&evdev->wait);
}
该函数主要就是将硬件产生的信息封装成一个struct input_event结构体
六、输入设备驱动层源码分析
1、先找到bsp中按键驱动源码
(1)锁定目标:板载按键驱动
(2)确认厂家提供的BSP是否已经有驱动
(3)找到bsp中的驱动源码
cat /dev/input/event0
测试发现,event0代表的是按键
再去cat /sys/class/input/event0/device/name
得到按键的名字,一般情况这个名字都是驱动源码某个结构体的成员.name的值。
得到这个name去sourceinsight中搜索可大致确定按键是那个驱动文件,这个文件在哪。
定位到两个文件中:
kernel/arch/arm/mach-s5pv210/button-smdkv210.c
kernel/arch/arm/mach-s5pv210/button-x210.c
2、按键驱动源码初步分析
开发板按键驱动的实现与ubuntu不同,通过编译执行app_input.c发现开发板这块打印出的信息是没有同步数据包的。
(1)模块装载分析
kernel/arch/arm/mach-s5pv210/button-x210.c
static int __init s3c_button_init(void)
{
platform_device_register(&s3c_device_button);
return platform_driver_register(&s3c_button_device_driver);
}
static void __exit s3c_button_exit(void)
{
platform_driver_unregister(&s3c_button_device_driver);
platform_device_unregister(&s3c_device_button);
}
module_init(s3c_button_init);
module_exit(s3c_button_exit);
(2)平台总线相关分析
platform框架实现的是device与driver连接到一起,二者结合起来,而input子系统框架则是在操控硬件的同时向应用层上报信息,故而platform与input子系统二者是一个叠加的关系
static struct platform_device s3c_device_button = {
.name = "s3c-button",
.id = -1,
};
static struct platform_driver s3c_button_device_driver = {
.probe = s3c_button_probe,//初始化、安装设备驱动
.remove = s3c_button_remove,
.suspend = s3c_button_suspend,
.resume = s3c_button_resume,
.driver = {
.name = "s3c-button",
.owner = THIS_MODULE,
}
};
(3)确定重点:probe函数
kernel/arch/arm/mach-s5pv210/button-x210.c
static int s3c_button_probe(struct platform_device *pdev)
{
int i;
int ret;
#if defined(CONFIG_X210) && (CONFIG_X210 > 0)
/* gph0_1 (power) */
ret = gpio_request(S5PV210_GPH0(1), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(1) fail");
s3c_gpio_setpull(S5PV210_GPH0(1), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(1), S3C_GPIO_SFN(0));
s3c_button_history[0] = gpio_get_value(S5PV210_GPH0(1));
/* gph2_0 (up) */
ret = gpio_request(S5PV210_GPH2(0), "GPH2");
if(ret)
printk("button-x210: request gpio GPH2(0) fail");
s3c_gpio_setpull(S5PV210_GPH2(0), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH2(0), S3C_GPIO_SFN(0));
s3c_button_history[1] = gpio_get_value(S5PV210_GPH2(0));
/* gph0_3 (down) */
ret = gpio_request(S5PV210_GPH0(3), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(3) fail");
s3c_gpio_setpull(S5PV210_GPH0(3), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(3), S3C_GPIO_SFN(0));
s3c_button_history[2] = gpio_get_value(S5PV210_GPH0(3));
/* gph0_2 (left) */
ret = gpio_request(S5PV210_GPH0(2), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(2) fail");
s3c_gpio_setpull(S5PV210_GPH0(2), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0));
s3c_button_history[3] = gpio_get_value(S5PV210_GPH0(2));
/* gph2_1 (right) */
ret = gpio_request(S5PV210_GPH2(1), "GPH1");
if(ret)
printk("button-x210: request gpio GPH2(1) fail");
s3c_gpio_setpull(S5PV210_GPH2(1), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH2(1), S3C_GPIO_SFN(0));
s3c_button_history[4] = gpio_get_value(S5PV210_GPH2(1));
/* gph2_3 (menu) */
ret = gpio_request(S5PV210_GPH2(3), "GPH2");
if(ret)
printk("button-x210: request gpio GPH2(3) fail");
s3c_gpio_setpull(S5PV210_GPH2(3), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH2(3), S3C_GPIO_SFN(0));
s3c_button_history[5] = gpio_get_value(S5PV210_GPH2(3));
/* gph2_2 (back) */
ret = gpio_request(S5PV210_GPH2(2), "GPH2");
if(ret)
printk("button-x210: request gpio GPH2(2) fail");
s3c_gpio_setpull(S5PV210_GPH2(2), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH2(2), S3C_GPIO_SFN(0));
s3c_button_history[6] = gpio_get_value(S5PV210_GPH2(2));
#elif defined(CONFIG_I210) && (CONFIG_I210 > 0)
/* gph0_1 (power) */
ret = gpio_request(S5PV210_GPH0(1), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(1) fail");
s3c_gpio_setpull(S5PV210_GPH0(1), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(1), S3C_GPIO_SFN(0));
s3c_button_history[0] = gpio_get_value(S5PV210_GPH0(1));
/* gph0_4 (up) */
ret = gpio_request(S5PV210_GPH0(4), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(4) fail");
s3c_gpio_setpull(S5PV210_GPH0(4), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(4), S3C_GPIO_SFN(0));
s3c_button_history[1] = gpio_get_value(S5PV210_GPH0(4));
/* gph0_3 (down) */
ret = gpio_request(S5PV210_GPH0(3), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(3) fail");
s3c_gpio_setpull(S5PV210_GPH0(3), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(3), S3C_GPIO_SFN(0));
s3c_button_history[2] = gpio_get_value(S5PV210_GPH0(3));
/* gph0_2 (left) */
ret = gpio_request(S5PV210_GPH0(2), "GPH0");
if(ret)
printk("button-x210: request gpio GPH0(2) fail");
s3c_gpio_setpull(S5PV210_GPH0(2), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0));
s3c_button_history[3] = gpio_get_value(S5PV210_GPH0(2));
/* gph1_0 (right) */
ret = gpio_request(S5PV210_GPH1(0), "GPH1");
if(ret)
printk("button-x210: request gpio GPH1(0) fail");
s3c_gpio_setpull(S5PV210_GPH1(0), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH1(0), S3C_GPIO_SFN(0));
s3c_button_history[4] = gpio_get_value(S5PV210_GPH1(0));
/* gph1_3 (menu) */
ret = gpio_request(S5PV210_GPH1(3), "GPH1");
if(ret)
printk("button-x210: request gpio GPH1(3) fail");
s3c_gpio_setpull(S5PV210_GPH1(3), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH1(3), S3C_GPIO_SFN(0));
s3c_button_history[5] = gpio_get_value(S5PV210_GPH1(3));
/* gph1_1 (back) */
ret = gpio_request(S5PV210_GPH1(1), "GPH1");
if(ret)
printk("button-x210: request gpio GPH1(1) fail");
s3c_gpio_setpull(S5PV210_GPH1(1), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH1(1), S3C_GPIO_SFN(0));
s3c_button_history[6] = gpio_get_value(S5PV210_GPH1(1));
#endif
input = input_allocate_device();
if(!input)
return -ENOMEM;
set_bit(EV_KEY, input->evbit);
for(i = 0; i < MAX_BUTTON_CNT; i++)
set_bit(s3c_Keycode[i], input->keybit);
input->name = "s3c-button";
input->phys = "s3c-button/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
input->keycode = s3c_Keycode;
if(input_register_device(input) != 0)
{
printk("s3c-button input register device fail!!\n");
input_free_device(input);
return -ENODEV;
}
/* Scan timer init */
init_timer(&timer);
timer.function = s3cbutton_timer_handler;
timer.expires = jiffies + (HZ/100);//时刻,HZ/100:10ms
add_timer(&timer);//添加并启动定时器,开始自动计时
//使用定时器是为了消抖
printk("s3c button Initialized!!\n");
return 0;
}
3、s3c_button_probe源码细节实现分析
(1)gpio_request:申请到相关GPIO并设为输入模式
/* gph1_3 (menu) */
ret = gpio_request(S5PV210_GPH1(3), "GPH1");
if(ret)
printk("button-x210: request gpio GPH1(3) fail");
s3c_gpio_setpull(S5PV210_GPH1(3), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPH1(3), S3C_GPIO_SFN(0));
s3c_button_history[5] = gpio_get_value(S5PV210_GPH1(3));
(2)input_allocate_device:申请到内存,对static struct input_dev * input进行填充
static struct input_dev * input;//静态全局变量
input = input_allocate_device();
set_bit(EV_KEY, input->evbit);//当前驱动支持ev_key事件,被当作按键来处理
static int s3c_Keycode[MAX_BUTTON_CNT] = {KEY_POWER, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_A, KEY_B};
for(i = 0; i < MAX_BUTTON_CNT; i++)
set_bit(s3c_Keycode[i], input->keybit);
只有经过这个校验后才会上报信息
//kernel/include/linux/input.h
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
//位域,用1/0表示有无注册
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//该数组的一个元素
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//32个位,可描
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//述32个,从位域
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//可知晓该设备是否注册
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)];
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
unsigned int scancode, unsigned int keycode);
int (*getkeycode)(struct input_dev *dev,
unsigned int scancode, unsigned int *keycode);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int sync;
int abs[ABS_CNT];
int rep[REP_MAX + 1];
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)];
int absmax[ABS_CNT];
int absmin[ABS_CNT];
int absfuzz[ABS_CNT];
int absflat[ABS_CNT];
int absres[ABS_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 *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;
};
(3)input_register_device:填充完后进行注册
if(input_register_device(input) != 0)
{
printk("s3c-button input register device fail!!\n");
input_free_device(input);
return -ENODEV;
}
(4)timer:启动定时器
/* Scan timer init */
init_timer(&timer);
timer.function = s3cbutton_timer_handler;
timer.expires = jiffies + (HZ/100);//时刻,HZ/100:10ms
add_timer(&timer);//添加并启动定时器,开始自动计时
//使用定时器是为了轮询的周期,同时进行按键消抖
使用轮询查看按键。
kernel/arch/arm/mach-s5pv210/button-x210.c 60行
static void s3cbutton_timer_handler(unsigned long data)
{
flag = gpio_get_value(S5PV210_GPH0(1));
if(flag != s3c_button_history[0])//按键状态未发生改变时不去打印信息
{
if(flag)
{
input_report_key(input, s3c_Keycode[0], 0);
}
else
{
input_report_key(input, s3c_Keycode[0], 1);
}
s3c_button_history[0] = flag;
}
/* Kernel Timer restart */
mod_timer(&timer,jiffies + HZ/100);//再次开启定时器进行工作,从头开始计时,
计时时间:jiffies + HZ/100
}
//上报一个按键事件
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
add_input_randomness(type, code, value);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
七、中断方式按键驱动实战
1、模板
kernel/Documentation/input/input-programming.txt
(1)input类设备驱动模式非常固定,用参考模版修改即可
(2)新建驱动项目并粘贴模版内容
2、模板驱动的解析
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
unsigned int irq这个中断号并非数据手册的,而是内核里重新规定的,类似gpio的编号。(kernel/arch/arm/mach-s3c2410/include/mach/irqs.h)
typedef irqreturn_t (*irq_handler_t)(int, void *);//中断处理程序
共享中断:多个中断使用了同一个中断号。例如一个中断处理程序处理七个按键的中断,中断号相同,但不同的按键的中断处理程序不同。中断号可用于确认中断处理程序中的中断号与注册的是否相同,进行一个验证,避免出错。
unsigned long flags:标志位
部分:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001//上升沿
#define IRQF_TRIGGER_FALLING 0x00000002//下降沿
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
const char *name:申请的中断号的名字
void *dev:传递参数,若不需要,传一个NULL进去即可。
3、驱动移植细节
完整工程文件链接:https://www.aliyundrive.com/s/2fnPtGqKrJk
按键对应的GPIO口设为外部中断模式。
裸机中中断处理程序不需要返回,但操作系统中是需要返回的,操作系统需要对系统的软硬件情况做一个完全的掌控。
更多细节阅读参考模板文档!
4、驱动实践
cat /proc/interrupts:查看已注册的中断
配置内核,禁用它原有的按键驱动,把使用gpio资源释放出来,修改arch/arm/mach-s5pv210/Makefile
obj-$(CONFIG_MACH_SMDKV210) += button-x210.o
注释掉,不可把这个宏去掉,因为这个宏牵扯了很多的代码。
实验效果:
执行应用程序时按下按键left的实验现象
type:1
code:105
value:1
------------------------------------
type:0
code:0
value:0
------------------------------------
type:1
code:105
value:0
------------------------------------
type:0
code:0
value:0
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。