1. 程式人生 > >input子系統詳解

input子系統詳解

Input子系統詳解

一.Input子系統架構

Linux系統提供了input子系統,按鍵、觸控式螢幕、鍵盤、滑鼠等輸入都可以利用input介面函式來實現裝置驅動,下面是Input子系統架構:

Input子系統架構

二.Input系統的組成

輸入子系統由驅動層(Drivers),輸入子系統核心層( Input Core )和事件處理層(Event Handler)三部份組成。一個輸入事件,如滑鼠移動,鍵盤按鍵按下等都是通過 Driver -> InputCore -> Eventhandler -> userspace 的順序到達使用者空間傳給應用程式。下面介紹各部分的功能:

(1)驅動層功能:負責和底層的硬體裝置打交道,將底層硬體裝置對使用者輸入的響應轉換為標準的輸入事件以後再向上傳送給輸入子系統核心層(Input Core)。

(2)Input系統核心層:Input Core即Input Layer,由driver/input/input.c及相關標頭檔案實現,它對下提供了裝置驅動層的介面,對上提供了事件處理層(Event Handler)的程式設計介面。

(3)事件處理層將硬體裝置上報的事件分發到使用者空間和核心。

三.Input裝置驅動編寫

在Linux核心中,input裝置用input_dev結構體描述,使用input子系統實現輸入裝置驅動的時候,驅動的核心工作是向系統報告按鍵、觸控式螢幕、鍵盤、滑鼠等輸入事件(event,通過input_event結構體描述),不再需要關心檔案操作介面,因為input子系統已經完成了檔案操作介面。驅動報告的事件經過InputCore和 Eventhandler最終到達使用者空間。下面給出一個使用input子系統的例子,通過這個例子來解析input子系統的方方面面。

(1)鍵盤驅動

static void button_interrupt(int irq, void *dummy, struct pt_regs *fp)
{

input_report_key(&button_dev, BTN_1, inb(BUTTON_PORT) & 1);

input_sync(&button_dev);
}

static int __init button_init(void)
{

    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.evbit[0] = BIT(EV_KEY);

button_dev.keybit[LONG(BTN_0)] = BIT(BTN_0);

input_register_device(&button_dev);
}

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

    這是個最簡單使用input子系統的例子,權且引出這input子系統,這個驅動中主要涉及input子系統的函式下面一一列出,後面會有詳細的介紹:

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) input_unregister_device()

    用來登出一個input_dev裝置

四.Input子系統探幽

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



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鏈上。所有的handler都掛在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函式.
(2)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的註冊有相似的地方.都是註冊到各自的連結串列,然後與另外一條連結串列的物件相匹配.

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

list_add_tail(&handle->h_node, &handler->h_list);

if (handler->start)
handler->start(handle);

return 0;
}

在這個函式裡所做的處理其實很簡單.將handle掛到所對應input device的h_list連結串列上.還將handle掛到對應的handler的hlist連結串列上.如果handler定義了start函式,將呼叫之。

到這裡,我們已經看到了input device, handler和handle是怎麼關聯起來的了。

(4)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子系統的初始化說起。

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


if (!handler || !(new_fops = fops_get(handler->fops)))
return -ENODEV;

iminor(inode) 為開啟檔案所對應的次裝置號。input_table是一個struct input_handler全域性陣列,在這裡它先裝置結點的次裝置號右移5位做為索引值到input_table中取對應項。

從這裡我們也可以看到.一 個handle代表1<<5個裝置節點(因為在input_table中取值是以次備號右移5位為索引的.即低5位相同的次備號對應的是同一個索引)。在這裡,終於看到了input_talbe大顯身手的地方了,input_talbe[ ]中取值和input_talbe[ ]的賦值,這兩個過程是相對應的.在input_table中找到對應的handler之後,就會檢驗這個handle是否存,是否帶有fops檔案操作集.如果沒有.則返回一個裝置不存在的錯誤.

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替換掉當前的fops.如果新的fops中有open()函式,則呼叫它。

(2)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 }, 
{ }, 
};

它沒有定義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()中完成的。

(3)evdev裝置結點的open()操作

我們知道.對主裝置號為INPUT_MAJOR的裝置節點進行操作,會將操作集轉換成handler的操作集,在evdev中,這個操作集就是evdev_fops對應的open函式如下示:

