天天看點

Linux核心的輸入子系統架構解析

我們自己寫驅動的流程一般是:

  1. 自己确定或由系統自動配置設定主裝置号;
  2. 建立fops結構;
  3. 使用register_chrdev在初始化函數中進行注冊;
  4. 定義入口函數MODULE_INIT()和出口函數MODULE_EXIT()。

但這種我們自己寫的驅動程式,隻有自己可以調用。因為這種驅動不标準,隻有别人知道驅動用法的情況下才能使用。當我們使用QT等标準程式時,這類标準程式不能打開像我們這樣的野驅動,是以,我們應該讓我們的驅動程式融入“标準”中去。這個标準就是linux提供的輸入子系統架構。

1、Linux輸入子系統架構

下圖是input輸入子系統架構:

Linux核心的輸入子系統架構解析

輸入子系統由核心層(Input Core)、驅動層、事件處理層(Event Handler)三部份組成。一個輸入事件,如滑鼠移動,鍵盤按鍵按下,joystick的移動等等通過input driver -> Input core -> Event handler -> userspace 到達使用者空間傳給應用程式。

怎麼寫符合輸入子系統架構的驅動程式?

  1. 配置設定一個input_dev結構體
  2. 編寫函數,設定結構體
  3. 驅動注冊
  4. 編寫硬體相關代碼,比如在中斷服務程式裡上報事件等

2、核心層:driver/input/input.c

分析一個驅動程式,首先看他的入口函數,即init初始化函數:

static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
};

