一:前言
最近在研究android的sensor driver,主要是E-compass,其中用到了Linux input子系統.在網上也看了很多這方面的資料,感覺還是這篇分析的比較細緻透徹,因此轉載一下以便自己學習,同時和大家分享!
(這篇博客主要是以鍵盤驅動爲例的,不過講解的是Linux Input Subsystem,可以仔細的研究一下!)
鍵盤驅動將檢測到的所有按鍵都上報給了input子系統。Input子系統是所有I/O設備驅動的中間層,爲上層提供了一個統一的界面。例如,在終 端系統中,我們不需要去管有多少個鍵盤,多少個鼠標。它只要從input子系統中去取對應的事件(按鍵,鼠標移位等)就可以了。
二:使用input device的例子
下面的代碼是基於linux kernel 2.6.25.分析的代碼主要位於kernel2.6.25/drivers/input下面.
在內核自帶的文檔Documentation/input/input-programming.txt中。有一個使用input子系統的例子,並附帶相應的說明。以此爲例分析如下:
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h>
static struct input_dev *button_dev;
static irqreturn_t button_interrupt(int irq, void *dummy)
{
input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
input_sync(button_dev);
return IRQ_HANDLED;
}
static int __init button_init(void)
{
int error;
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
button_dev = input_allocate_device();
if (!button_dev) {
printk(KERN_ERR "button.c: Not enough memory\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)
{
input_unregister_device(button_dev);
free_irq(BUTTON_IRQ, button_interrupt);
}
module_init(button_init);
module_exit(button_exit);
這個示例module代碼還是比較簡單,在初始化函數裏註冊了一箇中斷處理例程。然後註冊了一個input device.在中斷處理程序裏,將接收到的按鍵上報給input子系統。文檔的作者在之後的分析裏又對這個module作了優化。主要是在註冊中斷處理 的時序上。在修改過後的代碼裏,爲input device定義了open函數,在open的時候再去註冊中斷處理例程(Gick:因爲init裏首先註冊中斷,但是這個中斷裏上報的按鍵事件依賴中斷註冊後面的註冊按鍵事件,所以時序有問題)。具體的信息請自行參考這篇文檔。在資料缺乏的情況下,kernel自帶的文檔就 是剖析kernel相關知識的最好資料.文檔的作者還分析了幾個api函數。列舉如下:
1): set_bit(EV_KEY, button_dev.evbit);
set_bit(BTN_0, button_dev.keybit);
分別用來設置設備所產生的事件以及上報的按鍵值。Struct iput_dev中有兩個成員,一個是evbit.一個是keybit.分別用表示設備所支持的事件和按鍵類型。
2): input_register_device(&button_dev);
用來註冊一個input device.
3): input_report_key()
用於給上層上報一個按鍵動作
4): input_sync()
用來告訴上層,本次的事件已經完成了.
5): NBITS(x) - returns the length of a bitfield array in longs for x bits
LONG(x) - returns the index in the array in longs for bit x
BIT(x) - returns the index in a long for bit x
這幾個宏在input子系統中經常用到。上面的英文解釋已經很清楚了。
三:input設備註冊分析
Input設備註冊的接口爲:input_register_device()。代碼如下:
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
__set_bit(EV_SYN, dev->evbit);
/*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
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;
}
Input_device的evbit表示該設備所支持的事件。在這裏將其EV_SYN置位,即所有設備都支持這個事件.如果 dev->rep[REP_DELAY]和dev->rep[REP_PERIOD]沒有設值,則將其賦默認值。這主要是處理重複按鍵的.(這個地方還沒有仔細研究過,有點疑問 )
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
error = device_add(&dev->dev);
if (error)
return error;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
}
如果input device沒有定義getkeycode和setkeycode.則將其賦默認值。然後調用device_add()將input_dev中封裝的device註冊到sysfs。
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
這裏就是重點了。將input device 掛到input_dev_list鏈表上.然後,對每一個掛在input_handler_list的handler調用 input_attach_handler().這裏的情況好比設備模型中的device和driver的匹配。所有的input device都掛在input_dev_list鏈上。所有的handle都掛在input_handler_list上。
看一下這個匹配的詳細過程。匹配是在input_attach_handler()中完成的。代碼如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
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;
}
如果handle的blacklist被賦值。要先匹配blacklist中的數據跟dev->id的數據是否匹配。匹配成功過後再來匹 配handle->id和dev->id中的數據。如果匹配成功,則調用handler->connect().
來看一下具體的數據匹配過程,這是在input_match_device()中完成的。代碼如下:
static const struct input_device_id *input_match_device(const struct input_device_id *id, struct input_dev *dev)
{
int i;
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(,, 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;
}
MATCH_BIT宏的定義如下:
#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;
由此看到。在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註冊分析
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);
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的註冊有相似的地方.都是註冊到各自的鏈表,.然後與另外一條鏈表的對象相匹配.
五:handle的註冊
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
/*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
list_add_tail_rcu(&handle->d_node, &dev->h_list);
mutex_unlock(&dev->mutex);
synchronize_rcu();
/*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
list_add_tail(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
在這個函數裏所做的處理其實很簡單.將handle掛到所對應input device的h_list鏈表上. 再將handle掛到對應的handler的h_list鏈表上.如果handler定義了start函數,將調用之.
到這裏,我們已經看到了input device, handler和handle是怎麼關聯起來的了.以圖的方式總結如下:
但這個圖上顯示: 在Handler->event中調用input_register_handle(),但是我在code裏面看到
/**
* input_register_handle - register a new input handle
* @handle: handle to register
*
* This function puts a new input handle onto device's
* and handler's lists so that events can flow through
* it once it is opened using input_open_device().
*
* This function is supposed to be called from handler's
* connect() method.
*/
建議在connect中調用input_register_handle(),這裏也是有點疑問的。
六:event事件的處理
在開始的時候曾以linux kernel文檔中自帶的代碼作分析.提出了幾個事件上報的API. 這些API其實都是input_event()的封裝.代碼如下:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
unsigned long flags;
//判斷設備是否支持這類事件
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
//利用鍵盤輸入來調整隨機數產生器
add_input_randomness(type, code, value);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
首先,先判斷設備產生的這個事件是否合法.如果合法,流程轉入到input_handle_event()中.
代碼如下:
static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
if (!dev->sync) {
dev->sync = 1;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
}
break;
case EV_KEY:
//判斷按鍵值是否被支持
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) {
if (value != 2) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
}
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) {
__change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX)) {
value = input_defuzz_abs_event(value,
dev->abs[code], dev->absfuzz[code]);
if (dev->abs[code] != value) {
dev->abs[code] = value;
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS;
break;
case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL;
break;
case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) {
__change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) {
if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_REP:
if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_FF:
if (value >= 0)
disposition = INPUT_PASS_TO_ALL;
break;
case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
}
if (type != EV_SYN)
dev->sync = 0;
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event (dev, type, code, value);
}
在這裏, 忽略掉具體事件的處理. 到最後,如果該事件需要input device來完成的,就會將disposition設置成INPUT_PASS_TO_DEVICE.如果需要handler來完成的,就將 dispostion設爲INPUT_PASS_TO_HANDLERS.如果需要兩者都參與,將disposition設置爲 INPUT_PASS_TO_ALL.
需要輸入設備參與的,回調設備的event函數.如果需要handler參與的.調用input_pass_event().代碼如下:
static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
rcu_read_lock();
handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle,
type, code, value);
rcu_read_unlock();
}
如果input device被強制指定了handler,則調用該handler的event函數.
結合handle註冊的分析.我們知道.會將handle掛到input device的h_list鏈表上.
如果沒有爲input device強制指定handler.就會遍歷input device->h_list上的handle成員.如果該handle被打開,則調用與輸入設備對應的handler的event()函數.注意,只有在handle被打開的情況下才會接收到事件.
另外,輸入設備的handler強制設置一般是用帶EVIOCGRAB標誌的ioctl來完成的.如下是發圖的方示總結evnet的處理過程:
到此,已經分析了input device,handler和handle的註冊過程以及事件的上報和處理.下面以evdev爲實例做分析.來貫穿理解一下整個過程.
七:evdev概述
Evdev對應的設備節點一般位於/dev/input/event0 ~ /dev/input/event4.理論上可以對應32個設備節點.分別代表被handler匹配的32個input device.
可以用cat /dev/input/event0.然後移動鼠標或者鍵盤按鍵就會有數據輸出(兩者之間只能選一.因爲一個設備文件只能關能一個輸入設備).還可以往這個文件裏寫數據,使其產生特定的事件.這個過程之後再詳細分析.
爲了分析這一過程,必須從input子系統的初始化說起.
八:input子系統的初始化
Input子系統的初始化函數爲input_init().代碼如下:
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 device都屬於這個類.在sysfs中表現就是.所有input device所代表的目錄都位於/dev/class/input下面.
然後調用input_proc_init()在/proc下面建立相關的交互文件.
最後調用register_chrdev()註冊了主設備號爲INPUT_MAJOR(13).次設備號爲0~255的字符設備.它的操作指針爲input_fops.
這裏可以看到.所有主設備號13的字符設備的操作最終都會轉入到input_fops中.在前面分析的/dev/input/event0~/dev/input/event4的主設備號爲13.操作也不例外的落在了input_fops中.
Input_fops定義如下:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
打開文件所對應的操作函數爲input_open_file.代碼如下示:
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;
iminor(inode)爲打開文件所對應的次設備號.input_table是一個struct input_handler全局數組.先將設備結點的次設備號右移5位做爲索引值到input_table中取對應項.這裏可以看到.一 個handler代表1<<5個設備節點(因爲在input_table中取值是以次備號右移5位爲索引的.即低5位相同的次備號對應的是同一 個索引).終於看到了input_talbe大顯身手的地方了.input_talbe[ ]中取值和input_talbe[ ]的賦值,這兩個過程是相對應的.
在input_table中找到對應的handler之後,就會檢驗這個handler是否存,是否帶有fops文件操作集.如果沒有.則返回一個設備不存在的錯誤.
/*
* 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;
}
然後將handler中的fops替換掉當前file的fops.如果handler的fops中有open()函數,則調用它.(如果以evdev爲例的話,這裏的open()函數應該就是evdev_open()函數)
九:evdev的初始化
Evdev的模塊初始化函數爲evdev_init().代碼如下:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
它調用了input_register_handler註冊了一個handler.
注意到,在這裏evdev_handler中定義的minor爲EVDEV_MINOR_BASE(64).也就是說evdev_handler所表示的設備文件範圍爲(13,64)~(13,64+32).
從之前的分析知道.匹配成功的關鍵在於handler中的blacklist和id_talbe. Evdev_handler的id_table定義如下:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
它沒有定義flags.也沒有定義匹配屬性值.這個handler是匹配所有input device的.匹配成功之後會調用handler->connect函數.
在Evdev_handler中,該成員函數如下所示:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error;
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;
if (minor == EVDEV_MINORS) {
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE;
}
EVDEV_MINORS定義爲32.表示evdev_handler所表示的32個設備文件.evdev_talbe是一個struct evdev類型的數組.struct evdev是模塊使用的封裝結構.在接下來的代碼中可以看到這個結構的使用.
這一段代碼的在evdev_talbe找到爲空的那一項.minor就是數組中第一項爲空的序號.
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
evdev->exist = 1;
evdev->minor = minor;
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;
evdev->handle.private = evdev;
接下來,分配了一個evdev結構,並對這個結構進行初始化.這個結構封裝了一個handle結構,這結構與之前所討論的handler是不相同的.注意有一個字母的差別哦.可以把handle看成是handler和input device的信息集合體.在這個結構裏集合了匹配成功的handler和input device
strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id));
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
在這段代碼裏主要完成evdev封裝的device的初始化.注意在這裏,使它所屬的類指向input_class.這樣在/sysfs中創建的設備目錄就會在/sys/class/input/下面顯示.
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;
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);
return error;
}
註冊handle,如果成功,那麼調用evdev_install_chrdev將evdev_table的minor項指向evdev. 然後將evdev->device註冊到sysfs.如果失敗,則進行相關的錯誤處理.
萬事俱備了,但是要接收事件,還得要等”東風”.這個”東風”就是要打開相應的handle.這個打開過程是在文件的open()中完成的.