static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;
int error;

if (i >= EVDEV_MINORS)
return -ENODEV;

error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
evdev = evdev_table[i];
if (evdev)
get_device(&evdev->dev);
mutex_unlock(&evdev_table_mutex);

if (!evdev)
return -ENODEV;

client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
evdev_attach_client(evdev, client);

error = evdev_open_device(evdev);
if (error)
goto err_free_client;

file->private_data = client;
return 0;

err_free_client:
evdev_detach_client(evdev, client);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}

iminor(inode) - EVDEV_MINOR_BASE就得到了在evdev_table[ ]中的序號.然後將陣列中對應的evdev取出.遞增devdev中device的引用計數。

分配並初始化一個client.並將它和evdev關聯起來: client->evdev指向它所表示的evdev. 將client掛到evdev->client_list上. 將client賦為file的私有區。對應handle的開啟是在此evdev_open_device()中完成的,程式碼如下:

static int evdev_open_device(struct evdev *evdev)
{
int retval;

retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;

if (!evdev->exist)
retval = -ENODEV;
else if (!evdev->open++) {
retval = input_open_device(&evdev->handle);
if (retval)
evdev->open--;
}

mutex_unlock(&evdev->mutex);
return retval;
}

如果evdev是第一次開啟,就會呼叫input_open_device()開啟evdev對應的handle,跟蹤一下這個函式:

int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval;

retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;

if (dev->going_away) {
retval = -ENODEV;
goto out;
}

handle->open++;

if (!dev->users++ && dev->open)
retval = dev->open(dev);

if (retval) {
dev->users--;if (!--handle->open) {

synchronize_rcu();
}
}

out:
mutex_unlock(&dev->mutex);
return retval;
}

在這個函式中,我們看到遞增handle的開啟計數,如果是第一次開啟.則呼叫input device的open()函式.

(4)evdev的事件處理

經過上面的分析.每當input device上報一個事件時,會將其交給和它匹配的handler的event函式處理.在evdev中.這個event函式對應的程式碼為:

static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;

do_gettimeofday(&event.time);
event.type = type;
event.code = code;
event.value = value;

rcu_read_lock();

client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);

rcu_read_unlock();

wake_up_interruptible(&evdev->wait);
}

首先構造一個struct input_event結構.並裝置它的type.code,value為處理事件的相關屬性.如果該裝置被強制設定了handle.則呼叫如之對應的client。

我們在open的時候分析到.會初始化clinet並將其鏈入到evdev->client_list. 這樣,就可以通過evdev->client_list找到這個client了。對於找到的第一個client都會呼叫evdev_pass_event( ),程式碼如下:

static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{

spin_lock(&client->buffer_lock);
client->buffer[client->head++] = *event;
client->head &= EVDEV_BUFFER_SIZE - 1;
spin_unlock(&client->buffer_lock);

kill_fasync(&client->fasync, SIGIO, POLL_IN);
}

這裡的操作很簡單.就是將event儲存到client->buffer中.而client->head就是當前的資料位置.注意這裡是一個環形快取區.寫資料是從client->head寫.而讀資料則是從client->tail中讀。

(5)裝置節點的read處理

對於evdev裝置節點的read操作都會由evdev_read()完成.它的程式碼如下:

static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;

if (count < evdev_event_size())
return -EINVAL;

if (client->head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;

retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
if (retval)
return retval;

if (!evdev->exist)
return -ENODEV;

while (retval + evdev_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {

if (evdev_event_to_user(buffer + retval, &event))
return -EFAULT;

retval += evdev_event_size();
}

return retval;
}

首先,它判斷快取區大小是否足夠.在讀取資料的情況下,可能當前快取區內沒有資料可讀.在這裡先睡眠等待快取區中有資料。如果在睡眠的時候,條件滿足,是不會進行睡眠狀態而直接返回的。然後根據read()提夠的快取區大小.將client中的資料寫入到使用者空間的快取區中。

(6)裝置節點的寫操作

同樣.對裝置節點的寫操作是由evdev_write()完成的.程式碼如下:

static ssize_t evdev_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;

retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;

if (!evdev->exist) {
retval = -ENODEV;
goto out;
}

while (retval < count) {

if (evdev_event_from_user(buffer + retval, &event)) {
retval = -EFAULT;
bsp; goto out;
}

input_inject_event(&evdev->handle,
event.type, event.code, event.value);
retval += evdev_event_size();
}

out:
mutex_unlock(&evdev->mutex);
return retval;
}

