This article introduces the driver development of the Linux input subsystem.
The input subsystem of the Linux kernel provides a driver framework for input devices such as mouse, keyboard, touch screen, and joystick. When programmers want to write drivers for their own input devices, they only need to implement getting input events from the device. As for how to handle input events and how to implement user interface, it is all done by the input subsystem. This greatly reduces the coding effort of the input driver and also improves the robustness of the driver.
At the same time, the input subsystem provides a standard interface for the application layer for all input devices, which greatly improves the usability of the driver.The driver code for the input subsystem is in the kernel's <drivers/input/> directory.
The implementation of the input subsystem composing
the input subsystem needs to meet the following requirements:
(1) The input subsystem needs to generate a device file in the /dev/ directory for each input device, so as to facilitate the application program to read the events generated by the specified input device ;
(2) For each input device, the input subsystem only needs to realize its event acquisition, and it does not need to consider how to process the event and how to reach the device file;
(3) Input devices in Linux can be divided into event classes (such as USB mouse, USB keyboard, touch screen, etc.), MOUSE class (specifically PS/2 interface input devices), joysticks and other types, and are implemented for these input devices. The interface of the device file must be different. Therefore the input subsystem needs to implement the correct device file interface for different types of input devices.
This time, we will learn about the input subsystem by developing a simple input subsystem code. This time, we will develop it in combination with the platform bus + input subsystem. The following is the relevant code:
First define a header file:
#ifndef __PLAT_INPUT_H__ #define __PLAT_INPUT_H__ struct key_info { char *name; int gpio; int code; int flags; }; struct key_platdata { struct key_info *key_desc; int num; }; #endif
Enter the Subsystem Platform Device Information Code:
#include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include "key_info.h" struct key_info key_pdesc[4] = { [0] = { .name = "KEY_UP", .gpio = EXYNOS4_GPX3(2), .code = KEY_UP, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [1] = { .name = "KEY_DOWN", .gpio = EXYNOS4_GPX3(3), .code = KEY_DOWN, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [2] = { .name = "KEY_LEFT", .gpio = EXYNOS4_GPX3(4), .code = KEY_LEFT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [3] = { .name = "KEY_RIGHT", .gpio = EXYNOS4_GPX3(5), .code = KEY_RIGHT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, }; struct key_platdata key_pdev_info = { .key_desc = key_pdesc, .num = ARRAY_SIZE(key_pdesc), }; struct platform_device key_input_pdev = { .name = "EXYNOS4_KEY", .id = -1, .dev = { .platform_data = &key_pdev_info, }, }; static void __exit key_platdev_exit(void) { platform_device_unregister(&key_input_pdev); } static int __init key_platdev_init(void) { return platform_device_register(&key_input_pdev); } module_init(key_platdev_init); module_exit(key_platdev_exit); MODULE_LICENSE("Dual BSD/GPL");
Enter the subsystem platform device driver code:
#include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/gpio.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include "key_info.h" struct key_platdata *key_plat_info = NULL; struct input_dev *key_inputdev = NULL; irqreturn_t key_input_irq(int irqno, void *devid) { int i = 0; udelay (25); for(i = 0; i < key_plat_info->num; i++){ if(gpio_get_value(key_plat_info->key_desc[i].gpio)){ input_report_key(key_inputdev, key_plat_info->key_desc[i].code, 0); } else{ printk("<KERNEL> %s PRESSED\n", key_plat_info->key_desc[i].name); input_report_key(key_inputdev, key_plat_info->key_desc[i].code, 1); } } input_sync(key_inputdev); return IRQ_HANDLED; } int key_input_remove(struct platform_device *pdev) { int i; for(i = 0; i < key_plat_info->num; i++) free_irq(gpio_to_irq(key_plat_info->key_desc[i].gpio), &key_plat_info->key_desc[i]); input_unregister_device(key_inputdev); input_free_device(key_inputdev); } int key_input_probe(struct platform_device *pdev) { int ret = -1, i; // 0, get platform data key_plat_info = pdev->dev.platform_data; // 1, construct an input_devvice object key_inputdev = input_allocate_device(); if(NULL == key_inputdev){ printk("input alloc failed!\n"); return -EINVAL; } // 2, initialize the input_device object // 2.1, set what types of data can be generated __set_bit(EV_KEY, key_inputdev->evbit); // 2.2, set which key values can be generated __set_bit(KEY_UP, key_inputdev->keybit); __set_bit(KEY_DOWN, key_inputdev->keybit); __set_bit(KEY_LEFT, key_inputdev->keybit); __set_bit(KEY_RIGHT,key_inputdev->keybit); // 3, register the input device object ret = input_register_device(key_inputdev); if (0! = ret) { printk("input register failed!\n"); goto err1; } // 4, hardware initialization --> apply for interrupt for(i = 0; i < key_plat_info->num; i++){ ret = request_irq(gpio_to_irq(key_plat_info->key_desc[i].gpio), key_input_irq, key_plat_info->key_desc[i].flags, key_plat_info->key_desc[i].name, &key_plat_info->key_desc[i]); if (0! = ret) { printk("request irq failed!\n"); goto err2; } } return 0; err2: input_unregister_device(key_inputdev); err1: input_free_device(key_inputdev); return ret; } const struct platform_device_id key_id_table[] = { {"S5PV210_KEY", 0x5555}, {"EXYNOS4_KEY", 0x6665}, }; struct platform_driver key_input_pdrv = { .probe = key_input_probe, .remove = key_input_remove, .driver = { .name = "Samsung_KEY", }, .id_table = key_id_table, }; static void __exit key_platdrv_exit(void) { platform_driver_unregister(&key_input_pdrv); } static int __init key_platdrv_init(void) { return platform_driver_register(&key_input_pdrv); } module_init(key_platdrv_init); module_exit(key_platdrv_exit); MODULE_LICENSE("Dual BSD/GPL");
User layer test code:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(void) { int ret = -1, fd = -1; struct input_event event; fd = open("/dev/input/event2", O_RDWR); if(fd < 0){ perror("open failed!\n"); exit(1); } for(;;){ ret = read(fd, &event, sizeof(struct input_event)); if(ret < 0){ perror("read failed!\n"); exit(1); } if(EV_KEY == event.type){ switch(event.code) { case KEY_UP: if(event.value) printf("KEY_UP pressed!\n"); else printf("KEY_UP UP!\n"); break; case KEY_DOWN: if(event.value) printf("KEY_DOWN pressed!\n"); else printf("KEY_DOWN UP!\n"); break; case KEY_LEFT: if(event.value) printf("KEY_LEFT pressed!\n"); else printf("KEY_LEFT UP!\n"); break; case KEY_RIGHT: if(event.value) printf("KEY_RIGHT pressed!\n"); else printf("KEY_RIGHT UP!\n"); break; default: break; } } } if(close(fd) < 0){ perror("close"); exit(1); } return 0; }
And finally the Makefile:
#Linux source code path KERNEL_DIR = /home/george/1702/exynos/linux-3.5 #Specify the current path CUR_DIR = $ (shell pwd) MYAPP = key_test MODULE = plat_input_dev MODULE2 = plat_input_drv all: make -C $(KERNEL_DIR) M=$(CUR_DIR) modules arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c clean: make -C $(KERNEL_DIR) M=$(CUR_DIR) clean $ (RM) $ (MYAPP) install: cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702 #Specify which source file to compile in the current directory obj-m = $(MODULE).o obj-m += $(MODULE2).oThat's all.