static int __init input_init(void)
{
    int err;

    err = class_register(&input_class);
    if (err) {
        printk(KERN_ERR "input: unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();
    if (err)
        goto fail1;

    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;
}      
  • 在初始化函數中我們可以看出這隻是執行了一個普通的字元裝置注冊過程,建立了一個input類,在該類下并沒有建立具體裝置,其餘沒有什麼特别。在字元裝置注冊時的操作函數集合input_fops中隻有一個open函數,直覺上看一個open函數并不能執行read等操作。那我們分析一下這個open函數究竟做了些什麼:
static int input_open_file(struct inode *inode, struct file *file)
{
    struct input_handler *handler = input_table[iminor(inode) >> 5];
    const struct file_operations *old_fops, *new_fops = NULL;
    int err;

    /* No load-on-demand here? */
    if (!handler || !(new_fops = fops_get(handler->fops)))
        return -ENODEV;

    /*
     * That's _really_ odd. Usually NULL ->open means "nothing special",
     * not "no device". Oh, well...
     */
    if (!new_fops->open) {
        fops_put(new_fops);
        return -ENODEV;
    }
    old_fops = file->f_op;
    file->f_op = new_fops;

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

    if (err) {
        fops_put(file->f_op);
        file->f_op = fops_get(old_fops);
    }
    fops_put(old_fops);
    return err;
}      
  1. 其中​

    ​iminor(inode)​

    ​​函數調用了​

    ​MINOR(inode->i_rdev)​

    ​​讀取子裝置号,然後将子裝置除以32,找到新挂載的input驅動的數組号,然後放在​

    ​input_handler​

    ​​驅動處理函數​

    ​handler​

    ​中。因為輸入子系統支援的裝置大類就那麼幾項,每項支援最多32個裝置,除32意味着可将在這32區段的裝置都能準确定位到自己對應的大類上,這些裝置都可以使用對應大類的公共fops。
  2. 例如:輸入子系統的事件裝置evdev,次裝置号起始位置為64,之後32個裝置都屬于evdev裝置,​

    ​input_table[iminor(inode) >> 5]​

    ​,次裝置号64~95的裝置都對應input_table[2],這個數組位置指向的是evdev的handler,這個區段内裝置的fops,在這個函數中都會指向evdev裝置共用的fops,這樣就不用驅動編寫者自己編寫fops,直接使用該裝置類型下别人寫出的fops即可。
  3. 若handler有值,說明挂載有這個驅動。就将handler結構體裡的成員file_operations *fops賦到新的file_operations *new_fops裡面。
  4. 再将新的file_operations *new_fops賦到file-> file_operations *f_op裡, 此時input子系統的file_operations就等于新挂載的input驅動的file_operations結構體,即實作一個偷天換日的效果。
  5. 然後調用新挂載的input驅動的old_fops裡面的成員**.open**函數,打開驅動open函數
  • 為什麼除以32:linux輸入子系統,作為将輸入裝置标準化處理的一種方式,使别人使用這類驅動時不必關心驅動細節即可使用。輸入裝置分為好多類型,鍵盤類、滑鼠類、觸摸屏類等等,linux将輸入子系統裝置的主裝置号定為13,次裝置号以32為間隔細分了幾大類,(例如事件裝置evdev,屬于次裝置号為64起始向後32個成員都屬于事件裝置分段,64-95這些裝置都屬于事件裝置),這些裝置次裝置号除以32結果都是2,在調用驅動操作時他們都可以使用事件裝置提供的fops,進而不用驅動編寫者自己編寫fops,大大提高了驅動易用性
  • **分析:**struct input_handler *handler = input_table[iminor(inode) >> 5];
  • 将傳入的節點的次裝置号除以32(右移5位),并以其作為索引,将數組input_table[]中的某一項指派給handle。
  • 建立檔案操作結構體new_fops,并将其用剛剛的handle結構體中的fops初始化,實作複制。
  • 那麼存放各類輸入裝置的input_handler結構體數組input_table是由誰構造,怎麼初始化的呢?

    全局搜尋發現其被​

    ​input_register_handler​

    ​調用:
int input_register_handler(struct input_handler *handler)
{
    struct input_dev *dev;

    INIT_LIST_HEAD(&handler->h_list);
  //判斷傳進來的檔案操作結合不是空的進行進一步操作
    if (handler->fops != NULL) {

       //如果配置設定的位置已經有值不為空,說明此位置已經被占用,傳回EBUSY
        if (input_table[handler->minor >> 5])
            return -EBUSY;
         // 将 handler 放入 input_table數組,數組序号為次裝置号/32
        input_table[handler->minor >> 5] = handler;
    }
   // 将 handler 放入 input_handler_list 連結清單
    list_add_tail(&handler->node, &input_handler_list);

   // 取出 input_dev_list 連結清單中的每一個dev與該handler進行比對
    list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler);

    input_wakeup_procfs_readers();
    return 0;
}      
  • 全局搜尋​

    ​input_register_handler​

    ​,發現​

    ​evdev.c joydev.c mousedev.c​

    ​等等都是通過​

    ​input_register_handler​

    ​向核心層注冊自己的結構:
---- input_register_handler Matches (10 in 9 files) ----
evbug_init in evbug.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :  return input_register_handler(&evbug_handler);
evdev_init in evdev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :  return input_register_handler(&evdev_handler);
input.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) line 1182 : int input_register_handler(struct input_handler *handler)
input.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) line 1203 : EXPORT_SYMBOL(input_register_handler);
input.h (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\include\linux) line 1130 : int input_register_handler(struct input_handler *);
joydev_init in joydev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :    return input_register_handler(&joydev_handler);
kbd_init in keyboard.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\char) :  error = input_register_handler(&kbd_handler);
mousedev_init in mousedev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :    error = input_register_handler(&mousedev_handler);
rfkill_handler_init in rfkill-input.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\net\rfkill) :     return input_register_handler(&rfkill_handler);
tsdev_init in tsdev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :  return input_register_handler(&tsdev_handler);      
  • 分析其中一個​

    ​evdev.c​

    ​,其入口函數如下:
static int __init evdev_init(void)
{
    return input_register_handler(&evdev_handler);
}      
  • 看看​

    ​evdev_handler​

    ​結構體的定義:
static struct input_handler evdev_handler = {
    .event =    evdev_event,
    //.connect:連接配接函數,将裝置input_dev和某個input_handler建立連接配接
    .connect =  evdev_connect,

    .disconnect =   evdev_disconnect,
 
    //.fops:檔案操作結構體,其中evdev_fops函數就是自己的寫的操作函數,然後賦到.fops中
    .fops =     &evdev_fops,

     //.minor:用來存放次裝置号
     /*其中EVDEV_MINOR_BASE=64, 然後調用input_register_handler(&evdev_handler)後,由于EVDEV_MINOR_BASE/32=2,是以存到input_table[2]中,是以當open打開這個input裝置,就會進入 input_open_file()函數,執行evdev_handler-> evdev_fops -> .open函數*/
    .minor =    EVDEV_MINOR_BASE,

    .name =     "evdev",

