input子系統——kernel中input設備介紹

一、輸入子系統驅動層分析


   在鍵盤驅動代碼分析的筆記中,接觸到了input子系統,鍵盤驅動,鍵盤驅動將檢測到的所有按鍵都上報給了input子系統。Input子系統是所有I/O設備驅動的中間層,爲上層提供了一個統一的界面。例如,在終端系統中,我們不需要去管有多少個鍵盤,多少個鼠標。它只要從input子系統中去取對應的事件(按鍵,鼠標移位等)就可以了。今天就對input子系統做一個詳盡的分析。   
     輸入子系統由驅動層、輸入子系統核心、事件處理層三部分組成。一個輸入事件,如鼠標移動、鍵盤按下等通過Driver->Inputcore->Event handler->userspace的順序到達用戶控件的應用程序,具體的流程可以用下圖描敘。

1、驅動層:將底層的硬件輸入轉化爲統一事件形式,向輸入核心(Input Core)彙報;6

2、輸入子系統核心:承上啓下,爲驅動層提供輸入設備註冊與操作接口,如:input_register_device,通知事件處理層對事件進行處理,在/Proc下產生相應的設備信息;

3、事件處理層:主要是和用戶空間交互。(Linux中在用戶空間將所有的設備都當初文件來處理,由於在一般的驅動程序中都有提供fops接口,以及在/dev下生成相應的設備文件nod,這些操作在輸入子系統中由事件處理層完成);

4、設備描述:


    struct input_dev {  
        const char *name;  //名字
        const char *phys;  
        const char *uniq;  
        struct input_id id;  //輸入id
        unsigned long evbit[NBITS(EV_MAX)];   // 表示能產生哪類事件  
        unsigned long keybit[NBITS(KEY_MAX)]; // 表示能產生哪些按鍵  
        unsigned long relbit[NBITS(REL_MAX)]; // 表示能產生哪些相對位移事件, x,y,滾輪  
        unsigned long absbit[NBITS(ABS_MAX)]; // 表示能產生哪些絕對位移事件, x,y  
        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)];  
        ...  
    }
     實現設備驅動核心工作是:向系統報告按鍵、觸摸屏等輸入事件event,通過input_event結構描述,不再需要關心文件操作接口。驅動報告事件經過inputCore和Eventhandler到達用戶空間。

    b、註冊輸入設備函數:int input_register_device(struct input_dev *dev) 

    c、註銷輸入設備函數:void input_unregister_device(struct input_dev *dev)

    d、驅動實現——初始化(事件支持):-->   probe函數

        set_bit()告訴input輸入子系統支持哪些事件,哪些按鍵。例如: 

      set_bit()告訴input輸入子系統支持哪些事件,哪些按鍵。例如: 
            /*  設置按鍵能產生哪類事件 */  
                set_bit(EV_KEY,buttons_dev->evbit);  
                set_bit(EV_REP,buttons_dev->evbit);  
  
            /* 設置能產生這類操作的哪些事件 */  
                set_bit(KEY_L,buttons_dev->keybit);  
                set_bit(KEY_S,buttons_dev->keybit);  
                set_bit(KEY_ENTER,buttons_dev->keybit);  
                set_bit(KEY_LEFTSHIFT,buttons_dev->keybit); 
        struct input_dev中有兩個成員爲:
            evbit:事件類型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
            在input.h中有這些類的定義:      
                #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     //LED或其他指示設備
                #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:按鍵類型(當事件類型爲EV_KEY時包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等) 
     e、驅動實現——報告事件:

         用於報告EV_KEY,EV_REL,EV_ABS事件的函數分別爲

             void input_report_key(struct   input_dev *dev,unsigned int code,int value)
             void input_report_rel(struct  input_dev *dev,unsigned int code,int value)
             void input_report_abs(struct   input_dev *dev,unsigned int code,int value)
     f、驅動實現——報告結束:

         input_sync()同步用於告訴input core子系統報告結束。

總結而言:

    probe爲input子系統做了三件事: 

    (1)首先調用input_allocate_device創建一個input_dev對象;

    (2)然後設置設備input_dev的各種屬性以告訴input core你將提供哪些事件;

    (3)最後調用input_register_device把input_dev註冊到input core;

實例分析(按鍵中斷程序):

