Linux字符設備驅動框架(四):Linux內核的input子系統

/************************************************************************************html

*本文爲我的學習記錄,若有錯誤,歡迎指正。node

*本文參考資料: 
linux

*        https://blog.csdn.net/qq_35865125/article/details/80637809api

*        https://blog.csdn.net/yueqian_scut/article/details/48792939
數組

*        hhttps://www.cnblogs.com/lifexy/p/7542989.html數據結構

*        http://www.javashuo.com/article/p-tlyoojul-bv.html架構

************************************************************************************/框架

1. input子系統概述

 Linux輸入設備總類繁雜,常見的包括有按鍵、鍵盤、觸摸屏、鼠標、搖桿等;這些輸入設備屬於字符設備,而linux內核將這些設備的共同性抽象出來,簡化驅動開發創建了一個input子系統。input子系統對linux的輸入設備驅動進行了高度抽象,最終分紅了三層:input設備驅動層、input核心層、input事件處理層。input子系統的框架以下圖。ide

 

(1)input設備驅動層:負責操做具體的硬件設備,將底層的硬件輸入轉化爲統一事件形式,向input核心層彙報;函數

(2)input核心層:鏈接input設備驅動層與input事件處理層,向下提供驅動層的接口,向上提供事件處理層的接口;

(3)input事件處理層:爲不一樣硬件類型提供了用戶訪問及處理接口,將硬件驅動層傳來的事件報告給用戶程序。

2. 相關數據結構

先了解三個定義在/linux/input.h下重要的結構體input_dev、input_handler、input_handle。

(1)input_dev

Linux內核中使用input_dev結構體來描述一個input設備。input_dev結構體包含了一個input設備的全部信息,不一樣的input設備可能只用到其中的一部分。

struct input_dev {
    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;  //與input_handler匹配使用的id

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];  //設備支持的輸入事件位圖
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//對於按鍵事件,設備支持的輸入子事件位圖
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];// 對於相對座標事件,設備支持的相對座標子事件位圖
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//對於絕對座標事件,設備支持的絕對座標子事件位圖
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];//混雜設備的支持的子事件位圖
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

    unsigned int keycodemax;
    unsigned int keycodesize;
    void *keycode;
    int (*setkeycode)(struct input_dev *dev,
              unsigned int scancode, unsigned int keycode);
    int (*getkeycode)(struct input_dev *dev,
              unsigned int scancode, unsigned int *keycode);

    struct ff_device *ff;

    unsigned int repeat_key;
    struct timer_list timer;

    int sync;

    int abs[ABS_CNT];
    int rep[REP_MAX + 1];

    unsigned long key[BITS_TO_LONGS(KEY_CNT)];
    unsigned long led[BITS_TO_LONGS(LED_CNT)];
    unsigned long snd[BITS_TO_LONGS(SND_CNT)];
    unsigned long sw[BITS_TO_LONGS(SW_CNT)];

    int absmax[ABS_CNT];//絕對座標事件的最大鍵值
    int absmin[ABS_CNT];//絕對座標事件的最小鍵值
    int absfuzz[ABS_CNT];
    int absflat[ABS_CNT];
    int absres[ABS_CNT];

    int (*open)(struct input_dev *dev);
    void (*close)(struct input_dev *dev);
    int (*flush)(struct input_dev *dev, struct file *file);
    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

    struct input_handle *grab;

    spinlock_t event_lock;
    struct mutex mutex;

    unsigned int users;
    bool going_away;

    struct device dev;

    struct list_head    h_list;//該鏈表頭用於連接該設備所關聯的input_handle
    struct list_head    node;  //該鏈表頭用於將設備連接到input_dev_list
};
struct input_dev

(2)input_handler

input_handler表示對輸入事件的具體處理,它爲輸入設備的功能實現了一個接口。

struct input_handler 
{
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); struct input_handle* (*connect)(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id); void (*disconnect)(struct input_handle *handle); const struct file_operations *fops; //提供給用戶對設備操做的函數指針 int minor; char *name; struct input_device_id *id_table; //與input_dev匹配用的id struct input_device_id *blacklist; //標記的黑名單 struct list_head h_list; //用於連接和該handler相關的handle struct list_head node; //用於將該handler鏈入input_handler_list };

