Linux设备驱动剖析之Input(一)
前言
以前在移植Qt到开发板上时只知道在配置文件中需要指定触摸屏的设备文件/dev/input/event0,仅此而已。直到一年半前突然想到用红外遥控器控制Tiny6410开发板上的Android系统,从而代替物理按键。实现原理是很简单的,就是首先解码红外信号,然后根据解码出的键值模拟一个按键信号。既然要模拟按键信号,那得首先找到按键信号产生的地方,通过查看内核编译生成的文件知道drivers/input/keyboard/gpio_keys.c文件是产生按键信号的源头,这是一个通用的用IO口模拟键盘的驱动程序。别小看这样一个功能,这是开发Android机顶盒、Android盒子必须要接触到的。
虽说当时功能是实现了,但是对Linux的整个Input子系统的了解一点都不深入,本文就是来解决这个问题的。基于Linux-2.6.36版本,本文讲解的Input子系统的主线是这样的:和前面讲SPI、IIC子系统的方法类似,先讲Input核心的初始化,再从底层往上,分别是Input设备驱动程序(以drivers/input/keyboard/gpio_keys.c为例)、Input核心、Input事件驱动程序(以drivers/input/evdev.c为例)。按照输入信号的产生以及在内核中的传递过程,最后到应用程序这条线路,从而深入理解Linux的Input子系统。
先给出Linux Input子系统的架构图,如下图所示。
Linux Input子系统架构图
下面开始进入Input子系统的学习。
首先找到Input子系统的初始化函数,它是位于drivers/input/input.c中的input_init函数:
static int __init input_init(void)
{
int err; 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 ; fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
2056行,向系统注册input_class这么一个类,以后向Input子系统注册设备时,所注册的设备都会从属于这个类。
2062行,proc文件系统相关的,不细讲,但可以看下它的定义:
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", , proc_bus_input_dir,
&input_devices_fileops);
if (!entry)
goto fail1; entry = proc_create("handlers", , proc_bus_input_dir,
&input_handlers_fileops);
if (!entry)
goto fail2; return ; fail2: remove_proc_entry("devices", proc_bus_input_dir);
fail1: remove_proc_entry("bus/input", NULL);
return -ENOMEM;
}
1114行,很明显,在/proc下创建一个目录bus/input。
1118行,在/proc/bus/input目录下创建proc文件,文件名为devices。
1123行,在/proc/bus/input目录下创建proc文件,文件名为handlers。
回到input_init函数,2066行,注册字符设备,主设备号为INPUT_MAJOR,它的值为13,该设备的文件操集作实例为input_fops,它的定义为:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
可以看到,里面只将open函数指针指向input_open_file函数,这个函数放到最后再说。
input_init函数说完了,下面进入Input设备驱动程序,也就是Input子系统的最底层部分。首先看drivers/input/keyboard/gpio_keys.c驱动程序的初始化函数gpio_keys_init的定义:
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
这是一个平台驱动,关于平台设备和平台驱动绑定过程应该都很了解了吧,634行,platform_driver_register函数参数gpio_keys_device_driver的定义:
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = __devexit_p(gpio_keys_remove),
.driver = {
.name = "gpio-keys",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &gpio_keys_pm_ops,
#endif
}
};
注意,该结构体实例里没有为id_table变量赋值,因此写平台设备结构体实例的时候name成员的值要设置为gpio-keys,这样才能与该驱动匹配和绑定,从而该驱动中的probe函数才会被调用。下面看gpio_keys_probe函数的定义:
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct gpio_keys_drvdata *ddata;
struct device *dev = &pdev->dev;
struct input_dev *input;
int i, error;
int wakeup = ; ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
input = input_allocate_device();
if (!ddata || !input) {
dev_err(dev, "failed to allocate state\n");
error = -ENOMEM;
goto fail1;
} ddata->input = input;
ddata->n_buttons = pdata->nbuttons;
ddata->enable = pdata->enable;
ddata->disable = pdata->disable;
mutex_init(&ddata->disable_lock); platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata); input->name = pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->open = gpio_keys_open;
input->close = gpio_keys_close; input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100; /* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit); for (i = ; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
unsigned int type = button->type ?: EV_KEY; bdata->input = input;
bdata->button = button; error = gpio_keys_setup_key(pdev, bdata, button);
if (error)
goto fail2; if (button->wakeup)
wakeup = ; input_set_capability(input, type, button->code);
} error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
goto fail2;
} error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto fail3;
} /* get current state of buttons */
for (i = ; i < pdata->nbuttons; i++)
gpio_keys_report_event(&ddata->data[i]);
input_sync(input); device_init_wakeup(&pdev->dev, wakeup); return ; fail3:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
fail2:
while (--i >= ) {
free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
if (ddata->data[i].timer_debounce)
del_timer_sync(&ddata->data[i].timer);
cancel_work_sync(&ddata->data[i].work);
gpio_free(pdata->buttons[i].gpio);
} platform_set_drvdata(pdev, NULL);
fail1:
input_free_device(input);
kfree(ddata); return error;
}
445行,获取平台设备数据。
452行,为struct gpio_keys_drvdata对象分配内存,注意,另外还分配pdata->nbuttons * sizeof(struct gpio_button_data)内存,pdata->nbuttons的值为按键的个数。struct gpio_keys_platform_data的定义在include/linux/gpio_keys.h:
struct gpio_keys_platform_data {
struct gpio_keys_button *buttons;
int nbuttons;
unsigned int rep:; /* enable input subsystem auto repeat */
int (*enable)(struct device *dev);
void (*disable)(struct device *dev);
};
17行,buttons,指向板文件中定义的struct gpio_keys_button数组。
18行,nbuttons,按键的个数。
19行,rep,是否支持按键自动重复。
20、21行,使能和失能按键的函数指针。
下面看一下struct gpio_keys_drvdata的定义:
struct gpio_keys_drvdata {
struct input_dev *input;
struct mutex disable_lock;
unsigned int n_buttons;
int (*enable)(struct device *dev);
void (*disable)(struct device *dev);
struct gpio_button_data data[];
};
39行,input,当前Input设备。
41行,n_buttons,按键的个数。
44行,是一个变长数组,当获取到平台数据时才能确定该数组的长度。看下该数组类型struct gpio_button_data的定义:
struct gpio_button_data {
struct gpio_keys_button *button;
struct input_dev *input;
struct timer_list timer;
struct work_struct work;
int timer_debounce; /* in msecs */
bool disabled;
};
先看31行,input,设备的指针。
32行,timer,用来延时的定时器。
33行,work,工作队列。
34行,timer_debounce,定时器的定时时间,单位为ms。
35行,disabled,按键是否已经使能。
看回30行,struct gpio_keys_button的定义在include/linux/gpio_keys.h中:
struct gpio_keys_button {
/* Configuration parameters */
int code; /* input event code (KEY_*, SW_*) */
int gpio;
int active_low;
char *desc;
int type; /* input event type (EV_KEY, EV_SW) */
int wakeup; /* configure the button as a wake-up source */
int debounce_interval; /* debounce ticks interval in msecs */
bool can_disable;
};
6行,code,键值。
7行,gpio,所使用的IO口。
8行,active_low,1表示低电平有效。
9行,desc,按键的名字。
10行,type,按键的事件类型。
11行,wakeup,1表示按键作为唤醒源。
12行,debounce_interval,延时消抖所需要的时间,单位为ms。
13行,can_disable,按键是否可以失能。
回到gpio_keys_probe函数,455行,分配Input设备,input_allocate_device函数在drivers/input/input.c里定义:
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;
}
1557行,看下struct input_dev结构体在include/linux/input.h中的定义:
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id; 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)];
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,
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 rep[REP_CNT]; struct input_mt_slot *mt;
int mtsize;
int slot; struct input_absinfo *absinfo; 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 (*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; bool sync; struct device dev; struct list_head h_list;
struct list_head node;
};
1151行,name,设备的名字。
1152行,phys,设备在系统层次结构中的路径。
1153行,uniq,设备的识别码。
1154行,id,还是ID,直接看它的定义更直接。
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
Linux设备驱动剖析之Input(一)的更多相关文章
- Linux设备驱动剖析之Input(二)
分别是总线类型.厂商号.产品号和版本号. 1156行,evbit,设备支持的事件类型的位图,每一位代表一种事件,比如EV_KEY.EV_REL事件等等.BITS_TO_LONGS(nr)是一个宏,假设 ...
- Linux设备驱动剖析之Input(三)
/* get current state of buttons */ ; i < pdata->nbuttons; i++) gpio_keys_report_event(&dda ...
- Linux设备驱动剖析之Input(四)
static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) ...
- Linux设备驱动剖析之IIC(一)
写在前面 由于IIC总线只需要两根线就可以完成读写操作,而且通信协议简单,一条总线上可以挂载多个设备,因此被广泛使用.但是IIC总线有一个缺点,就是传输速率比较低.本文基于Linux-2.6.36版本 ...
- Linux设备驱动剖析之SPI(三)
572至574行,分配内存,注意对象的类型是struct spidev_data,看下它在drivers/spi/spidev.c中的定义: struct spidev_data { dev_t de ...
- Linux设备驱动剖析之SPI(二)
957至962行,一个SPI控制器用一个master来描述.这里使用SPI核心的spi_alloc_master函数请求分配master.它在drivers/spi/spi.c文件中定义: struc ...
- Linux设备驱动剖析之SPI(一)
写在前面 初次接触SPI是因为几年前玩单片机的时候,由于普通的51单片机没有SPI控制器,所以只好用IO口去模拟.最近一次接触SPI是大三时参加的校内选拔赛,当时需要用2440去控制nrf24L01, ...
- Linux设备驱动剖析之IIC(二)
953行,适配器的编号大于MAX_ID_MASK是不行的,MAX_ID_MASK是一个宏,展开后的值为61. 957至968行,关于管理小整形ID数的,没怎么了解,略过. 974行,调用i2c_reg ...
- Linux设备驱动剖析之SPI(四)
781行之前没什么好说的,直接看783行,将work投入到工作队列里,然后就返回,在这里就可以回答之前为什么是异步的问题.以后在某个合适的时间里CPU会执行这个work指定的函数,这里是s3c64xx ...
随机推荐
- git代码合并:Merge、Rebase的选择
代码合并:Merge.Rebase的选择 Zhongyi Tong edited this page on Dec 7, 2015 · 3 revisions Pages 19 Home 2.1 快速 ...
- ArcGIS中国工具2.5正式发布
ArcGIS中国工具2.5正式发布 1. 支持1:500,1:1000,1:2000的梯形接幅表和图框打印(见3.2) 2. 支持接幅表,一定间隔如1秒加节点生成并能自动识别打印(见3. ...
- UnityEditor研究学习之EditorWindow
在unity使用过程中,其实我们都是在各个不同功能的Window下工作. 比如在Scene窗口中操作物体,在Inspector中操作物体属性,在Game视窗中观察游戏状态. 所以窗口是Unity的灵魂 ...
- exited abnormally with signal 11: Segmentation fault 的相关处理
前一阵子遇到一个问题,程序打包后,在某个界面总是崩溃,device log只打印了exited abnormally with signal 11: Segmentation fault 网上找了下相 ...
- caffe solver 配置详解
caffe solver通过协调网络前向推理和反向梯度传播来进行模型优化,并通过权重参数更新来改善网络损失求解最优算法,而solver学习的任务被划分为:监督优化和参数更新,生成损失并计算梯度.caf ...
- StackExchange.Redis 管道 批量 高性能插入数据
现在用redis来做数据缓存的越来越多了,很多项目都有初始化redis数据的过程,由于初始化的数据比较大,那么该过程越快越好.这里我们以HashSet方法为例, 这里我们推荐用HashEntry[] ...
- Java 8 学习资料汇总【转载】
原文地址 2014年3月18日,Java SE 8 发布,而 Java 9 预期2016年发布: 2011年7月7日,Java 7 发布,是2009年4月20日被Oracle 以74亿美元收购首次推出 ...
- 后台任务hangfire
Installation¶ There are a couple of packages for Hangfire available on NuGet. To install Hangfire in ...
- 【PMP】项目的定义和特点
1.定义 项目是为创建独特的产品.服务和成果而进行的的临时性工作. 2.特点 2.1 独特的产品.服务或成果 实现项目目标可能产生一个或多个可交付成果.例如:即便采用相同的材料或者相同的施工单位来建设 ...
- 使用Amalgamate将C/C++项目合并成一个.h/.c[pp]文件
简述 C/C++开源库一般是一堆的头文件和源文件,做到声明和实现分离,减小单个模块大小,这在设计上是很好的,但是用起来稍显麻烦.在网上看到有好心人推荐了一个开源工具Amalgamate,专门用来对C/ ...