输入设备是典型的字符驱动。
工作机理
输入设备驱动工作机理:
- 底层在按键发生时,产生一个中断(或者驱动通过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\n"); 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\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 = 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\n", 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\n", error); return error; } error = input_register_device(input); if (error) { dev_err(dev, "Unable to register input device, error: %d\n", 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文件里面。比如:arch\arm\mach-mx6\board-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, } };