(3)input_handle

 input_handle是用來關聯input_dev和input_handler。爲何用input_handle來關聯input_dev和input_handler而不將input_dev和input_handler直接對應呢?由於一個device能夠對應多個handler,而一個handler也可處理多個device。就如一個觸摸屏設備能夠對應event handler也能夠對應tseve handler。

struct input_handle 
{
      void *private;
 
      int open;                      //記錄設備打開次數
      char *name;
 
      struct input_dev *dev;         //指向所屬的input_dev
      struct input_handler *handler; //指向所屬的input_handler
 
      struct list_head      d_node;  //用於鏈入所指向的input_dev的handle鏈表
      struct list_head      h_node;  //用於鏈入所指向的input_handler的handle鏈表
};

3. input核心層分析

input核心層的核心代碼:/kernel/drivers/input/input.c。

input核心層完成的主要工做包括:

1) 直接跟字符設備驅動框架交互,字符設備驅動框架根據主設備號來進行管理,而input-core則是依賴於次設備號來進行分類管理。Input子系統的全部輸入設備的主設備號都是13,其對應input核心層定義的structfile_operations input_fops。驅動架構層經過主設備號13獲取到input_fops,以後的處理便交給input_fops進行。

2) 提供接口供事件處理層(input-handler)和輸入設備(input-device)註冊,併爲輸入設備找到匹配的事件處理者。

3) 將input-device產生的消息(如觸屏座標和壓力值)轉發給input-handler,或者將input-handler的消息傳遞給input-device(如鼠標的閃燈命令)。

(1)input子系統的初始化

input子系統使用subsys_initcall宏修飾input_init()函數,所以input_init()函數在內核啓動階段被調用。input_init()函數的主要工做是:在sys文件系統下建立一個設備類(/sys/class/input),調用register_chrdev()函數註冊input設備。

static const struct file_operations input_fops =

{
  .owner = THIS_MODULE,
  .open = input_open_file,
};


static int __init input_init(void)
{
    int err;

    input_init_abs_bypass();

    err = class_register(&input_class);   //在sys文件系統下建立input_class類,/sys/class/input
    if (err) {
        printk(KERN_ERR "input: unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();//在/proc下創建相關文件
    if (err)
        goto fail1;

    //註冊input設備。INPUT_MAJOR是input設備的主設備號,INPUT_MAJOR=13;即,input子系統的全部設備的主設備號相同
    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
    if (err) {
        printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

    return 0;

 fail2:    input_proc_exit();
 fail1:    class_unregister(&input_class);
    return err;
}
subsys_initcall(input_init);

(2)input_handler與input_device的匹配

input核心層提供了input_dev與input_handler的註冊接口。從如下代碼中能夠看出,input_register_handler()與input_register_device中都進行了input_handler與input_device的匹配;由此可知,無論新添加input_dev仍是input_handler,都會進入input_attach_handler()判斷二者id是否有支持, 若二者支持便進行鏈接。

int input_register_handler(struct input_handler *handler)
{
   ... ...
  list_add_tail(&handler->node, &input_handler_list); //將新的handler放入鏈表中
  ... ...
  //遍歷input_dev_list鏈表中的全部input_dev,是否支持這個新添加的input_handle;若二者支持,便進行鏈接
    list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler);
  ... ...
}

int input_register_device(struct input_dev *dev)
{
  ... ...
  list_add_tail(&dev->node, &input_dev_list); //將新的dev放入鏈表中
  ... ...
  //遍歷input_handler_list鏈表中的全部input_handler,是否支持這個新添加的input_dev;若二者支持,便進行鏈接
  list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler); 
  ... ...
}

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev);  //匹配二者

if (!id)                                          //若不匹配,return退出
return -ENODEV; 

error = handler->connect(handler, dev, id);       //調用input_handler ->connect函數創建鏈接
... ...

}

若input_handler與input_device匹配成功,就會自動進入input_handler 的connect函數創建鏈接。以evdev_handler的.connect函數evdev_connect()爲例進行分析。

static struct input_handler evdev_handler = 
{
    .event        = evdev_event,
    .connect    = evdev_connect,
    .disconnect    = evdev_disconnect,
    .fops        = &evdev_fops,
    .minor        = EVDEV_MINOR_BASE,
    .name        = "evdev",
    .id_table    = evdev_ids,
};

