【輸入子系統】【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
- 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為什麼一個都沒有被匹配上,這些不是很重要的還是放在後面來看吧!
- 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。
- 返回值
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,錯誤就返回錯誤碼