歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux驅動之輸入子系統框架

Linux驅動之輸入子系統框架

日期:2017/3/1 11:49:45   编辑:關於Linux

好記性不如爛筆頭,整理一下筆記~Linux驅動之輸入子系統框架

\

輸入子系統將該類驅動劃分為3部分
1、核心層 input.c
2、設備層 Gpio_keys.c ...
3、事件處理層 Evdev.c

事件處理層為純軟件的東西,設備層涉及底層硬件,它們通過核心層建立聯系,對外提供open write等接口。


1、我們首先來看,核心層 input.c如何向外界提供接口
在 input_init 中注冊了字符設備驅動
register_chrdev(INPUT_MAJOR, "input", &input_fops);

static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};

在注冊的fops中,僅僅只有1個open函數,下面我們來看這個open函數
static int input_open_file(struct inode *inode, struct file *file)
{
// 根據次設備號,從 input_table 數組中取出對應的 handler
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;

// 將 handler 的fops賦值給file->f_op,並用調用新的open函數
old_fops = file->f_op;
file->f_op = fops_get(handler->fops);
err = file->f_op->open(inode, file);
}
那麼,我們應該可以猜到,必定有個地方創建了handler並對它進行一定的設置,提供fops函數,將它放入input_table。
就這樣,Input.c 實現了一個通用對外接口。

2、事件處理層,注冊input_handler
2.1 放入鏈表、數組(input_register_handler)
input.c input_register_handler 函數中 創建了handler並對它進行一定的設置,提供fops函數,將它放入input_table,

int input_register_handler(struct input_handler *handler)
{
// 將 handler 放入 input_table
input_table[handler->minor >> 5] = handler;

// 將 handler 放入 input_handler_list 鏈表
list_add_tail(&handler->node, &input_handler_list);

// 取出 input_dev_list 鏈表中的每一個 dev 與 該 handler 進行 比對
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
}
2.2 匹配 (input_attach_handler)
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
// 看 dev.id 是否存在於 handler->id_table 中
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
// 在的話,調用 handler->connect
error = handler->connect(handler, dev, id);
}
2.3 建立連接
我們以 Evdev.c 為例,看一下connect函數
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
// 不要關心 evdev ,只看 evdev->handle 即可,這裡構建了一個 handle ,注意不是handler
// handle 就是個 中間件,可以理解成膠帶,它把 hander 與 dev 連在一起
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

// 第一次建立聯系,在 handle 中記錄 dev 與 handle 的信息,這樣通過handle就可以找到dev與handler
// 即是 實現 handle -> dev handle -> hander 的聯系
evdev->handle.dev = dev;
evdev->handle.handler = handler;

// 創建 類 ,暫時不知道在哪 創建設備,估計是在 設備層
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);
// 注冊 handle
error = input_register_handle(&evdev->handle);
}
2.4 注冊handle,第二次建立聯系
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
// 將handle 記錄在 dev->h_list 中
list_add_tail(&handle->d_node, &handle->dev->h_list);
// 將handle 記錄在 handler->h_list 中
list_add_tail(&handle->h_node, &handler->h_list);
// 至此,dev 與 hander 也可以找到handle了,dev <-> handle <-> handler 之間暢通無阻
}
簡單梳理一下:
事件處理層,構建 handler , 通過 input_register_handler 進行注冊,注冊時
1、將 handler 放入 input_handler_list 鏈表
2、將 handler 放入 input_table
3、取出 input_dev_list鏈表中的每一個dev 調用 input_attach_handler 進行id匹配
4、如果匹配成功,則調用 handler->connect 第一次建立連接
5、創建 handle 在 handle 中記錄 dev 與 handle 的信息,這樣通過handle就可以找到dev與handler
6、在dev hander 中記錄 handle的信息,實現 dev <-> handle <-> handler


3、設備層,注冊input_dev
int input_register_device(struct input_dev *dev)
{
// 將 dev 放入 input_dev_list
list_add_tail(&dev->node, &input_dev_list);
// 設置 設備名?所謂的input0 input1 由此而來吧
snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
// 創建設備?
error = class_device_add(&dev->cdev);
// 匹配 handler ,參考 2.2-2.4
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
}