    /*.id_table : 表示能支援哪些輸入裝置,比如某個驅動裝置的input_dev->的id和某個input_handler的id_table相比對,就會調用.connect連接配接函數*/
    .id_table = evdev_ids,
};

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,
};      
  • ​input_register_device()​

    ​函數,如何建立驅動裝置
int input_register_device(struct input_dev *dev)   //*dev:要注冊的驅動裝置
{
 ... ...
       list_add_tail(&dev->node, &input_dev_list);   //(1)放傳入連結表中
 ... ...
       list_for_each_entry(handler, &input_handler_list, node)  //(2)
          input_attach_handler(dev, handler); 
 ... ...
}      

步驟如下:

(1)将要注冊的input_dev驅動裝置鍊入input_dev_list連結清單中,其中input_handler_list在前面講過,就是存放每個input_handle驅動處理結構體。

(2)然後list_for_each_entry()函數會将每個input_handle從連結清單中取出并放到handler中,然後再調用input_attach_handler()函數,判斷每個input_handle的id_table能否支援目前dev裝置,若兩者支援便進行連接配接。

  • 回過頭來看注冊​

    ​input_handler​

    ​的​

    ​input_register_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]) {
            retval = -EBUSY;
            goto out;
        }
        input_table[handler->minor >> 5] = handler;
    }

    list_add_tail(&handler->node, &input_handler_list);

    list_for_each_entry(dev, &input_dev_list, node)//在裝置清單中找出裝置結構體
        input_attach_handler(dev, handler);//裝置結構體中的id與handler中的id進行比對

    input_wakeup_procfs_readers();

 out:
    mutex_unlock(&input_mutex);
    return retval;
}      

發現:不管新添加input_dev還是input_handler,都會調用input_attach_handler()判斷兩者id是否比對, 若兩者比對便進行連接配接

  • ​input_attach_handler()​

    ​如何實作比對兩者id
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 的connect函數建立連接配接

  • 以​

    ​evdev.c​

    ​(事件驅動) 的​

    ​evdev_handler->connect​

    ​函數來分析是怎樣建立連接配接的
  • ​evdev_handler​

    ​​的​

    ​.connect​

    ​函數是​

    ​evdev_connect()​

    ​,代碼如下:
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, devt,dev->cdev.dev, evdev->name); 
                                                           // (3)在input類下建立驅動裝置

... ...
error = input_register_handle(&evdev->handle); //(4)注冊這個input_handle結構體

... ...
}      
  • 是在儲存驅動裝置名字,名為​

    ​event%d​

    ​, 因為沒有設定子裝置号,預設從小到大排列,其中​

    ​event0​

    ​是表示這個​

    ​input​

    ​子系統,是以這個鍵盤驅動名字就是​

    ​event1​

  • 是在儲存驅動裝置的主次裝置号,其中主裝置号​

    ​INPUT_MAJOR=13​

    ​,因為​

    ​EVDEV_MINOR_BASE=64​

    ​,是以此裝置号=​

    ​64​

    ​+驅動程式本事子裝置号
  • 在之前在​

    ​2​

    ​小結裡就分析了​

    ​input_class​

    ​類結構,會在​

    ​/sys/class/input​

    ​類下建立驅動裝置​

    ​event%d​

  • 最終會進入​

    ​input_register_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;
}      
  • 因為​

    ​handle->dev​

    ​​指向​

    ​input_dev​

    ​​驅動裝置,是以就是将​

    ​handle->d_node​

    ​​放入到​

    ​input_dev​

    ​​驅動裝置的​

    ​h_list​

    ​​連結清單中,即​

    ​input_dev​

    ​​驅動裝置的​

    ​h_list​

    ​​連結清單就指向​

    ​handle->d_node​

    ​。
  • 同樣,​

    ​input_handler​

    ​​驅動處理結構體的​

    ​h_list​

    ​​也指向了​

    ​handle->h_node​

    ​​,兩者的​

    ​.h_list​

    ​​都指向了同一個​

    ​handle​

    ​​結構體,然後通過​

    ​.h_list​

    ​​ 來找到​

    ​handle​

    ​​的成員​

    ​.dev​

    ​​和​

    ​handler​

    ​,便能找到對方建立連接配接了。
  • 建立了連接配接後,如何讀取​

    ​evdev.c​

    ​(事件驅動) 的​

    ​evdev_handler->.fops->.read​

    ​函數呢?

事件驅動的​

​.read​

​​函數是​

​evdev_read()​

​函數,我們來分析下:

