輸入子系統(tǒng)是為了將輸入設備的功能呈現(xiàn)給應用程序。
它支持 鼠標、鍵盤、蜂鳴器、觸摸屏、傳感器等需要不斷上報數(shù)據(jù)的設備。
簡單的例子
這個例子中的設備只有一個按鍵key,當key按下時,將產(chǎn)生中斷,內(nèi)核檢測到中斷并對其進行處理。
stat
ic struct input_dev *button_dev;? ? /*輸入設備結(jié)構(gòu)體*/??
/*中斷處理函數(shù)*/??
static irqreturn_t button_interrupt(int irq, void *dummy)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
{??
? ? /*向輸入子系統(tǒng)報告產(chǎn)生按鍵事件*/??
? ? input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);? ? ?
? ? /*通知接收者,一個報告發(fā)送完畢*/??
? ? input_sync(button_dev);? ? ? ? ? ? ?
? ? return IRQ_HAND
LED;? //?
}??
/*加載函數(shù)*/??
static int __init button_init(void)
{??
? ? int error;??
? ? /*申請中斷處理函數(shù)*/ //返回0表示成功,返回-INVAL表示無效
? ? if(request_irq(BUTTON_IRQ,button_interrupt,0,"button",NULL))
? ? {??
? ? ? ? /*申請失敗,則打印出錯
信息*/??
? ? ? ? printk(KERN_ERR "button.c:
Can't alloca
te irq %d\n", button_irq);??
? ? ? ? return -EBUSY;??
? ? }??
? ? /*分配一個設備結(jié)構(gòu)體*/?
? ? //將在 sys/class/input/input-n 下面創(chuàng)建設備屬性文件
? ? button_dev = input_allocate_device();? ? ?
? ? if (!button_dev)? ? ? ? ? ? ? ? ? ? ? ? /*判斷分配是否成功*/??
? ? {??
? ? ? ? printk(KERN_ERR "button.c: Not enough mem
ory\n");??
? ? ? ? error = -ENOMEM;??
? ? ? ? goto err_free_irq;??
? ? }??
? ? button_dev->evbit[0] = BIT_MASK(EV_KEY);? ? /*設置按鍵信息*/??
? ? button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);??
? ? error = input_register_device(button_dev);? /*注冊一個輸入設備*/??
? ? if (error)??
? ? {??
? ? ? ? printk(KERN_ERR "button.c: Failed to register device\n");??
? ? ? ? goto err_free_dev;??
? ? }??
? ? return 0;??
? ? err_free_dev:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/*以下是錯誤處理*/??
? ? ? ? input_free_device(button_dev);??
? ? err_free_irq:??
? ? ? ?free_irq(BUTTON_IRQ, button_interrupt);??
? ? return error;??
}??
static void __exit button_exit(void)? ? ? ? ? ? /*卸載函數(shù)*/??
{??
? ? input_unregister_device(button_dev);? ? ? ? /*注銷按鍵設備*/??
? ? free_irq(BUTTON_IRQ, button_interrupt);? ? ?/*釋放按鍵占用的中斷線*/??
}??
module_init(button_init);??
module_exit(button_exit);?
從這個簡單的例子中可以看到。
在初始化函數(shù) button_init() 中注冊了一個中斷處理函數(shù),然后調(diào)用 input_allocate_device() 函數(shù)分配了一個 input_dev 結(jié)構(gòu)體,并調(diào)用 input_register_device() 對其進行注冊。
在中斷處理函數(shù) button_interrupt() 中,實例將接收到的按鍵信息上報給 input 子系統(tǒng),從而通過 input子系統(tǒng),向用戶態(tài)程序提供按鍵輸入信息。
input 子系統(tǒng)的關鍵函數(shù)
input_allocate_device()
input_register_device()-》input_attach_handler()-》input_match_device()
input_allocate_device()
這個函數(shù)在內(nèi)存中為輸入設備結(jié)構(gòu)體分配一個空間,并對其主要成員進行初始化。
其代碼如下
?
struct input_dev *input_allocate_device(void)??
{??
? ? struct input_dev *dev;??
? ? dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);? ? ? ? ? ? ??
? ? /*分配一個input_dev結(jié)構(gòu)體,并初始化為0*/??
? ? if (dev) {??
? ? ? ? dev->dev.type = &input_dev_type;? ? ? ? /*初始化設備的類型*/??
? ? ? ? dev->dev.class = &input_class;? ? ? ? ? /*設置為輸入設備類*/??
? ? ? ? device_ini
tialize(&dev->dev);? ? ? ? ? ?/*初始化device結(jié)構(gòu)*/??
? ? ? ? mutex_init(&dev->mutex);? ? ? ? ? ? ? ? /*初始化互斥鎖*/??
? ? ? ? spin_lock_init(&dev->event_lock);? ? ? ?/*初始化事件自旋鎖*/??
? ? ? ? INIT_LIST_HE
AD(&dev->h_list);? ? ? ? ? ?/*初始化鏈表*/??
? ? ? ? INIT_LIST_HEAD(&dev->node);? ? ? ? ? ? ?/*初始化鏈表*/??
? ? ? ? __module_get(THIS_MODULE);? ? ? ? ? ? ? /*模塊引用技術(shù)加1*/??
? ? }??
? ? return dev;??
}?
---------------------?
其返回一個指向 input_dev 類型的指針,該結(jié)構(gòu)體是一個輸入設備結(jié)構(gòu)體,包含了輸入設備的相關信息(按鍵碼、設備名、支持的事件)。
input_register_device()
這個函數(shù)是輸入子系統(tǒng)核心(input core)提供的函數(shù)。它將input_dev 結(jié)構(gòu)體注冊到輸入子系統(tǒng)核心中(input_dev 結(jié)構(gòu)體必須由 input_allocate_device()函數(shù)來分配的)。
如果函數(shù)注冊失敗,必須調(diào)用 input_free_device() 函數(shù)來釋放分配的空間。
如果函數(shù)注冊成功,在卸載函數(shù)中應該調(diào)用 input_unregister_device() 函數(shù)來注銷輸入設備結(jié)構(gòu)體。
我們看一下函數(shù)原型:
?
int input_register_device(struct input_dev *dev)??
{?
? ? //定義一些函數(shù)中將用到的局部變量
? ? static atomic_t input_no = ATOMIC_INIT(0);??
? ? struct input_handler *handler;
? ? const char *path;
? ? int error;
? ? //設置 input_dev 所支持的事件類型,由 evbit 成員來表示。具體類型在后面歸納。
? ? __set_bit(EV_SYN, dev->evbit);??
? ? //初始化 timer
定時器,用來處理重復點擊按鍵。(去抖)
? ? init_timer(&dev->timer);??
? ? //如果 rep[REP_DELAY] 和 [REP_PERIOD] 沒有設值,則賦默認值。為了去抖。
? ? 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;??
? ? }??
? ? //檢查下列兩個函數(shù)是否被定義,沒有被定義則賦默認值。
? ? if (!dev->getkeycode)??
? ? ? ? dev->getkeycode = input_default_getkeycode;? //得到指定位置鍵值
? ? if (!dev->setkeycode)??
? ? ? ? dev->setkeycode = input_default_setkeycode;? //設置指定位置鍵值
? ? //設置 input_dev 中 device 的名字為 inputN
? ? //將如 input0 input1 input2 出現(xiàn)在 sysfs 文件系統(tǒng)中
? ? dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no) - 1);??
? ? //將 input->dev 包含的 device 結(jié)構(gòu)注冊到
Linux 設備模型中。
? ? //并在文件系統(tǒng)中表現(xiàn)出來
? ? error = device_add(&dev->dev);??
? ? if (error)??
? ? ? ? return error;??
? ? //打印設備的路徑并輸出調(diào)試信息
? ? path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);??
? ? printk(KERN_INFO "input: %s as %s\n",??
? ? ? ? dev->name ? dev->name :?
? ? ? ? ? ? "Unspecified device" , path ? : "N/A");??
? ? kfree(path);??
? ? error = mutex_lock_interruptible(&input_mutex);??
? ? if (error) {??
? ? ? ?device_del(&dev->dev);??
? ? ? ?return error;??
? ? }??
? ? //將 input_dev 加入 input_dev_list 鏈表中(這個鏈表中包含有所有 input 設備)
? ? list_add_t
ail(&dev->node, &input_dev_list);??
? ? list_for_each_entry(handler, &input_handler_list, node);
? ? //調(diào)用 input_attatch_handler()函數(shù)匹配 handler 和 input_dev。
? ? //這個函數(shù)很重要,在后面單獨分析。
? ? input_attach_handler(dev, handler);??
? ? input_wakeup_procfs_reade
rs();??
? ? mutex_unlock(&input_mutex);??
? ? return 0;??
}?
?
給 evbit 設置的,input_dev所支持的事件類型:
#define EV_SYN? ? ? ? ? 0x00? ? /*表示設備支持所有的事件*/??
#define EV_KEY? ? ? ? ? 0x01? ? /*鍵盤或者按鍵,表示一個鍵碼*/??
#define EV_REL? ? ? ? ? 0x02? ? /*鼠標設備,表示一個相對的光標位置結(jié)果*/??
#define EV_ABS? ? ? ? ? 0x03? ? /*手寫板產(chǎn)生的值,其是一個絕對整數(shù)值*/??
#define EV_MSC? ? ? ? ? 0x04? ? /*其他類型*/??
#define EV_LED? ? ? ? ? 0x11? ? /*LED燈設備*/??
#define EV_SND? ? ? ? ? 0x12? ? /*蜂鳴器,輸入聲音*/??
#define EV_REP? ? ? ? ? 0x14? ? /*允許重復按鍵類型*/??
#define EV_PWR? ? ? ? ? 0x16? ? /*
電源管理事件*/?
input_attatch_handler()
這個函數(shù)用來匹配 input_dev 和 handler,匹配成功才進行關聯(lián)。
函數(shù)代碼如下
?
static int input_attach_handler(struct input_dev *dev,?
struct? ?input_handler *handler)??
{??
? ? // input_device_id 這個結(jié)構(gòu)體表示設備的標識,存儲了設備信息。
? ? const struct input_device_id *id;? ? ? ?/*輸入設備的指針*/??
? ? int error;??
? ? //先判斷 handler 的 blacklist 有無賦值,然后判斷是否匹配
? ? //blacklist 是一個 input_device_id *類型,指向了一個表,表中存放的是該驅(qū)動程序應該忽略的設備
? ? if (handler->blacklist && input_match_device(handler->blacklist,? ? ? ? dev))??
? ? ? ? return -ENODEV;? ? ? ? ? ? ? ? ? ? ?
? ? /*** 設備和處理函數(shù)之間的匹配 ***/
? ? //匹配 handler->id_table指向的列表中的設備 和 dev->id 數(shù)據(jù)
? ? id = input_match_device(handler->id_table, dev);??
? ? if (!id)??
? ? ? ? return -ENODEV;??
? ? //匹配成功則調(diào)用 handler->connect,連接 handler 和 input_dev
? ? error = handler->connect(handler, dev, id);/*連接設備和處理函數(shù)*/??
? ? if (error && error != -ENODEV)??
? ? ? ? printk(KERN_ERR??
? ? ? ? ? ? "input: failed to attach handler %s to device %s, "??
? ? ? ? ? ? "error: %d\n",??
? ? ? ? ? ? handler->name, kobject_name(&dev->dev.kobj), error);??
? ? return error;??
}?
input_device_id 結(jié)構(gòu)體的定義:
struct input_device_id {
? ? kernel_ulong_t flags;? ? ? ? ? ?/*標志信息*/??
? ? __u16 bustype;? ? ? ? ? ? ? ? ? /*總線類型*/??
? ? __u16 vendor;? ? ? ? ? ? ? ? ? ?/*制造商ID*/??
? ? __u16 product;? ? ? ? ? ? ? ? ? /*
產(chǎn)品ID*/??
? ? __u16 version;? ? ? ? ? ? ? ? ? /*版本號*/??
? ? ...??
? ? kernel_ulong_t driver_info;? ? ?/*驅(qū)動額外的信息*/??
};?
input_match_device()
這個函數(shù)用來將 input_dev 和 handler 進行匹配。
handler-》id_table 中定義了其支持 input_dev 設備。
?
static const struct input_device_id *input_match_device(const struct??
? ? input_device_id *id,struct input_dev *dev)??
{??
? ? int i;??
? ? //匹配 id 和 dev->id 中的信息
? ? for (; 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;??
? ? ? ? MATCH_BIT(evbit,? EV_MAX);??
? ? ? ? MATCH_BIT(keybit, KEY_MAX);??
? ? ? ? MATCH_BIT(relbit, REL_MAX);??
? ? ? ? MATCH_BIT(absbit, ABS_MAX);??
? ? ? ? MATCH_BIT(mscbit, MSC_MAX);??
? ? ? ? MATCH_BIT(ledbit, LED_MAX);??
? ? ? ? MATCH_BIT(sndbit, SND_MAX);??
? ? ? ? MATCH_BIT(ffbit,? FF_MAX);??
? ? ? ? MATCH_BIT(swbit,? SW_MAX);??
? ? ? ? return id;??
? ? }??
? ? return NULL;??
}?
?
在上面,只有 flags 中的信息匹配成功,或者 flags 沒有定義才會調(diào)用下面。
#define MATCH_BIT(bit, max) \??
? ? ? ? for (i = 0; i < BITS_TO_LONGS(max); i++) \??
? ? ? ? ? ? if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \??
? ? ? ? ? ? ? ? break; \??
? ? ? ? if (i != BITS_TO_LONGS(max)) \??
? ? ? ? ? ? continue;?
?
從宏定義中可以看到,
只有當 input device和input handler 的 ID 成員在 evbit、keybit、… swbit 項相同才會匹配成功。而且匹配的順序是從evbit、keybit到swbit。只要有一項不同,就會循環(huán)到ID中的下一項進行比較。
總結(jié)
在 input 的分配和注冊中,我們分析了四個函數(shù)。
1. input_allocate_device 在內(nèi)存中為輸入設備結(jié)構(gòu)體分配空間并進行初始化。
2. input_register_device()-》input_attach_handler()-》input_match_device()
input_register_device
將input_dev 結(jié)構(gòu)體注冊到輸入子系統(tǒng)核心中。主要操作是 初始化 input_dev 并將其 device_add 進 Linux 設備驅(qū)動模型中(在文件系統(tǒng)中創(chuàng)建 inputN 等文件);打印其路徑;調(diào)用 input_attach_handler 匹配 handler 和 input_dev。
input_attach_handler
匹配 handler 和 input_dev。主要操作是,判斷 dev 在不在 devices 的黑名單中,不在就 調(diào)用 input_match_device 進行匹配,成功就調(diào)用 handler-》connect 連接設備和處理函數(shù)。
input_match_device
真正的 將 input_dev 和 handler 進行匹配。主要操作是匹配 id 和 dev-》id 中的信息。包括 bustype、vendor、product、version 等;再匹配 evbit 事件類型、keybit 按鍵類型 等。
評論
查看更多