4、辛辛苦苦建立聯系,是干嘛的
在設備層,我們寫驅動的時候,比如鼠標按了一下,我們要上報event 到Handler層進行處理,然後提交給用戶程序。
例如:Gpio_keys.c 中斷處理函數中
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
input_event(input, type, button->code, !!state);
input_sync(input);
return IRQ_HANDLED;
}
又得回到input.c void input_event函數
void input_event{
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
}
最終調用 handler->event(handle, type, code, value);
好吧,Evdev.c 中的 event 函數看不懂。
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
......看不懂
// 喚醒 休眠
wake_up_interruptible(&evdev->wait);
}
// 讀函數中 休眠
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
//如果無數據可讀,且為非阻塞方式 立刻返回
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);
//將內核空間數據拷貝到用戶空間,略
return retval;
}


5、寫一個Input子系統 設備驅動
事件處理層不用我們管了,- -是暫時能力有限管不了。寫寫設備層的程序就好了。
軟件設計流程:
/* 1. 分配一個Input_dev結構體 */
/* 2. 設置 支持哪一類事件,該類事件裡的那些事件*/
/* 3.注冊 */
/* 4.硬件相關操作 */


事件類型:

struct input_dev {

        void *private;                           //輸入設備私有指針,一般指向用於描述設備驅動層的設備結構

        const char *name;                        //提供給用戶的輸入設備的名稱
        const char *phys;                        //提供給編程者的設備節點的名稱
        const char *uniq;                        //指定唯一的ID號,就像MAC地址一樣
        struct input_id id;                      //輸入設備標識ID,用於和事件處理層進行匹配

        unsigned long evbit[NBITS(EV_MAX)];      //位圖,記錄設備支持的事件類型
		/*
		 *	#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	//LED事件
		 *	#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
		 */
        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)];      //位圖,記錄設備支持的開關功能

        unsigned int keycodemax;                //設備支持的最大按鍵值個數
        unsigned int keycodesize;               //每個按鍵的字節大小
        void *keycode;                          //指向按鍵池,即指向按鍵值數組首地址
        int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);        //修改按鍵值
        int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);       //獲取按鍵值

        struct ff_device *ff;                        

        unsigned int repeat_key;                //支持重復按鍵
        struct timer_list timer;                //設置當有連擊時的延時定時器

        int state;                

        int sync;       //同步事件完成標識,為1說明事件同步完成

        int abs[ABS_MAX + 1];                //記錄坐標的值
        int rep[REP_MAX + 1];                //記錄重復按鍵的參數值

        unsigned long key[NBITS(KEY_MAX)];   //位圖,按鍵的狀態
        unsigned long led[NBITS(LED_MAX)];   //位圖,led的狀態
        unsigned long snd[NBITS(SND_MAX)];   //位圖,聲音的狀態
        unsigned long sw[NBITS(SW_MAX)];     //位圖,開關的狀態

        int absmax[ABS_MAX + 1];             //位圖,記錄坐標的最大值
        int absmin[ABS_MAX + 1];             //位圖,記錄坐標的最小值
        int absfuzz[ABS_MAX + 1];            //位圖,記錄坐標的分辨率
        int absflat[ABS_MAX + 1];            //位圖,記錄坐標的基准值

        int (*open)(struct input_dev *dev);                       	//輸入設備打開函數
        void (*close)(struct input_dev *dev);                       //輸入設備關閉函數
        int (*flush)(struct input_dev *dev, struct file *file);     //輸入設備斷開後刷新函數
        int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);        //事件處理

        struct input_handle *grab;            

        struct mutex mutex;               	 	//用於open、close函數的連續訪問互斥
        unsigned int users;                

        struct class_device cdev;        		//輸入設備的類信息
        union {                                	//設備結構體
                struct device *parent;
        } dev;

        struct list_head        h_list;        	//handle鏈表
        struct list_head        node;        	//input_dev鏈表
};



參考程序:基於mini2440 linux2.6.32內核
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include   //s3c2410_gpio_getpin
#include 	//S3C2410_GPG(x)
#include 
#include 
#include 
#include 
#include 

//中斷觸發方式的 一些宏定義
#define __IRQT_FALEDGE	IRQ_TYPE_EDGE_FALLING
#define __IRQT_RISEDGE	IRQ_TYPE_EDGE_RISING
#define __IRQT_LOWLVL	IRQ_TYPE_LEVEL_LOW
#define __IRQT_HIGHLVL	IRQ_TYPE_LEVEL_HIGH

#define IRQT_NOEDGE	(0)
#define IRQT_RISING	(__IRQT_RISEDGE)
#define IRQT_FALLING	(__IRQT_FALEDGE)
#define IRQT_BOTHEDGE	(__IRQT_RISEDGE|__IRQT_FALEDGE)
#define IRQT_LOW	(__IRQT_LOWLVL)
#define IRQT_HIGH	(__IRQT_HIGHLVL)
#define IRQT_PROBE	IRQ_TYPE_PROBE

