1. 程式人生 > >【輸入子系統】【input_allocate_device】【input_register_device】

【輸入子系統】【input_allocate_device】【input_register_device】

input_allocate_device

input_allocate_device是輸入核心層提供給裝置驅動層的介面函式,在裝置驅動層首先就會呼叫此函式為input_dev分配記憶體空間,同時input_core也會給他初始化一些input_dev的基本特徵。

函式定義如下:struct input_dev *input_allocate_device(void)

/**

 * input_allocate_device - allocate memory for new input device

 *

 * Returns prepared struct input_dev or %NULL.

 *

 * NOTE: Use input_free_device() to free devices that have not been

 * registered; input_unregister_device() should be used for already

 * registered devices.

 */

struct input_dev *input_allocate_device(void)// 分配input_dev的結構體

{

    // 定義原子量並初始化為0

    static atomic_t input_no = ATOMIC_INIT(0);// 實際上是一個原子量結構體

    struct input_dev *dev;

    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);

    // 分配成功之後進行初始化,使他具有所有的input裝置的共性特徵

    if (dev) {

        dev->dev.type = &input_dev_type;// 裝置類別

        dev->dev.class = &input_class;//從屬的類,所有的輸入裝置都屬於是input_class這一個類別

        device_initialize(&dev->dev);// 裝置初始化

        mutex_init(&dev->mutex);// 用於read和write函式的連續訪問互鎖

        spin_lock_init(&dev->event_lock);// 新增的事件上鎖

        init_timer(&dev->timer); //設定當有連擊時的延時定時器 

        INIT_LIST_HEAD(&dev->h_list);//handle連結串列 

        INIT_LIST_HEAD(&dev->node);//input_dev連結串列 

        // atomic_inc_return該函式對原子型別的變數v原子地增加1並且返回指向v的指標。

        dev_set_name(&dev->dev, "input%lu",

                 (unsigned long) atomic_inc_return(&input_no) - 1);// 對裝置的名稱進行操作

        __module_get(THIS_MODULE);// 獲取linux核心模組的具體資訊

    }

    return dev;

}

EXPORT_SYMBOL(input_allocate_device);// 註冊進核心,以後就可以拿來使用了

1.        裝置原子量計數

// 定義原子量並初始化為0

static atomic_t input_no = ATOMIC_INIT(0);// 實際上是一個原子量結構體

2.        分配input_dev空間

struct input_dev *dev;

dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);

3.        初始化\使他具有input_dev的基本特徵

// 分配成功之後進行初始化,使他具有所有的input裝置的共性特徵

if (dev) {

    dev->dev.type = &input_dev_type;// 裝置類別

    dev->dev.class = &input_class;//從屬的類,所有的輸入裝置都屬於是input_class這一個類別

    device_initialize(&dev->dev);// 裝置初始化

mutex_init(&dev->mutex);// 用於read和write函式的連續訪問互鎖

    spin_lock_init(&dev->event_lock);// 新增的事件上鎖

    init_timer(&dev->timer); //設定當有連擊時的延時定時器 

    INIT_LIST_HEAD(&dev->h_list);//handle連結串列 

    INIT_LIST_HEAD(&dev->node);//input_dev連結串列 

    // atomic_inc_return該函式對原子型別的變數v原子地增加1並且返回指向v的指標。

    dev_set_name(&dev->dev, "input%lu",

        (unsigned long)atomic_inc_return(&input_no) - 1);// 對裝置的名稱進行操作

    printk(" enter input_allocate_devic  and input%lu", (unsigned long)atomic_inc_return(&input_no));

// 在kernel4.4裡面,他的初始化是-1,且每次的值都沒有減去1,實際上就是依次的裝置號,

從0開始一直往後。

想要輸出這個值,需要這樣輸出。

printk(" enter input_allocate_device and input_no is input%d\n", atomic_read(&input_no));不可再在上面再做原子操作。

    __module_get(THIS_MODULE);// 獲取linux核心模組的具體資訊

}

1)         裝置類別

dev->dev.type = &input_dev_type;// 裝置類別

2)         裝置類別

dev->dev.class = &input_class;//從屬的類,所有的輸入裝置都屬於是input_class這一個類別

3)         裝置初始化

device_initialize(&dev->dev);// 裝置初始化

4)         RW連續訪問鎖

mutex_init(&dev->mutex);// 用於read和write函式的連續訪問互鎖

5)         新增事件鎖

spin_lock_init(&dev->event_lock);// 新增的事件上鎖

6)         連續點選延時計時器

init_timer(&dev->timer); //設定當有連擊時的延時定時器 

7)         初始化handle連結串列

INIT_LIST_HEAD(&dev->h_list);//handle連結串列 

8)         初始化input_dev連結串列

INIT_LIST_HEAD(&dev->node);//input_dev連結串列 

9)         裝置名稱

// atomic_inc_return該函式對原子型別的變數v原子地增加1並且返回指向v的指標。

