04. Linux輸入子系統

Linux Input 子系統

Input 子系統層次框架
輸入(Input)子系統是分層架構的,總共分爲 3 層,從上到下分別是:事件處理層(Event Handler)、輸入子系統核心層(Input Core)、硬件驅動層(Input Driver)。
在這裏插入圖片描述

  1. 硬件驅動層負責操作具體的硬件設備,這層的代碼是針對具體的驅動程序的,比如你的設備是觸摸輸入設備,還是鼠標輸入設備,還是鍵盤輸入設備,這些不同的設備,自然有不同的硬件操作,驅動工程師往往只需要完成這層的代碼編寫。
  2. 輸入子系統核心層是鏈接其他兩層之間的紐帶與橋樑,向下提供硬件驅動層的接口,向上提供事件處理層的接口。
  3. 事件處理層負責與用戶程序打交道,將硬件驅動層傳來的事件報告給用戶程序。
    各層之間通信的基本單位就是事件,任何一個輸入設備的動作都可以抽象成一種事件,如鍵盤的按下,觸摸屏的按下,鼠標的移動等。事件有三種屬性:類型(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;
	...
}
  1. 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)
  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
……
  1. 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。

輸入子系統框架

  1. 初始化
  • 分配input_dev結構:input_allocate_device
  • 申明可能會上報的事件類型:set_bit
  • 如果上報的是按鍵,申明可能上報的按鍵編號:set_bit
  • 註冊輸入型設備:input_register_device
  1. 上報
  • 上報產生的事件: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");

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章