之前已經分析過了編寫一個驅動程式,主要有以下幾個步驟:
- 自己設定或由系統自動配置設定驅動裝置的主裝置号;
- 編寫裝置操作函數(drv_open、drv_read、drv_write、drv_close等);
- 構造file_operations結構體,将上一步編寫的操作函數指派給結構體内的
等.open、.read、.write、.poll、.fasync
- 注冊驅動程式,調用
register_chrdev(major, name, fops);
- 編寫入口函數和出口函數。
但輸入子系統驅動架構将以上步驟分開了,它是由裝置層、核心層、事件層共同組成的。其中核心層提供一些裝置層與事件層公用的函數,比如說注冊函數、反注冊函數、事件到來的處理函數等等,完成了驅動程式編寫的第1-4步。但在input架構的file_operations中,隻含有一個open函數,正是通過它調用特定input_handler結構體,其裡面包含有根據不同次裝置号映射到不同的輸入裝置大類的**.fops**;事件處理層其實在Linux核心裡面已經幫我們寫好了很多有關的事件;而裝置層就是我們要新添加到輸入系統的具體裝置相關的程式了。
在分析輸入子系統架構時,我們已經知道核心對不同類型的輸入裝置已經抽象出了不同的handler進行處理,device層實作純硬體的操作,我們可以根據所實作驅動的功能對device層進行設計,主要是内容是當檢測有效輸入發送,調用input_event函數向handler層上報結果即可。
1、編寫符合輸入子系統架構的驅動程式步驟
- 配置設定一個
結構體,調用input_dev
或者input_allocate_device()
實作;devm_input_allocate_device(struct input_dev*)
- 編寫函數,設定input_dev結構體,選擇input裝置的事件類型(調用
函數實作);set_bit()
- 注冊input_dev結構體,調用
函數實作;input_register_device(struct input_dev*)
- 編寫硬體相關代碼:注冊中斷處理函數,比如鍵盤裝置需要編寫按鍵的擡起、放下,觸摸屏裝置需要編寫按下、擡起、絕對移動,滑鼠裝置需要編寫單擊、擡起、相對移動,并且需要在必要的時候送出硬體資料(鍵值/坐标/狀态等等),即上報輸入事件。
- 當送出輸入裝置産生的輸入事件之後,需要調用
函數來通知輸入子系統,以處理裝置産生的完整事件。void input_sync(struct input_dev *dev)
Linux輸入子系統支援的事件類型:(在include/linux/input.h中)
EV_SYN 0x00 同步事件 EV_KEY 0x01 按鍵事件 EV_REL 0x02 相對坐标(如:滑鼠移動,報告相對最後一次位置的偏移) EV_ABS 0x03 絕對坐标(如:觸摸屏或操作杆,報告絕對的坐标位置) EV_MSC 0x04 其它 EV_SW 0x05 開關 EV_LED 0x11 按鍵/裝置燈 EV_SND 0x12 聲音/警報 EV_REP 0x14 重複 EV_FF 0x15 力回報 EV_PWR 0x16 電源 EV_FF_STATUS 0x17 力回報狀态 EV_MAX 0x1f 事件類型最大個數和提供位掩碼支援
Linux輸入子系統提供的裝置驅動層上報輸入事件的函數:(在include/linux/input.h中)
void input_event(struct input_dev* dev, unsigned int type, unsigned int code, int value); //上報指定的type、code的輸入事件
void input_report_key(struct input_dev *dev, unsigned int code, int value); //上報按鍵事件
void input_report_rel(struct input_dev *dev, unsigned int code, int value); //上報相對坐标事件
void input_report_abs(struct input_dev *dev, unsigned int code, int value); //上報絕對坐标事件
2、編寫符合input系統架構的按鍵驅動程式
現在我們開始使用核心的輸入子系統架構,将自己編寫驅動程式融入其中,編寫更通用的按鍵驅動程式。這裡以JZ2440開發闆上的4個按鍵作為輸入子系統的按鍵,我們編寫的驅動程式實作将開發闆上的4個按鍵分别映射成鍵盤上不同鍵值,代表鍵盤上的字母L、S和Enter(回車)。這幾個值是在include\linux\input.h中被定義的。
2.1 編寫入口函數、出口函數,搭建好架構
/* 參考drivers\input\keyboard\gpio_keys.c */
#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>
static int buttons_init(void)
{
/* 1.配置設定一個input_dev結構體。 */
/* 2.設定input_dev結構體。 */
/* 3.注冊input_dev結構體。 */
/* 4.硬體相關的操作。 */
}
static void buttons_exit(vod)
{
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
2.1.1 配置設定一個input_dev結構體
/* 以下結構體定義位于函數外,屬于全局變量*/
static struct input_dev *buttons_dev;
/*以下語句位于buttons_init函數中*/
buttons_dev = input_allocate_device();
if (!buttons_dev)
return -ENOMEM;
2.1.2 設定input_dev結構體
先看以下Input_dev結構體的定義:

- evbit:用來設定該裝置能産生哪類事件類型(在第1節有具體描述輸入子系統所支援的所有事件類型),在此我們選擇按鍵事件EV_KEY和EV_REP,代碼如下:
set_bit(EV_KEY, buttons_input->evbit); //按鍵事件
set_bit(EV_REP, buttons_input->evbit); //重複事件(長按按鍵會重複輸入按鍵值)
- keybit:用來設定該裝置能産生哪些按鍵值,在此我們設定本節開頭說的鍵盤上的L、S、左shift、enter(為了可以在開發闆上執行ls指令)。按鍵碼定義在include\linux\input.h中,截取其中一小部分:
#define KEY_ENTER 28//enter的按鍵碼
#define KEY_S 31//S的按鍵碼
#define KEY_L 38//L的按鍵碼
#define KEY_LEFTSHIFT 42//leftshift的按鍵碼
設定輸入事件類型的哪一種按鍵的代碼:
set_bit(KEY_L, buttons_input->keybit);
set_bit(KEY_S, buttons_input->keybit);
set_bit(KEY_ENTER, buttons_input->keybit);
set_bit(KEY_LEFTSHIFT, buttons_input->keybit);
- relbit:表示能産生哪些相對位移事件, 例如滾輪
- absbit:表示能産生哪些絕對位移事件。
2.1.3 注冊input_dev結構體
注冊的功能其實就是将目前裝置buttons_dev放到input_dev連結清單中去,然後和事件層的evdev_handler逐個比較(通過比較id和id.table[]),如果比對,則會調用evdev_handler中的.connect函數,産生一個handle結構體(隻含有.handler和.dev,一個指向裝置,一個指向handler),将目前裝置與handler建立起關聯,将目前dev及其比對的handler放到各自的.h_list中。代碼如下:
input_register_device(buttons_input);//注冊裝置驅動
2.1.4 硬體相關的操作
- 定義各按鍵描述結構體
/* 以下2個結構體定義位于函數外,屬于全局變量*/
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF(0), KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF(2), KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG(3), KEY_ENTER},
};
- 初始化定時器(防抖動)
/* 以下結構體定義位于函數外,屬于全局變量*/
static struct timer_list buttons_timer; //定義一個定時器
/*以下語句位于buttons_init函數中*/
init_timer(&buttons_timer); //初始化定時器
buttons_timer.function = buttons_timer_function;//定義逾時處理函數
add_timer(&buttons_timer); //定義逾時時間
- 為每個按鍵注冊中斷
/*以下語句位于buttons_init函數中*/
for (i = 0; i < 3; i++) 注冊中斷
{
request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
}
- 是以整體buttons_init函數的代碼如下:
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);
/* 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 < 3; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
- 編寫出口函數
static void buttons_exit(void)
{
int i;
for (i = 0; i < 3; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
2.2 編寫按鍵的中斷處理函數
/* 以下結構體指針定義位于函數外,屬于全局變量*/
static struct pin_desc *irq_pd;
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);
}
2.3 編寫定時器逾時處理函數
當有按鍵資料産生時,調用input_event函數來上報事件,該函數會從input_dev連結清單中的.h_list找到對應的.handler,并調用裡面的.event函數。
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);
}
}
2.4 整體代碼buttons.c
/* 參考drivers\input\keyboard\gpio_keys.c */
/* 運作于JZ2440開發闆、Linux3.4.2核心、 gcc4.3.2*/
#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_GPF(0), KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF(2), KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG(3), KEY_ENTER},
// {IRQ_EINT19, "S5", S3C2410_GPG(11), 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 < 3; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 3; 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");
3、測試
3.1 利用 hexdump
測試
hexdump
hexdump /dev/event1 //等價于執行`open /dev/event1; read();`
秒 微秒 類 code value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815
3.2 如果目前沒有啟動QT,則按如下方法:
- 執行
cat /dev/tty1
- 開發闆上按下:S2、S3、S4
- 可以看到:ls
或者
- 執行
exec 0</dev/tty1
- 可以使用開發闆上的鍵盤來輸入ls指令
3.3 如果已經啟動了QT
- 先打開記事本
- 然後按鍵S2、S3、S4
- 可以看到記事本上顯示ls
4、小結
- 打開輸入裝置後發生了什麼?
- drivers/input/input.c:
- input_init > err = register_chrdev(INPUT_MAJOR, “input”, &input_fops);
-
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
- 怎麼讀按鍵?
- intput_open_file
- struct input_handler *handler = input_table[iminor(inode) >> 5];
- new_fops = fops_get(handler->fops) // =>&evdev_fops
- file->f_op = new_fops;
- err = new_fops->open(inode, file);
- APP: read > … >file->f_op->read
- input_table數組由誰構造?
- input_register_handler
- 如何注冊input_handler?
- input_register_handler
- input_table[handler->minor >> 5] = handler; // 放入數組
- list_add_tail(&handler->node, &input_handler_list); // 放傳入連結表
- // 對于每個input_dev,調用input_attach_handler
- list_for_each_entry(dev, &input_dev_list, node)
- input_attach_handler(dev, handler); // 根據input_handler的id_table判斷能否支援這個input_dev
- 如何 注冊輸入裝置:
- input_register_device
- list_add_tail(&dev->node, &input_dev_list); // 放傳入連結表
-
// 對于每一個input_handler,都調用input_attach_handler
list_for_each_entry(handler, &input_handler_list, node)
- input_attach_handler(dev, handler); // 根據input_handler的id_table判斷能否支援這個input_dev
input_attach_handler
- id = input_match_device(handler->id_table, dev);
- error = handler->connect(handler, dev, id);
注冊input_dev或input_handler時,會兩兩比較左邊的input_dev和右邊的input_handler,
根據input_handler的id_table判斷這個input_handler能否支援這個input_dev,
如果能支援,則調用input_handler的connect函數建立"連接配接"
- 怎麼建立連接配接?
- 配置設定一個input_handle結構體
-
input_handle.dev = input_dev; // 指向左邊的input_dev
input_handle.handler = input_handler; // 指向右邊的input_handler
-
注冊:
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
- evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 配置設定一個input_handle
-
// 設定
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);
- 怎麼讀按鍵?
- 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);
-
誰來喚醒?
evdev_event
wake_up_interruptible(&evdev->wait);
-
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);