static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)     
{
  ... ... 
  for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);//查找驅動設備的子設備號
   if (minor == EVDEV_MINORS)                  // EVDEV_MINORS=32,因此該事件下的驅動設備最多存32個
  { 
        printk(KERN_ERR "evdev: no more free evdev devices\n");
        return -ENFILE;                                   //沒找到驅動設備
    }
  ... ...
  evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);     //分配一個input_handle全局結構體(沒有r)
   ... ...
   evdev->handle.dev = dev;                             //指向參數input_dev驅動設備
  evdev->handle.name = evdev->name;
  evdev->handle.handler = handler;                      //指向參數 input_handler驅動處理結構體
  evdev->handle.private = evdev;
  sprintf(evdev->name, "event%d", minor);               //1)保存驅動設備名字, event%d
  ... ...
  devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),  //2) 將主設備號和次設備號轉換成dev_t類型
  cdev = class_device_create(&input_class, &dev->cdev,  //3)在input類下建立驅動設備
  devt,dev->cdev.dev, evdev->name); 
  ... ...
  error = input_register_handle(&evdev->handle);        //4)註冊這個input_handle結構體
  ... ...
}

1) 是在保存驅動設備名字,名爲event%d, 好比下圖(鍵盤驅動)event1: 由於沒有設置子設備號,默認從小到大排列,其中event0是表示這個input子系統,因此這個鍵盤驅動名字就是event1;

2)是在保存驅動設備的主次設備號,其中主設備號INPUT_MAJOR=13,次此設備號=EVDEV_MINOR_BASE+驅動程序自己子設備號;

3)會在/sys/class/input類下建立驅動設備event%d,譬如:鍵盤驅動event1;

4)最終會進入input_register_handle()函數來註冊handle。

int input_register_handle(struct input_handle *handle)
{
      struct input_handler *handler = handle->handler; //handler= input_handler驅動處理結構體 

      list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
      list_add_tail(&handle->h_node, &handler->h_list);    // (2)
 
      if (handler->start)
             handler->start(handle);
      return 0;
}

完成匹配後,最終結果以下:

 4.input事件處理層分析

 Linux內核中實現了幾個經常使用的handler,包括keyboard_handler、mouse_handler、joystick_handler、event_handler。這些handler已經知足絕大部分輸入設備的需求,下面以event_handler爲例對input事件處理層進行分析。

(1)event_handler初始化

Linux內核中以模塊的形式提供了event_handler,evdev_init被module_init修飾,即當event_handler模塊被裝載(insmod)時會調用evdev_init()函數。

evdev_init()函數調用input_register_handler()函數向Linux內核註冊event_handler,由input核心層分析可知,在input_register_handler()函數內部註冊event_handler時會遍歷當前的input_dev以判斷是否有input設備支持event_handler,若二者匹配則調用evdev_handler.connect來創建鏈接。

static struct input_handler evdev_handler = 
{
    .event       = evdev_event,
    .connect     = evdev_connect,
    .disconnect  = evdev_disconnect,
    .fops        = &evdev_fops,
    .minor       = EVDEV_MINOR_BASE,
    .name        = "evdev",
    .id_table    = evdev_ids,                       //用以和input_dev進行匹配
};

static int __init evdev_init(void)
{
    return input_register_handler(&evdev_handler);  //向Linux內核註冊event_handler
}

module_init(evdev_init);

(2)應用層open()input設備的過程分析

假設在註冊輸入設備過程當中生成/dev/input/event0設備文件,咱們來跟蹤打開這個設備的過程。

Open(「/dev/input/event0」)

1)vfs_open打開該設備文件,讀出文件的inode內容,獲得該設備的主設備號和次設備號;

2)chardev_open 字符設備驅動框架的open根據主設備號獲得輸入子系統的input_fops操做集;

3)進入到input_fops->open, 即input_open_file;

static int input_open_file(struct inode *inode, struct file *file)
{
    struct input_handler *handler;
    const struct file_operations *old_fops, *new_fops = NULL;
    int err;
  
  ... ...

    if (handler)
        new_fops = fops_get(handler->fops);

    old_fops = file->f_op;
    file->f_op = new_fops;

    err = new_fops->open(inode, file);
  
  ... ...

}