首先取得操作裝置檔案所對應的evdev,實際上,這裡寫入裝置檔案的是一個event結構的陣列.我們在之前分析過,這個結構裡包含了事件的type.code和event,將寫入裝置的event陣列取出,然後對每一項呼叫event_inject_event()。

這個函式的操作和input_event()差不多,就是將第一個引數handle轉換為輸入裝置結構,然後這個裝置再產生一個事件。程式碼如下:

void input_inject_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_dev *dev = handle->dev;
struct input_handle *grab;
unsigned long flags;

if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);

rcu_read_lock();
grab = rcu_dereference(dev->grab);
if (!grab || grab == handle)
input_handle_event(dev, type, code, value);
rcu_read_unlock();

spin_unlock_irqrestore(&dev->event_lock, flags);
}
}

我們在這裡也可以跟input_event()對比一下,這裡裝置可以產生任意事件,而不需要和裝置所支援的事件型別相匹配,由此可見,對於寫操作而言.就是讓與裝置檔案相關的輸入裝置產生一個特定的事件。

六.小結

在 這一節點,分析了整個input子系統的架構,各個環節的流程。最後還以evdev為例將各個流程貫穿在一起,以加深對input子系統的理解。

由此也可以看出:linux裝置驅動採用了分層的模式,從最下層的裝置模型到裝置,驅動和匯流排,再到input子系統最後到input device。這樣的分層結構使得最上層的驅動不必關心下層是怎麼實現的,而下層驅動又為多種型號同樣功能的驅動提供了一個統一的介面。

相關推薦

input子系統

