我的環(huán)境:
Fedora14 內(nèi)核版本,2.6.38.1
開發(fā)板:TQ2440
移植內(nèi)核版本:2.6.30.4
2.6版本的內(nèi)核中,驅(qū)動的開發(fā)逐漸發(fā)展成基于總線模型等一定結(jié)構(gòu)的開發(fā)模式,采用了分層的設(shè)計思想,這樣的變化使得驅(qū)動開發(fā)的工作量相對而言越來越少,但是也增加了我們閱讀、分析源碼的思想的難度。
Linux輸入子系統(tǒng)就是一個基于分層模式的系統(tǒng),其基本的層次分解如下圖所示。
在圖中我們可以發(fā)現(xiàn)輸入子系統(tǒng)主要包括三個部分設(shè)備驅(qū)動層(input driver)、核心層(input core)和輸入事件驅(qū)動層。輸入子系統(tǒng)的劃分使得輸入設(shè)備的驅(qū)動程序設(shè)計越來越簡單,但是其中的思想采用我們學(xué)習(xí)的重點和難點。
Input子系統(tǒng)處理輸入事務(wù),任何輸入設(shè)備的驅(qū)動程序都可以通過Input輸入子系統(tǒng)提供的接口注冊到內(nèi)核,利用子系統(tǒng)提供的功能來與用戶空間交互。輸入設(shè)備一般包括鍵盤,鼠標(biāo),觸摸屏等,在內(nèi)核中都是以輸入設(shè)備出現(xiàn)的。下面分析input輸入子系統(tǒng)的結(jié)構(gòu),以及功能實現(xiàn)。
1. Input子系統(tǒng)是分層結(jié)構(gòu)的,總共分為三層:硬件驅(qū)動層,子系統(tǒng)核心層,事件處理層。
(1)、其中硬件驅(qū)動層負責(zé)操作具體的硬件設(shè)備,這層的代碼是針對具體的驅(qū)動程序的,需要驅(qū)動程序的作者來編寫。
(2)、子系統(tǒng)核心層是鏈接其他兩個層之間的紐帶與橋梁,向下提供驅(qū)動層的接口,向上提供事件處理層的接口。
(3)、事件處理層負責(zé)與用戶程序打交道,將硬件驅(qū)動層傳來的事件報告給用戶程序。
2. 各層之間通信的基本單位就是事件,任何一個輸入設(shè)備的動作都可以抽象成一種事件,如鍵盤的按下,觸摸屏的按下,鼠標(biāo)的移動等。事件有三種屬性:類型(type),編碼(code),值(value),Input子系統(tǒng)支持的所有事件都定義在input.h中,包括所有支持的類型,所屬類型支持的編碼等。事件傳送的方向是硬件驅(qū)動層-->子系統(tǒng)核心-->事件處理層-->用戶空間。
在驅(qū)動程序設(shè)計中,我們對于設(shè)備的驅(qū)動設(shè)計主要集中在設(shè)備驅(qū)動層的實現(xiàn),但是這與之前的設(shè)備驅(qū)動開發(fā)存在較大的差別,主要是因為設(shè)備驅(qū)動不再是編寫基本操作的實現(xiàn)過程,也就是不在是對struct file_operations 這個結(jié)構(gòu)體對象的填充和實現(xiàn)。在輸入設(shè)備驅(qū)動中的主要實現(xiàn)包括下面幾個過程:
1、分配一個輸入設(shè)備對象。并完成響應(yīng)結(jié)構(gòu)體元素的填充,主要包括支持的事件類型和事件代號等。
分配對象的函數(shù):
struct input_dev *input_allocate_device(void);
釋放對象函數(shù):
void input_free_device(struct input_dev *dev);
設(shè)置支持的事件類型和事件代碼:
通常采用set_bit函數(shù)實現(xiàn):
設(shè)置支持的事件類型(支持按鍵事件)
set_bit(EV_KEY, input_dev->evbit);
設(shè)置支持的事件代碼(支持按鍵1)
set_bit(KEY_1, input_dev->keybit);
2、完成輸入設(shè)備對象的注冊,將設(shè)備對象注冊到輸入子系統(tǒng)當(dāng)中去,當(dāng)然也有對應(yīng)的釋放函數(shù)。
注冊設(shè)備到內(nèi)核:
int input_register_device(struct input_dev *dev);
注銷設(shè)備:
void input_unregister_device(struct input_dev *dev);
3、向核心層(input core)匯報事件的發(fā)生以及傳輸事件類型和事件代碼等。這一部分通常是采用中斷的方法實現(xiàn),在中斷中向上一層次(Input Core)傳送發(fā)生事件的事件類型、事件代號以及事件對應(yīng)的值等。但是上報的內(nèi)容結(jié)構(gòu)體都是基于一個固定結(jié)構(gòu)體的
struct input_event,在用戶空間也可以采用這個結(jié)構(gòu)體實現(xiàn)對事件的訪問。
struct input_event {
/*事件發(fā)生的時間*/
struct timeval time;
/*事件類型*/
__u16 type;
/*事件代號*/
__u16 code;
/*事件對應(yīng)的值*/
__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
…
主要的匯報函數(shù)如下:
/*匯報鍵值函數(shù)*/
void input_report_key(struct input_dev *dev, unsigned int code, int value);
/*匯報相對坐標(biāo)值函數(shù)*/
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
/*匯報絕對坐標(biāo)值函數(shù)*/
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
也可以采用更加一般的函數(shù)匯報,上面的三個函數(shù)是通過下面這個函數(shù)實現(xiàn)的。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
完成上面的三個部分,一個輸入設(shè)備的驅(qū)動程序也就完成了。但是其中的具體實現(xiàn)還需要閱讀相關(guān)的源碼。特別是設(shè)備的操作是如何通過輸入設(shè)備實現(xiàn)等基本的操作是我們應(yīng)該去關(guān)注的,設(shè)備的具體操作函數(shù)實質(zhì)上已經(jīng)因為一些共性被設(shè)計成了通用的接口,在輸入子系統(tǒng)內(nèi)部已經(jīng)實現(xiàn)。
TQ2440中的按鍵主要是采用外部中斷的形式實現(xiàn),所以我在實驗過程中也采用了外部中斷的模式直接對按鍵進行操作。
具體的程序如下所示:驅(qū)動代碼:
- #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]);
/*匯報端口的值,實質(zhì)上是將事件key[i]匯報*/
input_report_key(button_dev,keys[i],val);
/*匯報同步,也就是完成匯報工作*/
input_sync(button_dev);
break;
}
}
/*返回值*/
return IRQ_RETVAL(IRQ_HANDLED);
}
- /*設(shè)備初始化函數(shù)*/
- static int __init tq2440_button_init(void)
{
int err,i = 0;
/*主要要先申請設(shè)備,然后申請中斷*/
/*申請輸入設(shè)備*/
button_dev = input_allocate_device();
if(NULL == button_dev)
{
printk(KERN_ERR "not enough memory\n");
err = - ENOMEM;
}
/*設(shè)置相應(yīng)的事件處理類型,按鍵事件*/
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);
/*設(shè)置案件的代碼值,即code值*/
set_bit(keys[i],button_dev->keybit);
}
/*設(shè)備名,防止出現(xiàn)錯誤*/
button_dev->name = input_name;
/*注冊輸入設(shè)備*/
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:
/*注銷設(shè)備*/
input_unregister_device(button_dev);
return err;
}
- /*設(shè)備退出函數(shù)*/
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>
- /*事件結(jié)構(gòu)體*/
struct input_event buff;
- int main(int argc ,char **argv)
{
int fd;
int count;
/*打開設(shè)備文件*/
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);
}
測試效果:
從效果上來看,代碼基本上實現(xiàn)了按鍵的識別,但是該驅(qū)動程序的問題是按鍵并不能實現(xiàn)消抖操作。
這一年中最后的一天,希望自己明年更美好,堅持寫博客,作總結(jié)。