dev_set_name(&dev->dev, "input%lu",

    (unsigned long)atomic_inc_return(&input_no) - 1);// 對裝置的名稱進行操作

10)     獲取核心模組資訊

__module_get(THIS_MODULE);// 獲取linux核心模組的具體資訊

4.        返回值

返回值是返回的input_dev的指標return dev;

input_register_device

input_register_device這個函式也是input_core提供給事件處理層的函式,在為input_dev分配完空間並註冊了一系列的事件和事件碼之後,就會執行這個函式將input_dev註冊進input_core。

實際上基本上每個裝置都是在分配空間之後再執行註冊函式,具體可以看到裡面的log是配套存在的。

1.        新增input_devres指標

Input_devres是和input_dev相關的一個結構體,這裡我將他視為指向input_dev的二級指標

struct input_devres *devres = NULL;// input_devres成員是一個input_dev的結構體指標,也就是說他實際上是一個二級指標

/*

struct input_devres {

struct input_dev *input;

};

*/

2.        新增input_handler指標

既然需要在這個函式裡面將input_dev和input_handler匹配成input_handle那就一定需要初始化一個這樣的指標才行啊!

struct input_handler *handler;

3.        設定屬性devres_managed

// 這個屬性值一般設定的都是0

if (dev->devres_managed) {// 屬性的devres_managed定義的是一個bool型別的變數,但是這個似乎在觸控式螢幕裡面不怎麼被使用。

    devres = devres_alloc(devm_input_device_unregister,

        sizeof(struct input_devres), GFP_KERNEL);

    if (!devres)

        return -ENOMEM;

    devres->input = dev;

}

4.        支援同步事件

EV_SYN/SYN_REPORT

/* Every input device generates EV_SYN/SYN_REPORT events. */

// 系統認為所有的input裝置都產生EV_SYN/SYN_REPORT類事件

__set_bit(EV_SYN, dev->evbit);

預設是所有裝置都支援同步型別的事件。

5.        清除保留值

/* KEY_RESERVED is not supposed to be transmitted to userspace. */

__clear_bit(KEY_RESERVED, dev->keybit);

清除是不是就是表示不支援保留位型別的事件呢!

6.        清除位掩碼

/* Make sure that bitmasks not mentioned in dev->evbit are clean. *///確保把在初始化時註冊的所有事件型別清零

input_cleanse_bitmasks(dev);

1.       初始化事件平均數

// hint_events_per_packet在一個數據包裡面,由input裝置產生事件的平均數

packet_size = input_estimate_events_per_packet(dev);//其中input_estimate_events_per_packet函式獲取的是一個裝置中包含的輸入事件的個數

if (dev->hint_events_per_packet < packet_size)// 儘量初始化時設定得大一點

    dev->hint_events_per_packet = packet_size;

hint_events_per_packet這個一般的初始化是0,所以一般都是會執行到這個if條件這裡的

packet_size是對於某個具體裝置而言具有具體的大小值,log輸出的平均的輸入事件的個數如下所示。

2.       初始化事件最大數

最大數為平均數+2

dev->max_vals = dev->hint_events_per_packet + 2;

3.       為輸入裝置的事件分配空間

Dev->vals實際上是一個input_value的結構體陣列。這個後面再input_event的時候會被使用到。

dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);// 申請空間並初始化為0

if (!dev->vals) {// dev->vals實際上是input_value的結構體

    error = -ENOMEM;

    goto err_devres_free; // 直接跑到前面那個去了,那個預設是會執行嗎?

    // 一般都是不會到這裡來的。

}

4.       定時器實現重複按鍵

一般情況下,這個定時器都會被初始化。

// 如果rep中的REP_DELAY和REP_PERIOD對應的欄位沒設定,系統就會給input裝置設定一個核心預設的重複按鍵事件的處理過程,具體是用定時器實現的   

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;//重複掃描的時間要小於33ms,input.c的input_repeat_key會隔rep[REP_PERIOD]呼叫一次

}

5.       初始化getkeycode

// 如果沒有定義這個可選的檢測對映鍵的值話,就按系統默認了,下面的setkeycode類似   

if (!dev->getkeycode)// getkeycode表示檢測對映鍵值

dev->getkeycode = input_default_getkeycode;

6.       初始化setkeycode

if (!dev->setkeycode)

         dev->setkeycode = input_default_setkeycode;

7.       新增Device到核心

// 將設備註冊到核心

error = device_add(&dev->dev);

if (error)

goto err_free_vals;

8.       獲取裝置路勁

// 呼叫者必需使用kfree()來釋放結果, 因為它的程式碼中有path = kzalloc(len, gfp_mask);

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

獲取裝置的路勁是對應的是之前分配空間時候的原子量計數

或者直接通過這個來看。

9.       上鎖

// 獲得核心互斥鎖,防止競爭嘛

error = mutex_lock_interruptible(&input_mutex);

if (error)