Input子系統詳解 一.Input子系統架構 Linux系統提供了input子系統,按鍵、觸控式螢幕、鍵盤、滑鼠等輸入都可以利用input介面函式來實現裝置驅動,下面是Input子系統架構: Input子系統架構 二.Input系統的組成 輸入子系統由驅動層(

Android4.0 input輸入子系統

下面的文章是基於mini2440的gpio按鍵來講解input子系統。 以mini2440為例,用該板的bsp檔案,進行input子系統的講解.所用的版本為android4.0. 先來看下板級支援檔案都註冊了那些資源。 下面是五個按鍵的資源: #define KEY_POWER           116 

vue中ref在input

當我們在專案中遇見文字輸入框的時候,獲取時刻輸入框中的值 1、v-model <template> <input type="text" v-model="inputval"> </template> export default { data(){

嵌入式Linux核心I2C子系統

1.1 I2C匯流排知識 1.1.1  I2C匯流排物理拓撲結構      I2C匯流排在物理連線上非常簡單,分別由SDA(序列資料線)和SCL(序列時鐘線)及上拉電阻組成。 通訊原理是通過對SCL和SDA線高低電平時序的控制,來產生I2C匯流排協議所需

Input輸入對象常用方法

知新樹 寧金峰 Input對象可以獲取用戶所有行為的輸入,如鼠標、鍵盤、加速度、陀螺儀、按鈕等,所以掌握Input對象就可以在外部輸入信息和系統之間進行交互。 Input對象的主要變量:mousePonsition 當前鼠標的像素坐標anyKeyDown 用戶點擊任何鍵或鼠標按鈕,第一幀返回tru

logstash 外掛 input

本文只介紹幾種常用的, 具體可參考 input 外掛官方詳解: https://www.elastic.co/guide/en/logstash/current/input-plugins.html 標準輸入 stdin{} input{ stdin{

python學習之網站的編寫(HTML,CSS,JS)(三)----------input系列的標籤及示例程式碼(可上傳到伺服器form標籤)

文章編排,我們首先來講一下input系列的各種內容,然後用一個示例程式碼來清晰的理解其中特定的含義 input系列: 1.輸入文字內容: <input type="text" name="user"/>起個名字易於在伺服器端進行處理 2.輸入密碼內容:

logstash-input-jdbc實現mysql 與elasticsearch實時同步深入

引言: elasticsearch 的出現使得我們的儲存、檢索資料更快捷、方便。但很多情況下,我們的需求是:現在的資料儲存在mysql、oracle等關係型傳統資料庫中,如何儘量不改變原有資料庫表結構,將這些資料的insert,update,delete操作結果實時同步到elasticsearch(

react中的ref在input中的

當我們在專案中遇見文字輸入框的時候,獲取時刻輸入框中的值 1、受控元件 class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''};

Linux 輸入子系統分析 Linux之輸入子系統分析()

為什麼要引入輸入子系統? 在前面我們寫了一些簡單的字元裝置的驅動程式,我們是怎麼樣開啟一個裝置並操作的呢? 一般都是在執行應用程式時,open一個特定的裝置檔案,如:/dev/buttons 1 ..... 2 int main(int argc, char **argv) 3 {

輸入子系統------鍵盤按鍵驅動程式 13.Linux鍵盤按鍵驅動 ()

由上一節的輸入子系統的框架分析可知,其分三層:裝置驅動層,核心層,事件驅動層 我們在為某種裝置的編寫驅動層,只需要關心裝置驅動層,即如何驅動裝置並獲得硬體資料(如按下的按鍵資料),然後呼叫核心層提供的介面,核心層就會自動把資料提交給事件處理層。在輸入子系統中,事件驅動是標準的,適用於所有輸入類的。

ELK日誌監控分析系統Logstash之——input模組

摘要: Logstash由三個元件構造成,分別是input、filter以及output。我們可以吧Logstash三個元件的工作流理解為:input收集資料,filter處理資料,output輸出資料。至於怎麼收集、去哪收集、怎麼處理、處理什麼、怎麼發生以及傳送到哪等等一些列的問題就是我

Unity3D手機中Input類touch

Unity3D手機中Input類touch詳解: 1.Input.touchCount 觸控隨之增長,一秒50次增量。 2.Input.GetTouch(0).phase==TouchPhase.Moved 手指滑動中最後一幀滑動的狀態是運動的。 3.TouchPh

linux驅動由淺入深系列:ALSA框架 音訊子系統之二

本文以高通平臺為例,介紹一下android下的音訊結構。android使用的是tinyALSA作為音訊系統,使用方法和基本框架與linux中常用的ALSA音訊子系統是一致的。ALSA音訊框架ALSA(Advanced Linux Sound Architecture)是一個開

[logstash-input-log4j]外掛使用

Log4j外掛可以通過log4j.jar獲取Java日誌,搭配Log4j的SocketAppender和SocketHubAppender使用,常用於簡單的叢集日誌彙總。 最小化的配置 input { log4j { host=>"localhost"

[Logstash-input-redis] 使用

redis外掛的完整配置 input { redis { batch_count => 1 #返回的事件數量,此屬性僅在list模式下起作用。 data_type => "list" #logstash redis外掛工作方式 key =&g

[logstash-input-http] 外掛使用

外掛介紹 Http外掛是2.0版本才出現的新外掛,1.x是沒有這個外掛的。這個外掛可以幫助logstash接收其他主機或者本機發送的http報文。 外掛的原理很簡單,它自己啟動了一個ruby的伺服器,用於接收Http請求。然後會把host(IP地址)和header相關的資訊新增到event中。 下面就看看這個

input type=“range”滑塊自定義樣式,實現步驟及實際應用

寫在前面: 本文的主要內容包括:type="range"屬性介紹,修改range預設css樣式以及在js中的實際應用。本文面向前端小白,寫的不好之處,請多多見諒。文末有demo連結,可以自行復制到本地進行試驗。 最終要實現的效果: 其中包括一部分js程式碼

html5的input型別和所有屬性

<input type="text">:如果一個input沒有type屬性,那麼它會是預設type="text"。沒有什麼特別的,就是允許輸入文字,簡單明瞭。<input type="password">:顧名思義,在使用者輸入密碼的時候建議使用這個屬性而非text,使用了這個屬性,使

25.Unity3D手機中Input類touch-Unity觸屏事件解析到底(Twisted Fate)

首先貼一下Unity支援的模型檔案型別,以前沒有收集過。 Unity支援兩種型別的3D檔案格式: 1.  通用的“出口型”3D檔案 如.fbx、.dae、.3ds、.dxf、.obj等檔案格式。 2.  3D軟體專用的3D檔案格式 如Max, Maya, Blender,Cinema4D, Modo