目录
输入设备驱动框架简介
输入设备(案件、键盘、鼠标、触摸屏等)是典型的字符设备。该类驱动的工作特点是,底层在进行按键或者触屏等其他动作时会产生一个中断,或者驱动中定时获取底层的操作,当检测到动作发生的时候,回去读取键值或者座标数据,然后将数据存储在一段缓冲区中,字符设备驱动负责管理者这块缓冲区,而用户则直接通过字符设备驱动的用户接口进行键值的读取。
这些操作中,只有中断键值适合具体的设备相关,但是对于缓冲区的管理是和具体的设备无关的,所以linux中加入了一个input的核心层代码专门用于处理公共的工作,这样才符合linux下的驱动框架。
输入事件驱动程序:drivers\input\dvdev.c drivers\input\joydev.c drivers\input\mousedev.c
输入核心:drivers\input\input.c
输入设备驱动程序:drivers\input\gpio_keys.c
输入核心层
输入核心层提供了底层的设备驱动程序需要的API,申请和释放一个输入设备
struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
申请完设备之后,需要将设备注册到内核中:函数中使用的参数就是input_allocate_device函数的返回值
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
还提供了一些报告输入事件的接口:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/*报告指定type、code的输入事件*/
void input_report_key(struct input_dev *dev, 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_sync(struct input_dev *dev);
/*报告同步事件*/
input_report_key、input_report_rel、input_report_abs其实只是type不同,最终调用的还是input_event函数,一般调用完上述这四个函数之后,都需要调用input_sync,使上述发送的报告事件生效。
所有的事件都可以使用一个结构体表示:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
设备驱动程序
以gpio_keys.c为例,本文件实现了一套通用的GPIO按键驱动,因为它将硬件相关配置瓶坯在版文件中,并且符合platform驱动框架,所以可以使用在各个处理器中。
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
size_t size;
int i, error;
int wakeup = 0;
if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
size = sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data);
ddata = devm_kzalloc(dev, size, GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n");
return -ENOMEM;
}
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
ddata->pdata = pdata;
ddata->input = input;
mutex_init(&ddata->disable_lock);
platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata);
input->name = pdata->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 = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
error = gpio_keys_setup_key(pdev, input, bdata, button);
if (error)
return error;
if (button->wakeup)
wakeup = 1;
}
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);
return error;
}
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto err_remove_group;
}
device_init_wakeup(&pdev->dev, wakeup);
return 0;
err_remove_group:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error;
}
函数中主要完成了三个工作:
- 分配一个输入设备devm_input_allocate_device
- 53-63行对这个dpio进行了配置,包括申请中断等
- input_register_device注册这个设备到linux中
完成初始化工作之后,只要等案件或者其他输入动作发生之后,进行数据的读取并且报告事件即可
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned long flags;
BUG_ON(irq != bdata->irq);
spin_lock_irqsave(&bdata->lock, flags);
if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);
input_event(input, EV_KEY, button->code, 1);
input_sync(input);
if (!bdata->release_delay) {
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
goto out;
}
bdata->key_pressed = true;
}
if (bdata->release_delay)
mod_timer(&bdata->release_timer,
jiffies + msecs_to_jiffies(bdata->release_delay));
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
函数中最重要的两行代码如下所示:
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
GPIO按键驱动通过这两个函数来汇报按键事件以及同步事件
输入事件驱动程序
用户最终获取按键数据是通过drivers\input\dvdev.c drivers\input\joydev.c drivers\input\mousedev.c 中的read函数
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
size_t read = 0;
int error;
if (count != 0 && count < input_event_size())
return -EINVAL;
for (;;) {
if (!evdev->exist || client->revoked)
return -ENODEV;
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break;
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event))
return -EFAULT;
read += input_event_size();
}
if (read)
break;
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;
}
}
return read;
}
函数的17-19行首先判断是否是阻塞读,如果不是则直接退出报错,因为这种的事件只能阻塞读,41-43就是阻塞等待,当设备驱动调用input_event发送事件之后们就会唤醒这个阻塞的操作,这样用户就可以读取到输入事件的值了