Linux Input 子系統
Input 子系統層次框架
輸入(Input)子系統是分層架構的,總共分爲 3 層,從上到下分別是:事件處理層(Event Handler)、輸入子系統核心層(Input Core)、硬件驅動層(Input Driver)。
- 硬件驅動層負責操作具體的硬件設備,這層的代碼是針對具體的驅動程序的,比如你的設備是觸摸輸入設備,還是鼠標輸入設備,還是鍵盤輸入設備,這些不同的設備,自然有不同的硬件操作,驅動工程師往往只需要完成這層的代碼編寫。
- 輸入子系統核心層是鏈接其他兩層之間的紐帶與橋樑,向下提供硬件驅動層的接口,向上提供事件處理層的接口。
- 事件處理層負責與用戶程序打交道,將硬件驅動層傳來的事件報告給用戶程序。
各層之間通信的基本單位就是事件,任何一個輸入設備的動作都可以抽象成一種事件,如鍵盤的按下,觸摸屏的按下,鼠標的移動等。事件有三種屬性:類型(type),編碼(code),值(value), Input 子系統支持的所有事件都定義在 input.h中,包括所有支持的類型,所屬類型支持的編碼等。事件傳送的方向是 硬件驅動層–>子系統核心–>事件處理層–>用戶空間
三個重要的結構體
- 輸入設備(input_dev)
struct input_dev {
const char *name;
/* 標識設備驅動特徵,如總線類型、生產廠商、產品類型、版本 */
struct input_id id;
/* 表示能產生哪類事件 */
unsigned long evbit[NBITS(EV_MAX)];
/* 表示能產生哪些按鍵 */
unsigned long keybit[NBITS(KEY_MAX)];
/* 表示能產生哪些相對位移事件, x,y,滾輪 */
unsigned long relbit[NBITS(REL_MAX)];
/* 表示能產生哪些絕對位移事件, x,y */
unsigned long absbit[NBITS(ABS_MAX)];
struct device dev;
/* 用來鏈接他所支持的 input_handle 結構,然後用
* input_handle 找到裏面的 input_handler
*/
struct list_head h_list;
/* 鏈接到 input_handler_list,這個鏈表
* 鏈接了所有註冊到內核的事件處理器
*/
struct list_head node;
...
}
- evbit[BITS_TO_LONGS(EV_CNT)]數組, 這個數組以位掩碼的形式,代表了這個設備支持哪類事件,比如:
#define EV_SYN 0x00 //同步類
#define EV_KEY 0x01 //按鍵類
#define EV_REL 0x02 //相對位移類
#define EV_ABS 0x03 //絕對位移類
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14 //重複類
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
- keybit[BITS_TO_LONGS(KEY_CNT)]數組,這個數組也是以位掩碼的形式,代表這個設備支持哪些按鍵,比如:
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_TAB 15
#define KEY_ENTER 28
#define KEY_A 30
#define KEY_B 48
#define KEY_C 46
……
- relbit[BITS_TO_LONGS(REL_CNT)]數組,這個數組也是以位掩碼的形式,代表這個設備支持哪些相對位移事件,比如:
#define ABS_X 0x00
#define ABS_Y 0x01
- 事件處理器(input_handler)
struct input_handler {
/* 當事件處理器接收到了來自 input 設備傳來的
* 事件時調用的處理函數,負責處理事件。
*/
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
/* 當一個 input 設備註冊到內核的時候被調用, 將事件處理器與輸入設備
* 聯繫起來的函數, 也就是將 input_dev 和 input_handler 配對的函數。
*/
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
/* 與 connect 相反 */
void (*disconnect)(struct input_handle *handle);
/* 文件操作集,因爲事件處理器要完成讀寫功能 */
const struct file_operations *fops;
/* 事件處理器所支持的 input 設備 */
const struct input_device_id *id_table;
/* 鏈接他所支持的 input_handle 結構,然後用
* input_handle 找到裏面的 input_dev
*/
struct list_head h_list
/* 鏈接到 input_handler_list,這個鏈表
* 鏈接了所有註冊到內核的事件處理器
*/
struct list_head node;
};
- 事件溝通者(input_handle)
之所以稱 input_handle(注意了,不是事件處理器 input_handler)爲事件溝通者,是因爲它代表一個成功配對的 input_dev 和 input_handler。 主要成員有:
struct input_handle {
/* 每個配對的事件處理器都會分配一個對應的設備結構,
* 如 evdev 事件處理器的 evdev 結構,注意這個結構與
* 設備驅動層的 input_dev 不同,初始化 handle 時,保存到這裏。
*/
void *private;
/* 指向 input_dev 結構體實例 */
struct input_dev *dev;
/* 指向 input_handler 結構體實例 */
struct input_handler *handler;
/* input_handle 通過 d_node 連接到了 input_dev 上的 h_list 鏈表上 */
struct list_head d_node;
/* input_handle 通過 h_node 連接到了 input_handler 的 h_list 鏈表 */
struct list_head h_node;
};
- 三個結構體之間的關係
input_dev 是硬件驅動層,代表一個 input 設備。
通過全局的 input_dev_list 鏈接在一起。設備註冊的時候實現這個操作。
input_handler 是事件處理層,代表一個事件處理器。
通過全局的 input_handler_list 鏈接在一起。事件處理器註冊的時候實現這個操作(事件處理器一般內核自帶,一般不需要我們來寫)
input_handle 個人認爲屬於核心層,代表一個配對的 input 設備與 input 事件處理器。
它沒有一個全局的鏈表,它註冊的時候將自己分別掛在了 input_dev 和input_handler 的 h_list 上了。通過 input_dev 和 input_handler 就可以找到input_handle 在設備註冊和事件處理器, 註冊的時候都要進行配對工作,配對後就會實現鏈接。通過 input_handle 也可以找到 input_dev 和input_handler。
Input 子系統核心層
子系統核心層的功能是:向下提供硬件驅動層的接口,向上提供事件處理層的接口。
主要函數:
/* 向內核註冊一個 input 設備 */
int input_register_device(struct input_dev *dev)
/* 向內核註冊一個事件處理器 */
int input_register_handler(struct input_handler *handler)
/* 向內核註冊一個 handle 結構 */
int input_register_handle(struct input_handle *handle)
- input_register_device 的主要功能是:
初始化一些默認的值,將自己的 device 結構添加到 linux 設備模型當中,將 input_dev 添加到 input_dev_list鏈表中, 對於 input_handler_list 鏈表的每一項,都調用 input_attach_handler,它根據 input_handler 的 id_table 判斷能否支持這個輸入設備。 - input_register_handler的主要功能是:
將 handler 放入 input_handler_list 鏈表,對於每個 input_dev,調用 input_attach_handler,它根據 input_handler 的 id_table 判斷能否支持這個輸入設備,如果支持,則調用 handler->connect 函數,建立“連接” - input_register_handle的主要功能是:
把 handle 結構體通過 d_node 鏈表項鍊接到 input_dev 的 h_list;把 handle 結構體通過 h_node 鏈表項鍊接到input_handler 的 h_list。這樣一來,就可以通過 input_dev 的 h_list 找到 handle,進而找到 handle裏的 handler;或者通過 input_handler 的 h_list 找到 handle,進而找到 handle裏的 input_dev。
輸入子系統框架
- 初始化
- 分配input_dev結構:input_allocate_device
- 申明可能會上報的事件類型:set_bit
- 如果上報的是按鍵,申明可能上報的按鍵編號:set_bit
- 註冊輸入型設備:input_register_device
- 上報
- 上報產生的事件:input_report_key
- 告訴核心上報結束:input_sync
輸入型按鍵驅動編寫
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
#include <asm/io.h>
struct input_dev *button_dev = NULL;
struct button_irq_desc {
int irq; /* 中斷號 */
int pin; /* GPIO引腳 */
int button_val; /* 按鍵初始值 */
char *name; /* 名字 */
};
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT(16), S5PV210_GPH2(0), KEY_A, "S1"}, /* S1 */
{IRQ_EINT(17), S5PV210_GPH2(1), KEY_B, "S2"}, /* S2 */
{IRQ_EINT(18), S5PV210_GPH2(2), KEY_C, "S3"}, /* S3 */
{IRQ_EINT(19), S5PV210_GPH2(3), KEY_L, "S4"}, /* S4 */
{IRQ_EINT(24), S5PV210_GPH3(0), KEY_S, "S5"}, /* S5 */
{IRQ_EINT(25), S5PV210_GPH3(1), KEY_ENTER, "S6"}, /* S6 */
{IRQ_EINT(26), S5PV210_GPH3(2), KEY_LEFTSHIFT, "S7"}, /* S7 */
{IRQ_EINT(27), S5PV210_GPH3(3), KEY_DELETE, "S8"}, /* S8 */
};
static volatile int ev_press;
static struct button_irq_desc *irq_pd = NULL;
struct work_struct *button_work = NULL;
struct timer_list button_timer;
static void button_timer_function(unsigned long data)
{
unsigned int pinval = 0;
ev_press = 1;
/* 獲取產生中斷的引腳值 */
pinval = gpio_get_value(irq_pd->pin);
if(pinval == 1)
{
input_report_key(button_dev, irq_pd->button_val, 1);
}
else
{
input_event(button_dev, EV_KEY, irq_pd->button_val, 0);
}
/* 上報同步事件 */
input_sync(button_dev);
}
static void button_work_fun(struct work_struct *work)
{
/* 啓動定時器 */
mod_timer(&button_timer, (jiffies + HZ/10));
}
irqreturn_t button_interrupt(int irq, void *dev_id)
{
/* 檢查是否產生中斷(共享中斷才用)*/
/* 清除中斷標誌*/
ev_press = 1;
/* 中斷下半部: 將產生中斷的中斷號傳遞到工作函數,提交工作(到內核默認的工作隊列)*/
irq_pd = (struct button_irq_desc *)dev_id;
schedule_work(button_work);
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 驅動程序的入口函數 */
static int __init button_init(void)
{
int i = 0;
int err = 0;
/* 輸入子系統初始化 */
/* 分配input_dev結構 */
button_dev = input_allocate_device();
/* 申明可能會上報的事件類型 */
set_bit(EV_KEY, button_dev->evbit);
set_bit(KEY_A, button_dev->keybit);
set_bit(KEY_B, button_dev->keybit);
set_bit(KEY_C, button_dev->keybit);
set_bit(KEY_L, button_dev->keybit);
set_bit(KEY_S, button_dev->keybit);
set_bit(KEY_ENTER, button_dev->keybit);
set_bit(KEY_LEFTSHIFT, button_dev->keybit);
set_bit(KEY_DELETE, button_dev->keybit);
/* 註冊輸入型設備 */
err = input_register_device(button_dev);
if(err)
{
return -EBUSY;
}
/* 使用 request_irq 函數註冊中斷 */
for(i = 0; i < sizeof(button_irqs) / sizeof(button_irqs[0]); i++)
{
err = request_irq(button_irqs[i].irq, button_interrupt, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
button_irqs[i].name, (void *)&button_irqs[i]);
}
if(err)
{
i--;
while(i--)
{
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return -EBUSY;
}
/* 創建工作 */
button_work = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(button_work, button_work_fun);
/* 初始化定時器 */
init_timer(&button_timer);
button_timer.function = button_timer_function;
/* 註冊定時器 */
add_timer(&button_timer);
return 0;
}
/* 驅動程序的出口函數 */
static void __exit button_exit(void)
{
int i = 0;
/* 註銷輸入型設備*/
input_unregister_device(button_dev);
/* 註銷中斷 */
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
}
/* 用於修飾入口/出口函數,換句話說,相當於
* 告訴內核驅動程序的入口/出口函數在哪裏
*/
module_init(button_init);
module_exit(button_exit);
/* 該驅動支持的協議 */
MODULE_LICENSE("GPL");