我的環境:
Fedora14 內核版本,2.6.38.1
開發板:TQ2440
移植內核版本:2.6.30.4
2.6版本的內核中,驅動的開發逐漸發展成基于總線模型等一定結構的開發模式,采用了分層的設計思想,這樣的變化使得驅動開發的工作量相對而言越來越少,但是也增加了我們閱讀、分析源碼的思想的難度。
Linux輸入子系統就是一個基于分層模式的系統,其基本的層次分解如下圖所示。
在圖中我們可以發現輸入子系統主要包括三個部分設備驅動層(input driver)、核心層(input core)和輸入事件驅動層。輸入子系統的劃分使得輸入設備的驅動程序設計越來越簡單,但是其中的思想采用我們學習的重點和難點。
Input子系統處理輸入事務,任何輸入設備的驅動程序都可以通過Input輸入子系統提供的接口注冊到內核,利用子系統提供的功能來與用戶空間交互。輸入設備一般包括鍵盤,鼠標,觸摸屏等,在內核中都是以輸入設備出現的。下面分析input輸入子系統的結構,以及功能實現。
1. Input子系統是分層結構的,總共分為三層:硬件驅動層,子系統核心層,事件處理層。
(1)、其中硬件驅動層負責操作具體的硬件設備,這層的代碼是針對具體的驅動程序的,需要驅動程序的作者來編寫。
(2)、子系統核心層是鏈接其他兩個層之間的紐帶與橋梁,向下提供驅動層的接口,向上提供事件處理層的接口。
(3)、事件處理層負責與用戶程序打交道,將硬件驅動層傳來的事件報告給用戶程序。
2. 各層之間通信的基本單位就是事件,任何一個輸入設備的動作都可以抽象成一種事件,如鍵盤的按下,觸摸屏的按下,鼠標的移動等。事件有三種屬性:類型(type),編碼(code),值(value),Input子系統支持的所有事件都定義在input.h中,包括所有支持的類型,所屬類型支持的編碼等。事件傳送的方向是硬件驅動層-->子系統核心-->事件處理層-->用戶空間。
在驅動程序設計中,我們對于設備的驅動設計主要集中在設備驅動層的實現,但是這與之前的設備驅動開發存在較大的差別,主要是因為設備驅動不再是編寫基本操作的實現過程,也就是不在是對struct file_operations 這個結構體對象的填充和實現。在輸入設備驅動中的主要實現包括下面幾個過程:
1、分配一個輸入設備對象。并完成響應結構體元素的填充,主要包括支持的事件類型和事件代號等。
分配對象的函數:
struct input_dev *input_allocate_device(void);
釋放對象函數:
void input_free_device(struct input_dev *dev);
設置支持的事件類型和事件代碼:
通常采用set_bit函數實現:
設置支持的事件類型(支持按鍵事件)
set_bit(EV_KEY, input_dev->evbit);
設置支持的事件代碼(支持按鍵1)
set_bit(KEY_1, input_dev->keybit);
2、完成輸入設備對象的注冊,將設備對象注冊到輸入子系統當中去,當然也有對應的釋放函數。
注冊設備到內核:
int input_register_device(struct input_dev *dev);
注銷設備:
void input_unregister_device(struct input_dev *dev);
3、向核心層(input core)匯報事件的發生以及傳輸事件類型和事件代碼等。這一部分通常是采用中斷的方法實現,在中斷中向上一層次(Input Core)傳送發生事件的事件類型、事件代號以及事件對應的值等。但是上報的內容結構體都是基于一個固定結構體的
struct input_event,在用戶空間也可以采用這個結構體實現對事件的訪問。
struct input_event {
/*事件發生的時間*/
struct timeval time;
/*事件類型*/
__u16 type;
/*事件代號*/
__u16 code;
/*事件對應的值*/
__s32 value;
};
基本的事件類型包括如下:
#define EV_SYN 0x00
/*常用的事件類型*/
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
支持的事件代號:
...
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
#define KEY_MINUS 12
#define KEY_EQUAL 13
#define KEY_BACKSPACE 14
…
主要的匯報函數如下:
/*匯報鍵值函數*/
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);
也可以采用更加一般的函數匯報,上面的三個函數是通過下面這個函數實現的。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
完成上面的三個部分,一個輸入設備的驅動程序也就完成了。但是其中的具體實現還需要閱讀相關的源碼。特別是設備的操作是如何通過輸入設備實現等基本的操作是我們應該去關注的,設備的具體操作函數實質上已經因為一些共性被設計成了通用的接口,在輸入子系統內部已經實現。
TQ2440中的按鍵主要是采用外部中斷的形式實現,所以我在實驗過程中也采用了外部中斷的模式直接對按鍵進行操作。
具體的程序如下所示:驅動代碼:
- #include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/slab.h>
#include<linux/string.h>
#include<linux/input.h>
#include<linux/irq.h>
#include<mach/gpio.h>
#include<mach/regs-gpio.h>
#include<asm/irq.h>
#include<asm/io.h>
#include<linux/interrupt.h>
#include<linux/delay.h>
- #define DEVICE_NAME "TQ2440_BUTTON"
- #define NUMBERS_BUTTONS 4
- static const char * input_name = "Tq2440_button";
- static struct input_dev *button_dev;
- /*中斷集合*/
static const int irqs[NUMBERS_BUTTONS]={
IRQ_EINT0,
IRQ_EINT1,
IRQ_EINT2,
IRQ_EINT4,
};
- /*端口集合*/
static const int gpios[NUMBERS_BUTTONS]={
S3C2410_GPF0,
S3C2410_GPF1,
S3C2410_GPF2,
S3C2410_GPF4,
};
/*事件代號集合*/
static const int keys[NUMBERS_BUTTONS]={
KEY_1,
KEY_2,
KEY_3,
KEY_4,
};
- /*中斷處理程序*/
- static irqreturn_t button_interrupt(int irq,void * p)
{
int val = 0, i= 0;
for(i = 0; i < NUMBERS_BUTTONS; ++ i)
{
/*如果中斷號正確*/
if(irqs[i] == irq)
{
/*讀取端口的值*/
val = s3c2410_gpio_getpin(gpios[i]);
/*匯報端口的值,實質上是將事件key[i]匯報*/
input_report_key(button_dev,keys[i],val);
/*匯報同步,也就是完成匯報工作*/
input_sync(button_dev);
break;
}
}
/*返回值*/
return IRQ_RETVAL(IRQ_HANDLED);
}
- /*設備初始化函數*/
- static int __init tq2440_button_init(void)
{
int err,i = 0;
/*主要要先申請設備,然后申請中斷*/
/*申請輸入設備*/
button_dev = input_allocate_device();
if(NULL == button_dev)
{
printk(KERN_ERR "not enough memory\n");
err = - ENOMEM;
}
/*設置相應的事件處理類型,按鍵事件*/
set_bit(EV_KEY,button_dev->evbit);
- /*申請中斷*/
for(i = 0; i < NUMBERS_BUTTONS; ++ i)
{
/*申請中斷*/
request_irq(irqs[i],button_interrupt,IRQ_TYPE_EDGE_BOTH,
DEVICE_NAME,NULL);
/*設置案件的代碼值,即code值*/
set_bit(keys[i],button_dev->keybit);
}
/*設備名,防止出現錯誤*/
button_dev->name = input_name;
/*注冊輸入設備*/
err = input_register_device(button_dev);
if(err)
{
printk(KERN_ERR "failed to register device\n");
goto err_free_dev;
}
printk("initialized\n");
- return 0;
- /*錯誤處理*/
err_free_dev:
/*注銷設備*/
input_unregister_device(button_dev);
return err;
}
- /*設備退出函數*/
static void __exit tq2440_button_exit(void)
{
int i = 0;
input_unregister_device(button_dev);
- /*釋放中斷號*/
for(i = 0; i < NUMBERS_BUTTONS; ++ i)
{
free_irq(irqs[i],NULL);
}
}
- /*加載和卸載*/
module_init(tq2440_button_init);
module_exit(tq2440_button_exit);
- /*LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");
- 測試代碼:
- #include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<linux/input.h>
- /*事件結構體*/
struct input_event buff;
- int main(int argc ,char **argv)
{
int fd;
int count;
/*打開設備文件*/
fd = open("/dev/event0",O_RDWR);
- if(fd == -1)
{
printf("Open Failed!\n");
- exit(-1);
}
- while(1)
{
/*讀操作*/
if(count = read(fd,&buff,sizeof(struct input_event))!=0)
{
printf("type: %d\tcode: %d\t value: %d\n",buff.type,buff.code,buff.value);
}
}
close(fd);
exit(0);
}
測試效果:
從效果上來看,代碼基本上實現了按鍵的識別,但是該驅動程序的問題是按鍵并不能實現消抖操作。
這一年中最后的一天,希望自己明年更美好,堅持寫博客,作總結。