4)至此,進入到input_handler層。以evdev_handler爲例進行分析,new_fops->open(inode, file)即evdev_open。evdev不只關聯了底層具體的input_dev,並且記錄了應用層進程打開該設備的信息。以後input_dev產生的消息能夠傳遞到evdev的client中的消息隊列,便於上層讀取。

static int evdev_open(struct inode *inode, struct file *file)
{
    struct evdev *evdev;
    struct evdev_client *client;
  
  ... ...

    client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);

    //根據當前進程產生client的名稱
    snprintf(client->name, sizeof(client->name), "%s-%d",
            dev_name(&evdev->dev), task_tgid_vnr(current));
    wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
  //將表明打開該設備的進程相關的數據結構client和evdev綁定
    client->evdev = evdev;
    evdev_attach_client(evdev, client);
  //執行input_dev層的open
    error = evdev_open_device(evdev);
    ... ...
}

(3)應用層read()input設備的過程分析

open得到的fd句柄對應的file_operations是evdev_handler的evdev_fops。所以read接口最終會調用到evdev_fops的read接口,即evdev_read。

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
    struct evdev_client *client = file->private_data;
    struct evdev *evdev = client->evdev;
    struct input_event event;
    int retval;
  
  //判斷讀取長度是否小於單個input_event的長度
    if (count < input_event_size())
        return -EINVAL;

  //在非阻塞狀況下,若消息隊列爲空,則return
    if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
        return -EAGAIN;
  
  //等待消息隊列不爲空的事件
    retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist);
    if (retval)
        return retval;

    if (!evdev->exist)
        return -ENODEV;
  
  //將消息隊列中的消息取出,並經過input_event_to_user()返回至應用層
    while (retval + input_event_size() <= count && evdev_fetch_next_event(client, &event)) 
  {
        if (input_event_to_user(buffer + retval, &event))
            return -EFAULT;

        retval += input_event_size();
    }

    return retval;
}

在非阻塞狀況下, 假設消息隊列爲空時,則應用層的進程將會睡眠,直到被喚醒再進行消息讀取。此時,evdev_read進入休眠狀態,等待ecdev->wait變量被喚醒。在evdev_handler.event中,即evdev_event()函數中,ecdev->wait變量被喚醒。

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
    ... ...
    wake_up_interruptible(&evdev->wait);
}

evdev_event()函數被誰調用?由Linux內核 gpio_keys_isr()函數代碼可知,若底層輸入設備發生輸入事件,將觸發硬件中斷,在中斷服務函數中會調用input_event上報輸入事件,在input_event()函數內部調用了evdev_handler.event,即evdev_read將被喚醒。

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
   ... ...
  input_event(input, type, button->code, !!state);  //上報事件
  input_sync(input);                                //同步信號通知,表示事件發送完畢
}

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
  struct input_handle *handle;
  ... ...

  /* 經過input_dev ->h_list鏈表找到input_handle驅動處理結構體*/
  list_for_each_entry(handle, &dev->h_list, d_node)    
  if (handle->open)                        //若是input_handle以前open 過,那麼這個就是咱們的驅動處理結構體
      handle->handler->event(handle, type, code, value); //調用evdev_event()的.event事件函數 

}

5. input子系統的使用

(1)分配與釋放input_dev

1)分配input_dev

/*
*所在文件:/kernel/drivers/input/input.c
*參數:    
*            無
*返回值:    
*            struct input_dev *:指向申請的input_dev
*/
struct input_dev *input_allocate_device(void);

2)釋放input_dev

/*
*所在文件:/kernel/drivers/input/input.c
*參數:    
*            struct input_dev *dev:指向須要釋放的input_dev
*返回值:    
*            無
*/
void input_free_device(struct input_dev *dev);

(2)初始化input_dev

初始化一個input對象是使用input子系統編寫驅動的主要工做,內核在頭文件"include/uapi/linux/input.h"中規定了一些常見輸入設備的常見的輸入事件,這些宏和數組就是咱們初始化input對象的工具。這些宏同時用在用戶空間的事件解析和驅動的事件註冊,能夠看做是驅動和用戶空間的通訊協議,因此理解其中的意義十分重要。在input子系統中,每個事件的發生都使用事件(type)->子事件(code)->值(value)三級來描述,好比,按鍵事件->按鍵F1子事件->按鍵F1子事件觸發的值是高電平1。注意,事件和子事件和值是相輔相成的,只有註冊了事件EV_KEY,才能夠註冊子事件BTN_0,也只有這樣作纔是有意義的。