static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
{
 ... ...
/*判斷應用層要讀取的資料是否正确*/
if (count < evdev_event_size())
return -EINVAL;

/*在無資料且是非阻塞操作情況下打開,則立刻傳回*/
 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);

  ... ...           //上傳資料
}      
  • 若read函數進入了休眠狀态,又是誰來喚醒

通過搜尋​

​evdev->wait​

​​這個等待隊列變量,找到​

​evdev_event​

​被誰喚醒:

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
 wake_up_interruptible(&evdev->wait);   //有事件觸發,便喚醒等待中斷
}      

其中​

​evdev_event()​

​​是​

​evdev.c​

​​(事件驅動)的​

​evdev_handler->.event​

​​成員。當有事件發生了(比如對于按鍵驅動,當有按鍵按下時),就會進入​

​.event​

​函數中去處理事件。

  • 是誰調用​

    ​evdev_event()​

    ​這個​

    ​.event​

    ​事件驅動函數?

應該就是之前分析的​

​input_dev​

​​那層調用的,我們來看看核心 ​

​gpio_keys_isr()​

​​函數代碼例子就知道了​

​(driver/input/keyboard/gpio_key.c)​

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
 /*擷取按鍵值,賦到state裡*/
 ... ...
/*上報事件*/
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事件函數 
}      

總結:

  • 1.注冊輸入子系統,進入​

    ​input_init()​

    ​:
  • 1)建立主裝置号為​

    ​13​

    ​​的input字元裝置:​

    ​register_chrdev(INPUT_MAJOR, "input", &input_fops);​

  • 2.open打開驅動,進入input_open_file():
  • 1)更新裝置的file_oprations:​

    ​file->f_op=fops_get(handler->fops);​

  • 2)執行file_oprations->open函數:​

    ​new_fops->open(inode, file);​

  • 3.注冊input_handler,進入input_register_handler():
  • 1)添加到input_table[]處理數組中:​

    ​input_table[handler->minor >> 5] = handler;​

  • 2)添加到input_handler_list連結清單中:​

    ​list_add_tail(&handler->node, &input_handler_list);​

  • 3)判斷input_dev的id,看是否有支援這個驅動的裝置:
  • 周遊查找input_dev_list連結清單裡所有input_dev:​

    ​list_for_each_entry(dev, &input_dev_list, node)​

    ​。
  • 判斷兩者id,若兩者支援便進行連接配接:​

    ​input_attach_handler(dev, handler);​

  • 4.注冊input_dev,進入input_register_device():
  • 1)放在input_dev_list連結清單中:​

    ​list_add_tail(&dev->node, &input_dev_list);​

  • 2)判斷input_handler的id,是否有支援這個裝置的驅動:
  • 周遊查找input_handler_list連結清單裡所有input_handler:​

    ​list_for_each_entry(handler, &input_handler_list, node)​

  • 判斷兩者id,若兩者支援便進行連接配接:​

    ​input_attach_handler(dev, handler);​

  • 5.判斷input_handler和input_dev的id,進入input_attach_handler():
  • 1)比對兩者id:​

    ​input_match_device(handler->id_table, dev);​

  • 2)比對成功,則建立連接配接。調用:​

    ​input_handler ->connec(handler, dev, id);​

  • 6.建立input_handler和input_dev的連接配接,進入input_handler->connect():
  • 1)建立全局結構體,通過input_handle結構體連接配接雙方
  • 建立兩者連接配接的input_handle全局結構體:​

    ​evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);​

  • 連接配接input_dev->h_list:​

    ​list_add_tail(&handle->d_node, &handle->dev->h_list);​

  • 連接配接input_handle->h_list:​

    ​list_add_tail(&handle->h_node, &handler->h_list);​

  • 7.有事件發生時(比如按鍵中斷),在中斷函數中需要進入input_event()上報事件:
  • 1)找到驅動處理結構體,然後執行input_handler->event():
  • 通過input_dev ->h_list連結清單找到input_handle驅動處理結構體:​

    ​list_for_each_entry(handle, &dev->h_list, d_node)​

  • 如果input_handle之前open 過,那麼這個就是我們的驅動處理結構體(有可能一個驅動裝置在不同情況下有不同的驅動處理方式):​

    ​if (handle->open)​

  • 調用evdev_event()的.event事件函數:​

    ​handle->handler->event(handle, type, code, value);​

繼續閱讀