輸入子系統雖然錯綜複雜,但是隻要我們領會了輸入子系統的一些設計思想後,我們要使用它並非難事.
以下以內核自帶的gpio_keys驅動爲例,介紹輸入子系統的使用.
主要的原因是gpio_keys驅動比較簡單易懂,另外不是沒個人都有觸摸屏,但鍵盤的話相信每一塊開發板上都配有吧^_^
按照以前的習慣,先從下到上的研究底層驅動是如何提交輸入事件的:
#####################################################################################################
drivers/input/keyboard/gpio_keys.c:
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct input_dev *input;
int i, error;
input = input_allocate_device();//申請input_dev結構
if (!input)
return -ENOMEM;
platform_set_drvdata(pdev, input);//把input_dev結構放好(以後方便調用)
input->evbit[0] = BIT(EV_KEY);//目前event的類型不操作32,所以你會看到對於evbit數組的操作都是對evbit[0]中的位來進行操作.
input->name = pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
int irq = gpio_to_irq(button->gpio);
unsigned int type = button->type ?: EV_KEY;
set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);
/* 根據用戶所指定的gpio_keys來申請中斷和註冊中斷處理函數*/
error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,
button->desc ? button->desc : "gpio_keys",
pdev);
if (error) {
printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d/n",
irq, error);
goto fail;
}
input_set_capability(input, type, button->code);
}
error = input_register_device(input);//註冊輸入設備,並和對應的handler處理函數掛鉤
if (error) {
printk(KERN_ERR "Unable to register gpio-keys input device/n");
goto fail;
}
return 0;
fail:
for (i = i - 1; i >= 0; i--)
free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev);
input_free_device(input);
return error;
}
提到input_dev結構,以下談一下我對於它的理解:
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
/*
* 根據各種輸入信號的類型來建立類型爲unsigned long 的數組,
* 數組的每1bit代表一種信號類型,
* 內核中會對其進行置位或清位操作來表示時間的發生和被處理.
*/
unsigned long evbit[NBITS(EV_MAX)];
unsigned long keybit[NBITS(KEY_MAX)];
unsigned long relbit[NBITS(REL_MAX)];
unsigned long absbit[NBITS(ABS_MAX)];
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
.........................................
};
/**
* input_set_capability - mark device as capable of a certain event
* @dev: device that is capable of emitting or accepting event
* @type: type of the event (EV_KEY, EV_REL, etc...)
* @code: event code
*
* In addition to setting up corresponding bit in appropriate capability
* bitmap the function also adjusts dev->evbit.
*/
/* 記錄本設備對於哪些事件感興趣(對其進行處理)*/
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
switch (type) {
case EV_KEY:
__set_bit(code, dev->keybit);//比如按鍵,應該對哪些鍵值的按鍵進行處理(對於其它按鍵不予理睬)
break;
case EV_REL:
__set_bit(code, dev->relbit);
break;
case EV_ABS:
__set_bit(code, dev->absbit);
break;
case EV_MSC:
__set_bit(code, dev->mscbit);
break;
case EV_SW:
__set_bit(code, dev->swbit);
break;
case EV_LED:
__set_bit(code, dev->ledbit);
break;
case EV_SND:
__set_bit(code, dev->sndbit);
break;
case EV_FF:
__set_bit(code, dev->ffbit);
break;
default:
printk(KERN_ERR
"input_set_capability: unknown type %u (code %u)/n",
type, code);
dump_stack();
return;
}
__set_bit(type, dev->evbit);//感覺和前面重複了(前面一經配置過一次了)
}
EXPORT_SYMBOL(input_set_capability);
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
int i;
struct platform_device *pdev = dev_id;
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct input_dev *input = platform_get_drvdata(pdev);
for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
int gpio = button->gpio;
if (irq == gpio_to_irq(gpio)) {//判斷哪個鍵被按了?
unsigned int type = button->type ?: EV_KEY;
int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;//記錄按鍵狀態
input_event(input, type, button->code, !!state);//彙報輸入事件
input_sync(input);//等待輸入事件處理完成
}
}
return IRQ_HANDLED;
}
/*
* input_event() - report new input event
* @dev: device that generated the event
* @type: type of the event
* @code: event code
* @value: value of the event
*
* This function should be used by drivers implementing various input devices
* See also input_inject_event()
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
if (type > EV_MAX || !test_bit(type, dev->evbit))//首先判斷該事件類型是否有效且爲該設備所接受
return;
add_input_randomness(type, code, value);
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
if (dev->event)
dev->event(dev, type, code, value);
break;
case SYN_REPORT:
if (dev->sync)
return;
dev->sync = 1;
break;
}
break;
case EV_KEY:
/*
* 這裏需要滿足幾個條件:
* 1: 鍵值有效(不超出定義的鍵值的有效範圍)
* 2: 鍵值爲設備所能接受(屬於該設備所擁有的鍵值範圍)
* 3: 按鍵狀態改變了
*/
if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
return;
if (value == 2)
break;
change_bit(code, dev->key);//改變對應按鍵的狀態
/* 如果你希望按鍵未釋放的時候不斷彙報按鍵事件的話需要以下這個(在簡單的gpio_keys驅動中不需要這個,暫時不去分析) */
if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
dev->repeat_key = code;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
break;
........................................................
if (type != EV_SYN)
dev->sync = 0;
if (dev->grab)
dev->grab->handler->event(dev->grab, type, code, value);
else
/*
* 循環調用所有處理該設備的handle(event,mouse,ts,joy等),
* 如果有進程打開了這些handle(進行讀寫),則調用其對應的event接口向氣彙報該輸入事件.
*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
}
EXPORT_SYMBOL(input_event);
#########################################################################
好了,下面再來研究一下event層對於input層報告的這個鍵盤輸入事件是如何來處理的.
#########################################################################
drivers/input/evdev.c:
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
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;
if (evdev->grab) {
client = evdev->grab;
do_gettimeofday(&client->buffer[client->head].time);
client->buffer[client->head].type = type;
client->buffer[client->head].code = code;
client->buffer[client->head].value = value;
client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
} else
/* 遍厲client_list鏈表中的client結構(代表些打開evdev的進程(個人理解^_^)) */
list_for_each_entry(client, &evdev->client_list, node) {
/* 填充代表該輸入信號的struct input_event結構(事件,類型,鍵碼,鍵值) */
do_gettimeofday(&client->buffer[client->head].time);
client->buffer[client->head].type = type;
client->buffer[client->head].code = code;
client->buffer[client->head].value = value;
/* 更新寫指針 */
client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
kill_fasync(&client->fasync, SIGIO, POLL_IN);//通知調用input_sync的進程:輸入事件經已處理完畢(通知底層).
}
wake_up_interruptible(&evdev->wait);//喚醒睡眠在evdev->wait等待隊列等待輸入信息的進程(通知上層).
}
###################################################################################
好了,至此一個按鍵的輸入事件處理完畢,現在再來從上到上的來看看用戶是如何獲取這個輸入事件的.
###################################################################################
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush
};
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev_client *client;
struct evdev *evdev;
int i = iminor(inode) - EVDEV_MINOR_BASE;
int error;
if (i >= EVDEV_MINORS)
return -ENODEV;
evdev = evdev_table[i];
if (!evdev || !evdev->exist)
return -ENODEV;
client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
if (!client)
return -ENOMEM;
client->evdev = evdev;
/* 添加evdev_client結構到鏈表evdev->client_list中(好讓輸入事件到來的時候填寫該結構並喚醒進程讀取) */
list_add_tail(&client->node, &evdev->client_list);
if (!evdev->open++ && evdev->exist) {
error = input_open_device(&evdev->handle);
if (error) {
list_del(&client->node);
kfree(client);
return error;
}
}
file->private_data = client;//存放好evdev_client結構方便以後使用
return 0;
}
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;
int retval;
if (count < evdev_event_size())//對於每次讀取的數據大小是有一定的要求.
return -EINVAL;
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))//緩存中沒有數據可讀且設備是存在的,
如果設置爲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 (client->head != client->tail && retval + evdev_event_size() <= count) {//下面開始讀取數據
struct input_event *event = (struct input_event *) client->buffer + client->tail;//獲取緩存中的讀指針
if (evdev_event_to_user(buffer + retval, event))//返回數據給用戶
return -EFAULT;
client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);//更新讀指針
retval += evdev_event_size();
}
return retval;
}
呵呵,看到了吧,應用程序就是這樣獲取輸入事件的^_^
######################################################################################################################################
本來對於gpio_keys這樣的驅動程序,只要當發生按鍵事件的時候向上層應用程序彙報鍵值即可.
不過,對於一些帶輸出設備(例如led燈)的輸入設備來說(例如鍵盤),上層應用程序同樣可以利用event層來讀取或改變其狀態.
請看以下代碼:
######################################################################################################################################
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 = 0;
if (!evdev->exist)
return -ENODEV;
while (retval < count) {
if (evdev_event_from_user(buffer + retval, &event))//從用戶處獲取事件結構
return -EFAULT;
input_inject_event(&evdev->handle, event.type, event.code, event.value);//往底層發送事件
retval += evdev_event_size();
}
return retval;
}
/**
* input_inject_event() - send input event from input handler
* @handle: input handle to send event through
* @type: type of the event
* @code: event code
* @value: value of the event
*
* Similar to input_event() but will ignore event if device is "grabbed" and handle
* injecting event is not the one that owns the device.
*/
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
if (!handle->dev->grab || handle->dev->grab == handle)
input_event(handle->dev, type, code, value);
}
EXPORT_SYMBOL(input_inject_event);
/*
* input_event() - report new input event
* @dev: device that generated the event
* @type: type of the event
* @code: event code
* @value: value of the event
*
* This function should be used by drivers implementing various input devices
* See also input_inject_event()
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
if (type > EV_MAX || !test_bit(type, dev->evbit))//首先判斷該事件類型是否有效且爲該設備所接受
return;
add_input_randomness(type, code, value);
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
if (dev->event)
dev->event(dev, type, code, value);
break;
case SYN_REPORT:
if (dev->sync)
return;
dev->sync = 1;
break;
}
break;
.............................................................
case EV_LED:
if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
return;
change_bit(code, dev->led);
if (dev->event)
dev->event(dev, type, code, value);
break;
if (type != EV_SYN)
dev->sync = 0;
if (dev->grab)
dev->grab->handler->event(dev->grab, type, code, value);
else
/*
* 循環調用所有處理該設備的handle(event,mouse,ts,joy等),
* 如果有進程打開了這些handle(進行讀寫),則調用其對應的event接口向氣彙報該輸入事件.
*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
}
EXPORT_SYMBOL(input_event);
注:
鑑於簡單的gpio_keys驅動中沒有註冊自己的event接口,當然也沒有對於LED燈的處理,而event層只是簡單的向上層彙報輸入事件(event層也不可能幫你處理你的led設備,對吧),所以這個通過輸入子系統控制LED的部分暫時不去研究.
(輸出設備LED燈不屬於這個輸入設備gpio_key的一部分.當然,如果你想通過這個gpio_keys設備來控制led燈的話,可以修改這個gpio_keys驅動,詳細可參考driver/input/keyboard目錄下的驅動)