輸入子系統框架詳解
(1)爲什麼要使用輸入子系統框架
我們在剛開始學習字符設備驅動程序的編寫時,都會用到這樣一個框架:
首先分配一個主設備號( alloc_chrdev_region(),推薦用這個函數 ),接着填充file_opreation 結構體(這是字符設備驅動程序編寫的關鍵點),再接着在入口函數裏面調用字符設備註冊函數等,最後編寫出口函數,不要忘記修飾入口、出口函數,最後加上關於模塊信息的系列函數。
基於這種架構寫出來的字符設備驅動程序有一個問題,就是隻有編寫它的人知道怎麼去調用它,其他人是不知到的,爲了使得做應用程序開發相關人員的工作更輕鬆,需要提供一個統一的接口,輸入子系統就在這樣的需求下產生了。
(2)輸入子系統的框架(基於Linux 2.6.22 內核)
輸入子系統的核心層是 input.c ,我們先進入該文件看一下,能不能發現什麼線索,
我們在源碼文件中搜索 “init” 關鍵字,找到了 input_init(void) 函數,我們大膽假設該函數就是輸入子系統的入口函數,我們需要在該函數體中找到證據,該函數的源碼如下:
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;
}
由上面的兩條註釋應該就可以看出這就是輸入子系統的入口函數,可是,疑問又來了,創建設備類了,可是並沒有在該類下創建文件呀?這個問題先留着,接下來的分析你將會找到答案。
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); 看到這一行時,我們似乎對 傳入的 input_fops 參數比較感興趣,我們先找到它的原型,看看它的這是面目是啥?
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
奇怪了,平時我們我們填充 file_operations 結構體時要傳入一大堆函數指針,怎麼現在這裏就一個 input_open_f 的函數指針呀,輸入子系統一定是在這個函數中做了相關工作,不然,應用程序系統調用都找不到底層的相關函數。我們先看看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;
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;
}
首先它定義了一個 input_hand 結構體類型的指針,並使這個指針指向 input_table 數組的第(iminor(inode) >> 5)項(次設備號除以32),接着就把這個指針指向的結構體中的 fops 成員賦值給new_fops,再調用new_fops,從而調用了我們編寫的驅動程序的open函數。明顯,我們事先要把input_table這個數組填充好,那麼問題又來了,有誰來填充數組?如何去填充?
我們不妨先看看input_handle結構體,看看裏面有啥成員,說不定就能發現蛛絲馬跡,找到線索。
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
const struct file_operations *fops;
int minor;
const char *name;
const struct input_device_id *id_table;
const struct input_device_id *blacklist;
struct list_head h_list;
struct list_head node;
};
我們可以猜一下,平時一般的字符設備驅動程序調用註冊函數,就把設備相關聯的結構體存儲在一個數組中,這裏會不會也是某個註冊函數來填充數組了,事實上,確實如此。
數組的填充由input_register_handler() 函數來完成
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
INIT_LIST_HEAD(&handler->h_list);
/* 完成數組填充 */
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node) // (1)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
註釋(1):由這兩行代碼,我們可以知道 input_register_handler() 函數還建立了和設備之間的聯繫。
既然有 input_register_handle()函數,那麼肯定有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;
}
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
list_add_tail(&dev->node, &input_dev_list); //放入鏈表
snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
if (!dev->cdev.dev)
dev->cdev.dev = dev->dev.parent;
error = class_device_add(&dev->cdev); //在input設備類下創建設備節點
if (error)
return error;
path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
/* 對於每一個input_handler,都調用input_attach_handler, 根據input_handler的 id_table判斷能否支持這個input_dev
*/
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
註冊input_dev或input_handler時,會兩兩比較左邊的input_dev和右邊的input_handler,根據input_handler的id_table判斷這個input_handler能否支持這個input_dev,如果能支持,則調用input_handler的connect函數建立"連接".
問題又來了,怎麼樣建立連接?
1. 分配一個input_handle結構體(注意是handle)
2. input_handle.dev = input_dev; // 指向左邊的input_dev
input_handle.handler = input_handler; // 指向右邊的input_handler
3. 註冊,使得device和handler能夠互相找到對方
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
(3)舉例說明
3.1 evdev_connect() 函數:
// 分配一個input_handle
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
// 設置
evdev->handle.dev = dev; // 指向左邊的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右邊的input_handler
evdev->handle.private = evdev;
// 註冊
error = input_register_handle(&evdev->handle);
3.2 怎麼讀按鍵?
app: read() 調用
evdev_read() :
// 無數據並且是非阻塞方式打開,則立刻返回
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);
3.3 誰來喚醒?
evdev_event
wake_up_interruptible(&evdev->wait);
3.4 evdev_event被誰調用?
猜:應該是硬件相關的代碼,input_dev那層調用的
在設備的中斷服務程序裏,確定事件是什麼,然後調用相應的input_handler的event處理函數
gpio_keys_isr(舉例)
// 上報事件
input_event(input, type, button->code, !!state);
input_sync(input);
input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
struct input_handle *handle;
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
4 怎麼寫符合輸入子系統框架的驅動程序
1. 分配一個input_dev結構體
2. 設置
3. 註冊
4. 硬件相關的代碼,比如在中斷服務程序裏上報事件
5 程序實例
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms後啓動定時器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 鬆開 : 最後一個參數: 0-鬆開, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
static int buttons_init(void)
{
int i;
/* 1. 分配一個input_dev結構體 */
buttons_dev = input_allocate_device();;
/* 2. 設置 */
/* 2.1 能產生哪類事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能產生這類操作裏的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 註冊 */
input_register_device(buttons_dev);
/* 4. 硬件相關的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");