输入设备是典型的字符驱动。
工作机理
输入设备驱动工作机理:
- 底层在按键发生时,产生一个中断(或者驱动通过timer定时查询),
- 然后CPU通过总线(SPI, I2C)读取键值,并将它们放入到一个缓冲区
- 字符设备驱动管理该缓冲区。驱动的read() API让用户可以读取键值
只有中断,键值是具体设备相关,其他是输入设备通用。
因此,内核设计了输入子系统。
输入子系统框架
引用链接中架构图,点击打开链接
按键驱动分析
那么以GPIO按键驱动做个分析。
GPIO driver
drivers/input/keyboard/gpio_keys.c
probe()
对照输入子系统架构图, GPIO按键驱动调用了“Input Core"中提供的通用API, 见下面代码中的
input = devm_input_allocate_device(dev);
error = input_register_device(input);
/dev/input目录下的事件都是在驱动中调用input_register_device(struct input_dev *dev)产生的。
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 fwnode_handle *child = NULL;
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
");
return -ENOMEM;
}
ddata->keymap = devm_kcalloc(dev,
pdata->nbuttons, sizeof(ddata->keymap[0]),
GFP_KERNEL);
if (!ddata->keymap)
return -ENOMEM;
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device
");
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 = 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;
input->keycode = ddata->keymap;
input->keycodesize = sizeof(ddata->keymap[0]);
input->keycodemax = pdata->nbuttons;
/* 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];
if (!dev_get_platdata(dev)) {
child = device_get_next_child_node(dev, child);
if (!child) {
dev_err(dev,
"missing child device node for entry %d
",
i);
return -EINVAL;
}
}
error = gpio_keys_setup_key(pdev, input, ddata,
button, i, child);
if (error) {
fwnode_handle_put(child);
return error;
}
if (button->wakeup)
wakeup = 1;
}
fwnode_handle_put(child);
error = devm_device_add_group(dev, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d
",
error);
return error;
}
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d
",
error);
return error;
}
device_init_wakeup(dev, wakeup);
return 0;
}
ISR
GPIO按键通过 input core提供的API, input_event()和input_sync(),来汇报按键事件
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
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, *bdata->code, 1);
input_sync(input);
if (!bdata->release_delay) {
input_event(input, EV_KEY, *bdata->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 Core
设备注册,事件,同步等相关API, drivers/input/input.c , 点击打开链接
linux VFS相关API (file_operations), drivers/input/evdev.c, 点击打开链接
platform device
一般的,在板级的初始化c文件里面。比如:archarmmach-mx6oard-mx6q-sabresd.c
#if defined(CONFIG_KEYBOARD_GPIO) || defined(CONFIG_KEYBOARD_GPIO_MODULE)
#define GPIO_BUTTON(gpio_num, ev_code, act_low, descr, wake, debounce)
{
.gpio = gpio_num,
.type = EV_KEY,
.code = ev_code,
.active_low = act_low,
.desc = "btn " descr,
.wakeup = wake,
.debounce_interval = debounce,
}
static struct gpio_keys_button imx6q_buttons[] = {
GPIO_BUTTON(SABRESD_KEY_USER1, KEY_VOLUMEUP, 1, "user-key-1", 0, 1),
GPIO_BUTTON(SABRESD_KEY_USER2, KEY_VOLUMEDOWN, 1, "user-key-2", 0, 1),
GPIO_BUTTON(SABRESD_KEY_WHIBUSB, KEY_F1, 1, "whibusb", 0, 1),
GPIO_BUTTON(SABRESD_KEY_WHIBUSL, KEY_F2, 1, "whibusl", 0, 1),
GPIO_BUTTON(SABRESD_KEY_WHIBUSR, KEY_F3, 1, "whibusr", 0, 1),
};
static struct gpio_keys_platform_data imx6q_button_data = {
.buttons = imx6q_buttons,
.nbuttons = ARRAY_SIZE(imx6q_buttons),
};
static struct platform_device imx6q_button_device = {
.name = "gpio-keys",
.id = -1,
.num_resources = 0,
.dev = {
.platform_data = &imx6q_button_data,
}
};
static void __init imx6q_add_device_buttons(void)
{
platform_device_register(&imx6q_button_device);
}
#else
static void __init imx6q_add_device_buttons(void) {}
#endif
上面注册了5个按键设备。然后在board_init()初始化函数里面,添加imx6q_add_device_buttons()。我们就可以通过应用层操作了。比如:按下某个按键的时候,在read()函数中获取哪个键被按下。
利用proc文件系统。cat /proc/bus/input/devices 查看一下按键是哪个event。
I: Bus=0011 Vendor=0002 Product=0006 Version=0000
N: Name="ImExPS/2 Generic Explorer Mouse"
P: Phys=isa0060/serio1/input0
S: Sysfs=/devices/platform/i8042/serio1/input/input4
U: Uniq=
H: Handlers=mouse0 event3
B: PROP=1
B: EV=7
B: KEY=1f0000 0 0 0 0 0 0 0 0
B: REL=143
platform driver
drivers/input/keyboard/gpio_keys.cstatic struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = gpio_keys_of_match,
}
};