Linux內核定義的事件類型,對應事件對象的type域。每一類事件還有相應的子事件。

#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)

事件位圖:在Linux內核中,使用事件位圖來描述輸入設備所支持的事件類型或其子事件。事件位圖使用unsigned long變量來描述所支持的事件類型或其子事件,每個Bit表明一個事件類型或其子事件,該Bit爲1代表支持該事件類型或其子事件。

#define EV_CNT (EV_MAX+1)  //事件類型的最大個數
#define KEY_CNT (KEY_MAX+1) //按鍵事件的子事件最大個數
#define BITS_PER_BYTE        8
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))

//計算須要多少個long變量來描述EV_KEY的子事件(一個Bit描述一個子事件),即計算long類型數組變量的個數
#define BITS_TO_LONGS(nr)    DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

unsigned long evbit[BITS_TO_LONGS(EV_CNT)];  //使用一個unsigned long數組來描述該輸入設備的支持的事件類型,在struct input_dev中定義
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//使用一個unsigned long數組來描述該輸入設備的按鍵事件(EV_KEY)所支持的子事件,在struct input_dev中定義

Linux內核中還提供了相應的工具將這些事件正確的填充到input對象中描述事件的位圖中。

//第一種
//這種方式很是適合同時註冊多個事件
button_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);                                             //填充該設備所支持的事件類型
button_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) |BIT_MASK(BTN_MIDDLE);//填充該設備的按鍵事件所支持的子事件


//第二種
//一般用於只註冊一個事件
set_bit(EV_KEY,button_dev.evbit);//填充該設備所支持的事件類型
set_bit(BTN_0,button_dev.keybit);//填充該設備的按鍵事件所支持的子事件

(3)註冊與註銷input_dev

1)註冊input_dev

/*
*所在文件:/kernel/drivers/input/input.c
*參數:    
*            struct input_dev *dev:指向須要註冊的input_dev
*返回值:    
*            返回值爲0,註冊成功;不然,註冊失敗
*/
int nput_register_device(struct input_dev *dev);

2)註銷input_dev

/*
*所在文件:/kernel/drivers/input/input.c
*參數:    
*            struct input_dev *dev:指向須要註銷的input_dev
*返回值:    
*            無
*/
void input_unregister_device(struct input_dev *dev);

(4)驅動層報告事件

1)上報指定的事件

/*
*所在文件:/kernel/drivers/input/input.c
*參數:    
*            struct input_dev *dev:指向相應的輸入設備
*            unsigned int type:事件類型
*            unsigned int code:子事件
*            int value:子事件的值
*返回值:    
*            無
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

2)上報鍵值

/*
*所在文件:/kernel/include/linux/input.h
*參數:    
*            struct input_dev *dev:指向相應的輸入設備
*            unsigned int code:鍵盤事件的子事件
*            int value:子事件的值
*返回值:    
*            無
*/
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}

3)上報絕對事件

/*
*所在文件:/kernel/include/linux/input.h
*參數:    
*            struct input_dev *dev:指向相應的輸入設備
*            unsigned int code:絕對事件的子事件
*            int value:子事件的值
*返回值:    
*            無
*/
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_ABS, code, value);
}

4)上報相對事件

/*
*所在文件:/kernel/include/linux/input.h
*參數:    
*            struct input_dev *dev:指向相應的輸入設備
*            unsigned int code:相對事件的子事件
*            int value:子事件的值
*返回值:    
*            無
*/
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_REL, code, value);
}

5)同步全部的上報

/*
*所在文件:/kernel/include/linux/input.h
*參數:    
*            struct input_dev *dev:指向相應的輸入設備
*返回值:    
*            無
*/
static inline void input_sync(struct input_dev *dev)
{
    input_event(dev, EV_SYN, SYN_REPORT, 0);
}

 6. input子系統應用實例

驅動程序實例(四):按鍵驅動程序(platform + input子系統 + 外部中斷方式)。

相關文章
相關標籤/搜索