輸入子系統驅動
1.輸入子系統的驅動的框架結構體
1.1核心層 :提供時間處理層和裝置驅動層的註冊、登出,匹配過程
1.2事件處理層 :嚮應用層提供操作的介面如mouse.c evdev.c
1.3裝置驅動層 :讀取事件,上報事件
2.輸入子系統的目錄結構體
input.c
:核心層
evdev.c mousedev.c
:事件處理層
gpio_keys.c gt818.c
:裝置驅動層
3.input子系統的註冊和登出函式
事件處理層
int input_register_handler(struct input_handler *handler) void input_unregister_handler(struct input_handler *handler)
裝置驅動層
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
4.事件處理層註冊handler的過程 我們以linux-3.4/drivers/input/evdev.c為例
從上面的程式碼我們能夠分析看出來,這裡就是定義了一個input_handler結構體
將結構體的成員填充完成之後,通過input_register_handler函式註冊到核心中
接下來我們分析下注冊handler的過程。
input_register_handler(&evdev_handler); input_table[handler->minor >> 5] = handler; //將上面定義的結構體,填充到input_table陣列中 list_add_tail(&handler->node, &input_handler_list); //將註冊的該節點放在input_handler_list這條連結串列中 list_for_each_entry(dev, &input_dev_list, node) //遍歷input_dev_list這條連結串列 input_attach_handler(dev, handler); //那handler在input_dev_list這條連結串列上進行匹配 id = input_match_device(handler, dev); //開始匹配 for (id = handler->id_table; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) if (id->bustype != dev->id.bustype) id->flags //在前面沒有定義,它可以是匯流排型別,廠商,id等成員 MATCH_BIT(evbit, EV_MAX); MATCH_BIT(keybit, KEY_MAX); MATCH_BIT(relbit, REL_MAX); MATCH_BIT(absbit, ABS_MAX); #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; //這裡的continue是作用到外層迴圈的 //如果evbit,keybit,relbit等有一項不相等就接著匹配下一個input_dev, //如果上面的都匹配成功,就會執行handler中的match函式 if (!handler->match || handler->match(handler, dev)) //在evdev中並沒有對handler結構體中的match成員初始化,取反之後為真 return id;返回匹配到的id成員。 error = handler->connect(handler, dev, id); .connect = evdev_connect, struct evdev *evdev; evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); dev_set_name(&evdev->dev, "event%d", minor); evdev->handle.dev = input_get_device(dev); evdev->handle.handler = handler; //將匹配到的dev和handler放到handle結構體中 error = input_register_handle(&evdev->handle); list_add_tail_rcu(&handle->d_node, &dev->h_list); //將d_node新增到dev的h_list中 list_add_tail_rcu(&handle->h_node, &handler->h_list);//將h_node新增到handler的h_list中 //這裡我們需要回顧一下input_handler和input_dev結構體關於連結串列的成員 struct input_handler { struct list_head h_list; struct list_head node; ... }; struct input_dev { struct list_head h_list; struct list_head node; ... };
我們可以看到這兩個結構體中都有h_list和node這兩條連結串列,這兩條連結串列都是儲存的什麼成員呢?
input_handler結構體的node成員會放入input_handler_list連結串列。input_dev結構體的node成員會放入input_dev_list連結串列。也就是 說node是為了儲存自己的。而匹配到之後首先會構建input_handle結構體,然後將這個結構體分別放在兩個結構體體的h_list的連結串列 中。
5.裝置驅動層註冊過程
我們以fspad-733/lichee/linux-3.4/drivers/input/keyboard/gpio_keys.c為例子
從這個驅動的probe函式開始分析gpio_keys_probe
struct input_dev *input;
input = input_allocate_device(); //為input結構體分配空間,我們可以看下它的實現
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev;
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
if (dev) {
dev->dev.type = &input_dev_type; //建立裝置屬性檔案
dev->dev.class = &input_class; //填充匯流排型別為input
device_initialize(&dev->dev); //初始device結構體,包括kobject的資訊
INIT_LIST_HEAD(&dev->h_list); //初始化前面分析的兩條連結串列
INIT_LIST_HEAD(&dev->node);
input->open = gpio_keys_open; //填充open函式
input->close = gpio_keys_close; //填充close函式
input->id.bustype = BUS_HOST; //初始化匯流排型別,廠商,id,版本號
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
#define EV_REP 0x14 0001 010 0 --->20
__set_bit(EV_REP, input->evbit);
static inline void __set_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
*p |= mask;
}
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
((unsigned long *)addr) + ((nr) / 32) |= (1UL << ((nr) % 32))
通過__set_bit函式,來設定input->evbit變數的第20位為1,EV_REP命令碼代表重複。
當然還可以設定為下面的命令碼。evbit這一位代表設定不同的事件型別
EV_SYN 0x00 同步事件
EV_KEY 0x01 按鍵事件,如KEY_VOLUMEDOWN
EV_REL 0x02 相對座標, 如滑鼠上報的座標
EV_ABS 0x03 絕對座標,如觸控式螢幕上報的座標
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 聲音
EV_REP 0x14 Repeat
EV_FF 0x15 力反饋
error = input_register_device(input); //將input註冊到系統中
list_add_tail(&dev->node, &input_dev_list); //將input_dev節點新增到input_dev_list連結串列中
list_for_each_entry(handler, &input_handler_list, node) //在input_handler_list連結串列中查詢
input_attach_handler(dev, handler); //完成匹配過程,此過程以及分析過了,這裡就不在分析了
6.set_bit和__set_bit的關係
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p)
#define ATOMIC_BITOP(name,nr,p) _##name(nr,p)
__set_bit(nr,p)
7.輸入子系統驅動如何編寫
1.定義結構體
struct input_dev *input;
2.分配空間
input = input_allocate_device();
3.結構體成員的填充
3.1什麼型別的事件
set_bit(EV_KEY,fs_input->evbit);
3.2上報什麼值
set_bit(KEY_ENTER,fs_input->keybit);
set_bit(KEY_L,fs_input->keybit);
4.結構體的註冊和登出
input_register_device(input);
input_unregister_device(input);
8.如何上報事件
事件會通過input_event函式進行資料的上報。
input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
input_handle_event(dev, type, code, value);
switch (type) {
case EV_SYN:
case EV_KEY: //對disposition變數賦值
...
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
handle->handler->event(handle, type, code, value);
.event = evdev_event,
struct input_event event;
struct input_event {
struct timeval time; //時間
__u16 type; //事件型別
__u16 code; //編號
__s32 value; //值
};
event.type = type;
event.code = code;
event.value = value;
evdev_pass_event(client, &event, time_mono, time_real);
client->buffer[client->head++] = *event; //將要上報的input_event
client->head &= client->bufsize - 1; //放入緩衝區
if (unlikely(client->head == client->tail)) { //如果緩衝區存放滿之後,將最後一個設定為同步訊號
client->tail = (client->head - 2) & (client->bufsize - 1);
client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0;
client->packet_head = client->tail;
}
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
//從上述的程式碼中,我們可以看到,EV_SYN通過訊號的發出可以有兩種情況,
第一,input_client中的緩衝區存滿,第二使用者發出了EV_SYN同步訊號。當
同步訊號發出時,驅動中就會發送kill_fasync訊號,告訴應用程式有資料要
上報了。kill_fasync和fasync_helper實現非同步通知。
if (type == EV_SYN && code == SYN_REPORT)
wake_up_interruptible(&evdev->wait); //喚醒等待佇列
此時應用程式可以通過三種方式來讀取,第一種就是在應用程式中註冊signal訊號處理函式,在訊號處理函式中讀取資料,第二種就是直接呼叫read函式讀資料就行了。第三種就是使用select,poll,epoll函式來讀取資料。開啟的裝置節點為/dev/input/event%d
。
當然事件的上報還可以是input_report_key,input_report_rel,input_report_abs,input_sync等,你會發現這些函式最終都是呼叫input_event函式來實現的。只是暫時指定了他們的事件型別