#define DEBUG printk(KERN_ERR "%d\n",__LINE__)

static struct input_dev *buttons_dev = NULL;	/* 創建input_dev結構體指針 */

static struct timer_list buttons_timer;	//定時器去抖動

struct keys_desc{
	unsigned int irq;
	unsigned char *name;
	unsigned int key_addr;
	unsigned char key_value;
	int pin_state;
};

static struct keys_desc *key_desc = NULL;

struct keys_desc keys_desc[6]={
	{IRQ_EINT8,  "S1", S3C2410_GPG(0)  ,KEY_L ,S3C2410_GPG0_EINT8},
	{IRQ_EINT11, "S2", S3C2410_GPG(3)  ,KEY_S ,S3C2410_GPG3_EINT11},
	{IRQ_EINT13, "S3", S3C2410_GPG(5)  ,KEY_ENTER ,S3C2410_GPG5_EINT13},
	{IRQ_EINT14, "S4", S3C2410_GPG(6)  ,KEY_1 ,S3C2410_GPG6_EINT14},
	{IRQ_EINT15, "S5", S3C2410_GPG(7)  ,KEY_2 ,S3C2410_GPG7_EINT15},
	{IRQ_EINT19, "S6", S3C2410_GPG(11) ,KEY_3 ,S3C2410_GPG11_EINT19},
};


static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	key_desc = (struct keys_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);

	return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer_function(unsigned long data){
	DEBUG;	
	unsigned int keyval;

	if(key_desc == NULL) return;	
	DEBUG;
	//定時器啟動的時候會首先中斷一次,因為沒設置時間默認為0
	keyval = s3c2410_gpio_getpin(key_desc->key_addr);
	DEBUG;
	printk("keyval: %d\n", keyval);
	if (keyval)
	{
		DEBUG;
		
		input_event(buttons_dev, EV_KEY ,key_desc->key_value ,0);
		input_sync(buttons_dev);
	}
	else
	{
		DEBUG;
		
		input_event(buttons_dev, EV_KEY ,key_desc->key_value ,1);
		input_sync(buttons_dev);
	}
}

static int __init input_drv_init(void){
	int error,i;
	
	DEBUG;
	
/* 1. 分配一個Input_dev結構體 */
	buttons_dev = input_allocate_device();
	if(buttons_dev == NULL){
		printk(KERN_ERR "Unable to allocate input device\n");
	}
	
/* 2. 設置 */
	set_bit(EV_KEY, buttons_dev->evbit); 	//設置設備支持的事件類型為按鍵類型
	set_bit(KEY_L, buttons_dev->keybit);	//設置支持哪些 按鍵類型 
	set_bit(KEY_S, buttons_dev->keybit);	//設置支持哪些 按鍵類型 
	set_bit(KEY_ENTER, buttons_dev->keybit);//設置支持哪些 按鍵類型 
	set_bit(KEY_1, buttons_dev->keybit);	//設置支持哪些 按鍵類型 
	set_bit(KEY_2, buttons_dev->keybit);	//設置支持哪些 按鍵類型 
	set_bit(KEY_3, buttons_dev->keybit);	//設置支持哪些 按鍵類型 

/* 3.注冊 */
	error = input_register_device(buttons_dev);
	if (error) {
		printk(KERN_ERR "Unable to register gpio-keys input device\n");
	}
	
/* 4.硬件相關操作 */
	/* 定時器 */
	init_timer(&buttons_timer);	//初始化定時器
	buttons_timer.function = buttons_timer_function;//綁定定時器處理函數	
	add_timer(&buttons_timer);//將定時器加到timer列表中去,啟動定時器
	/* 注冊中斷 */
	for(i = 0; i < 6; i++){
		s3c2410_gpio_cfgpin(keys_desc[i].key_addr, keys_desc[i].pin_state);//新增
		request_irq(keys_desc[i].irq,  buttons_irq, IRQT_BOTHEDGE, 
			keys_desc[i].name, &keys_desc[i]);
	}
	DEBUG;
	return 0;
}

static void __exit input_drv_exit(void){
	int i;
	for(i=0; i<6; i++){
		free_irq(keys_desc[i].irq, &keys_desc[i]);
	}
	del_timer(&buttons_timer);
	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);//新增
	DEBUG;
}

module_init(input_drv_init);
module_exit(input_drv_exit);
MODULE_LICENSE("GPL");


Copyright © Linux教程網 All Rights Reserved