goto err_device_del;

10.  新增到輸入裝置連結串列

// 把input裝置新增到input_dev_list連結串列

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

其中dev->node實際上是一個input_dev的list_head,而input_dev_list是定義的一個全域性變數

11.  遍歷連結串列handler連結串列,attach, dev和handler

這個迴圈可能執行幾次,輸出的結果有看到輸出2次或者4次的

//遍歷input_handler_list連結串列,傳入的handler迴圈遍歷handler_list中的node子項,list_for_each_entry是核心定義的一個巨集,input_handler有一個子項叫做node

list_for_each_entry(handler, &input_handler_list, node)// 是定義的一個static的handler列表

input_attach_handler(dev, handler);// 匹配handler和dev

實際上看到的是除了ACCDET這個裝置執行了兩次之外,其餘的裝置都執行了4次。

但是實際匹配的話,他就只會匹配成功兩個handler一個evdev,一個是gpufreq_ib

且他們聲稱裝置的路勁是在/devices/virtual/input/input10,且後面的數字是隨機的。

a)     input_attach_handler

  1. input_match_device、匹配dev和handler

const struct input_device_id *id;

id = input_match_device(handler, dev);// 具體的匹配函式,匹配成功之後,返回input_device_id的值

if (!id)

return -ENODEV;

匹配主要分為兩個類別的匹配,一個是供應商資訊等等,另外一個就是點陣圖。

這些資訊的匹配都是在下面這個迴圈中進行。

const struct input_device_id *id;

for (id = handler->id_table; id->flags || id->driver_info; id++) {

// 正確返回input_device_id的指標

}

return NULL;// 錯誤返回NULL

其中這裡匹配的兩個handler的id_table均為

/*

為什麼evdev、gpufreq_ib這個handler基本上和所有的輸入裝置匹配,關鍵在於他其中的這個設定

static const struct input_device_id evdev_ids[] = {

  { .driver_info = 1 },    Matches all devices

  { },               Terminating zero entry

};

*/

1)        供應商資訊匹配

        // 下面這幾句就是匹配一下什麼匯流排型別了、生產廠商了,如果你看過觸控式螢幕驅動,你會發現在寫驅動時,的確定義這些引數,但是我感覺我好像沒有定義這些東西耶!

        // 實際上如果是可以匹配所有的輸入裝置的話,那這幾個選項也沒有什麼意義啊!

        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;

2)        點陣圖資訊匹配

點陣圖資訊主要在裝置驅動設定的是這三個選項evbit、keybit、absbit

    set_bit(EV_ABS, tpd->dev->evbit);

    set_bit(EV_KEY, tpd->dev->evbit);

    set_bit(ABS_X, tpd->dev->absbit);

    set_bit(ABS_Y, tpd->dev->absbit);

    set_bit(ABS_PRESSURE, tpd->dev->absbit);

#if !defined(CONFIG_MTK_S3320) && !defined(CONFIG_MTK_S3320_47)\

    && !defined(CONFIG_MTK_S3320_50) && !defined(CONFIG_MTK_MIT200) \

    && !defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) && !defined(CONFIG_MTK_S7020) \

    && !defined(CONFIG_TOUCHSCREEN_MTK_SYNAPTICS_3320_50)

    set_bit(BTN_TOUCH, tpd->dev->keybit);

#endif /* CONFIG_MTK_S3320 */

具體到這裡的匹配資訊是這樣的。

        // 匹配點陣圖,那就來看看這個點陣圖是如何進行匹配的,找一個沒有匹配上的專案來看看

        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;

3)        Handler的match函式

        // handler的match函式一般是沒有定義的。

        if (!handler->match || handler->match(handler, dev))

            return id;

實際上這個也有一點不明白的地方,比如說他的第一個device為什麼一個都沒有被匹配上,這些不是很重要的還是放在後面來看吧!

  1. Handler->connect、繫結dev和handler

匹配成功之後呼叫handler的connect函式,將dev和handler通過id繫結起來

error = handler->connect(handler, dev, id);// 匹配成功之後就把handler和dev通過input_device_id繫結起來

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

匹配的handler是evdev和gpufreq_ib,所以可以直接跳轉到這兩個函式裡面去看具體的handler的connect函式Evdev->connect以及函式mt_gpufreq_input_connect。

  1. 返回值

Normal返回0,錯誤返回錯誤碼,但是也不怎麼care他的返回值

12.  喚醒等待佇列

這個是哪個等待佇列呢?

input_wakeup_procfs_readers();

13.  解鎖

mutex_unlock(&input_mutex);

14.  devres加入到device的相關連結串列

if (dev->devres_managed) {

    dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", __func__, dev_name(&dev->dev));

    // 申請完struct devres就可以進行註冊,devres_add就是註冊函式,他把devres加入到device的相關連結串列中

    devres_add(dev->dev.parent, devres);

}

15.  返回值

正確返回0,錯誤就返回錯誤碼