//按鍵初始化
static int __init button_init(void)
{//申請中斷
        if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
              return –EBUSY;
        set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件 
        set_bit(BTN_0,button_dev.keybit); //支持設備兩個鍵
        set_bit(BTN_1,button_dev.keybit); // 
        input_register_device(&button_dev);//註冊input設備
}
/*在按鍵中斷中報告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
        input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//讀取寄存器BUTTON_PORT0的值
        input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
        input_sync(&button_dev);
}
總結:input子系統仍然是字符設備驅動程序,但是代碼量減少很多,input子系統只需要完成兩個工作:初始化和事件報告(這裏在linux中是通過中斷來實現的)。


二、input設備註冊分析
1、Input設備註冊的接口爲:input_register_device()

/kernel/driver/input/input.c


int input_register_device(struct input_dev *dev)
{
         //這個原子變量,代表總共註冊的input設備,每註冊一個加1,因爲是靜態變量,所以每次調用都不會清零的 
         static atomic_t input_no = ATOMIC_INIT(0);
         struct input_devres *devres = NULL;
         struct input_handler *handler;
         unsigned int packet_size;
         const char *path;
         int error;
         if (dev->devres_managed) {
                  devres = devres_alloc(devm_input_device_unregister,
                  sizeof(struct input_devres), GFP_KERNEL);
                  if (!devres)
                           return -ENOMEM;
                  devres->input = dev;
         }
         /* Every input device generates EV_SYN/SYN_REPORT events. */
         __set_bit(EV_SYN, dev->evbit);//EN_SYN是設備都要支持的事件類型所以要設置 
          /* KEY_RESERVED is not supposed to be transmitted to userspace. */ 
         __clear_bit(KEY_RESERVED, dev->keybit);
          /* Make sure that bitmasks not mentioned in dev->evbit are clean. */ 
         input_cleanse_bitmasks(dev);
         packet_size = input_estimate_events_per_packet(dev);
         if (dev->hint_events_per_packet < packet_size)
                  dev->hint_events_per_packet = packet_size;
                 dev->max_vals = max(dev->hint_events_per_packet, packet_size) + 2;
                 dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
                 if (!dev->vals) {
                          error = -ENOMEM;
                          goto err_devres_free;
                 }
         /*
            //rep主要是處理重複按鍵,如果沒有定義dev->rep[REP_DELAY]和dev->rep[REP_PERIOD], 
            //則將其賦值爲默認值。dev->rep[REP_DELAY]是指第一次按下多久算一次,這裏是250ms, 
            //dev->rep[REP_PERIOD]指如果按鍵沒有被擡起,每33ms算一次。 
        */
         init_timer(&dev->timer);//這個內核定時器是爲了重複按鍵而設置的
         if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
                  dev->timer.data = (long) dev;
                  dev->timer.function = input_repeat_key;
                  dev->rep[REP_DELAY] = 250;
                  dev->rep[REP_PERIOD] = 33;
                  //如果沒有定義有關重複按鍵的相關值,就用內核默認的
         }
        /*如果dev沒有定義getkeycode和setkeycode,則賦默認值。他們的作用一個是獲得鍵的掃描碼,一個是設置鍵的掃碼*/
         if (!dev->getkeycode)
                  dev->getkeycode = input_default_getkeycode;
         if (!dev->setkeycode) 
                  dev->setkeycode = input_default_setkeycode;
        //設置input_dev中device的名字,這個名字會在/class/input中出現
       dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no)-1); 
        //添加input設備,註冊到linux設備模型中,生成一系列的sys相關文件,udev會根據dev文件生成設備節點
        error = device_add(&dev->dev);
        if (error)         goto err_free_vals;
         path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
         pr_info("%s as %s\n",dev->name ? dev->name : "Unspecified device", path ? 
                                                                                                            path : "N/A");
         kfree(path);
         error = mutex_lock_interruptible(&input_mutex);
         if (error)        goto err_device_del;
/*     如果input device沒有定義getkeycode和setkeycode,則將其賦默認值,這兩個操作函數就可以用來取鍵的掃描碼和設置鍵的掃描碼,然後調用device_add()將input_dev中封裝的device註冊到sysfs;*/     
          //將新分配的input設備連接到input_dev_list鏈表上 
         list_add_tail(&dev->node, &input_dev_list);
         //遍歷input_handler_list鏈表,配對input_dev和input_handler
         list_for_each_entry(handler, &input_handler_list, node)
         input_attach_handler(dev, handler);
         input_wakeup_procfs_readers();//與proc文件系統有關
         mutex_unlock(&input_mutex);
         if (dev->devres_managed) {
                  dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
                   __func__, dev_name(&dev->dev));
                  devres_add(dev->dev.parent, devres);
         }
/*        這裏就是重點了,將input device 掛到input_dev_list鏈表上.然後,對每一個掛在input_handler_list的handler調用input_attach_handler(),在這裏的情況有好比設備模型中的device和driver的匹配,所有的input device都掛在input_dev_list鏈上。所有的handle都掛在input_handler_list上。*/
        下面介紹詳細的匹配過程:。。。。。
         return 0;
         ......
}

三、input_device和input_handle匹配過程
  1、一般來說input_handler的註冊會在input_dev之前註冊,常見的input_handler有:mousedev handler(處理來自鼠標類的Input事件),joydev_handler(處理搖桿類事件),kdev_handler(出來來自鍵盤類事件),evdev_handler(響應絕大部分的事件,默認的input處理事件)。先看input的handler的註冊函數,Handler註冊的接口如下所示: 


int input_register_handler(struct input_handler *handler) 

         struct input_dev *dev; 
         int retval;  
         retval = mutex_lock_interruptible(&input_mutex); 
         if (retval) 
                   return retval; 
         INIT_LIST_HEAD(&handler->h_list); 
         if (handler->fops != NULL) { 
                   if (input_table[handler->minor >> 5]) { //添加到全局數組中
        //爲什麼會這樣呢,因爲每個handler都會處理最大32個input_dev,所以要以minor的32爲倍數對齊,這個minor是傳進來的handler的MINOR_BASE 
        //每一個handler都有一個這一個MINOR_BASE,以evdev爲例,EVDEV_MINOR_BASE = 64,可以看出系統總共可以註冊8個handler 
                            retval = -EBUSY; 
                            goto out; 
                   } 
                   input_table[handler->minor >> 5] = handler; 
         } 
        //連接到input_handler_list鏈表中  
         list_add_tail(&handler->node, &input_handler_list); 
         list_for_each_entry(dev, &input_dev_list, node) 
         input_attach_handler(dev, handler);  //device和handle匹配函數
         input_wakeup_procfs_readers();   
 out: 
         mutex_unlock(&input_mutex); 
         return retval; 

        handler->minor表示對應input設備節點的次設備號,以handler->minor右移五位做爲索引值插入到input_table[ ]中.....之後再來分析input_talbe[ ]的作用,然後將handler掛到input_handler_list中,然後將其與掛在input_dev_list中的input device匹配,這個過程和input device的註冊有相似的地方,都是註冊到各自的鏈表,然後與另外一條鏈表的對象相匹配。


2、匹配是在input_attach_handler()中完成的。代碼如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
         const struct input_device_id *id;
         int error;
        //這個是主要的配對函數,主要比較id中的各項
         id = input_match_device(handler, dev);
         if (!id)      return -ENODEV; 
         //配對成功調用handler的connect函數,這個函數在事件處理器中定義,主要生成一個input_handle結構,並初始化,還生成一個事件處理器相關的設備結構
         error = handler->connect(handler, dev, id);//調用evdev_connect
         if (error && error != -ENODEV)
                  pr_err("failed to attach handler %s to device %s, error: %d\n",
                             handler->name, kobject_name(&dev->dev.kobj), error);
         return error;
}
        先來匹配handle->id和dev->id中的數據,如果匹配成功,則調用handler->connect()。每個handler在註冊的時候都有自己的id_table,如果設備和input handler能夠匹配成功的話,就會調用input handler的connect函數,在Input_match_device中會將input device的id.bus type vendor和id.version首先匹配,然後回去match的evbit等。

3、具體的數據匹配過程是在input_match_device()中完成的。代碼如下:
static const struct input_device_id *input_match_device(struct input_handler *handler,
       struct input_dev *dev) { 
         const struct input_device_id *id;     
         //id->driver_info=1,表示可以配對所有
         for (id = handler->id_table; id->flags || id->driver_info; id++) {
          if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
                   if (id->bustype != dev->id.bustype)
                        continue;
          if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
                   if (id->vendor != dev->id.vendor)
                        continue;
          if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
                   if (id->product != dev->id.product)
                        continue;
          if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
                   if (id->version != dev->id.version)
                        continue;
          if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
                   continue;
          if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
                   continue;
          if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
                   continue;
          if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
                   continue;
          if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
                   continue;
          if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
                   continue;
          if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
                   continue;
          if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
                   continue;
          if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
                   continue;
          if (!handler->match || handler->match(handler, dev))//沒有match函數
                   return id;
         }
         return NULL;
}   
        在id->flags中定義了要匹配的項,定義INPUT_DEVICE_ID_MATCH_BUS,則是要比較input device和input handler的總線類型。INPUT_DEVICE_ID_MATCH_VENDOR,INPUT_DEVICE_ID_MATCH_PRODUCT,INPUT_DEVICE_ID_MATCH_VERSION分別要求設備廠商。設備號和設備版本。

        如果id->flags定義的類型匹配成功,或者是id->flags沒有定義,就會進入到MATCH_BIT的匹配項了,從MATCH_BIT宏的定義可以看出,只有當iput device和input handler的id成員在evbit、keybit......swbit項相同纔會匹配成功。而且匹配的順序是從evbit,keybit到swbit.只要有一項不同,就會循環到id中的下一項進行比較。
        簡而言之,註冊input device的過程就是爲input device設置默認值,並將其掛以input_dev_list.與掛載在input_handler_list中的handler相匹配,如果匹配成功,就會調用handler的connect函數。   
      如果handler和device能夠匹配上,就會創建一個evdev,就會調用handler的match的回調函數,它裏邊封裝了一個handle,會把input_dev和input_handler關聯到一起。
    在evdev_connect,它設置設備名,初始化input_handle中各個數據成員(關鍵是其input_dev和input_handler),然後再調用input_register_handle把evdev中的input_handle添加到input_dev的h_lis鏈表中,並且把此input_handle添加到input_handler的h_list鏈表中。從此它們的三角關係建立完成。注:當用戶每打開一次它就要創建一個evdev_client,並加入到client_list鏈表中,當input_dev產生事件時,evdev_event函數將把此input_event放入evdev->client_list鏈表中的每個evdev_client的buffer中。它們的關係如下圖所示:


         可以看到evdev是作爲一個通用的handler去處理input_device的事件,也就是說一旦有設備註冊就會去調用evdev的connect函數。
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    struct evdev *evdev;
    int minor;
    int dev_no;
    int error;
    minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
    if (minor < 0) {
        error = minor;
        pr_err("failed to reserve new minor: %d\n", error);
        return error;
    }
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配evdev空間
    if (!evdev) {
        error = -ENOMEM;
        goto err_free_minor;
    }
    INIT_LIST_HEAD(&evdev->client_list);
    spin_lock_init(&evdev->client_lock);
    mutex_init(&evdev->mutex);
    init_waitqueue_head(&evdev->wait);
    evdev->exist = true;
    dev_no = minor;
    /* Normalize device number if it falls into legacy range */
    if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
        dev_no -= EVDEV_MINOR_BASE;
    dev_set_name(&evdev->dev, "event%d", dev_no);//命名/dev/evdev_num
 
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;
    //初始化evdev->dev結構,所屬類型指向input_class
    evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
    evdev->dev.class = &input_class;
    evdev->dev.parent = &dev->dev;
    evdev->dev.release = evdev_free;
    device_initialize(&evdev->dev);
 
    //註冊input->handle結構
    error = input_register_handle(&evdev->handle);
    if (error)
        goto err_free_evdev;
    //註冊字符設備,添加文件操作接口file_opertions
    cdev_init(&evdev->cdev, &evdev_fops);
    evdev->cdev.kobj.parent = &evdev->dev.kobj;
    error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
    if (error)
        goto err_unregister_handle;
 
    //將evdev->dev註冊到sysfs文件系統中
    error = device_add(&evdev->dev);
    if (error)
        goto err_cleanup_evdev;
    return 0;
 
 err_cleanup_evdev:
    evdev_cleanup(evdev);
 err_unregister_handle:
    input_unregister_handle(&evdev->handle);
 err_free_evdev:
    put_device(&evdev->dev);
 err_free_minor:
    input_free_minor(minor);
    return error;
}
 
static const struct file_operations evdev_fops = {
    .owner      = THIS_MODULE,
    .read       = evdev_read,
    .write      = evdev_write,
    .poll       = evdev_poll,
    .open       = evdev_open,
    .release    = evdev_release,
    .unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = evdev_ioctl_compat,
#endif
    .fasync     = evdev_fasync,
    .flush      = evdev_flush,
    .llseek     = no_llseek,
};

        這裏面又有個設備註冊的過程,重新創建了一個evdev設備,存在於/dev/input/eventX。這是Android上層需要直接操作的文件節點,就是在這個時候註冊的,input_register_handle也是將匹配好的input設備和input handler 分別加到自己的input設備鏈表和handler鏈表,Evdev對應的設備節點一般位於/dev/input/event0 ~ /dev/input/event4,理論上可以對應32個設備節點,分別代表被handler匹配的32個input device。 可以用cat  /dev/input/event0.然後移動鼠標或者鍵盤按鍵就會有數據輸出(兩者之間只能選一,因爲一個設備文件只能關能一個輸入設備),還可以往這個文件裏寫數據,使其產生特定的事件,。        
        到這裏整個kernel層的從設備的初始化到創建,再到與hal層交互的的工作就已經介紹完畢。
        以上文字敘述的具體流程用UML圖繪製如下圖所示:

--------------------- 
作者:frank-zyp 
來源:CSDN 
原文:https://blog.csdn.net/u013604527/article/details/53432623 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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