天天看點

輸入子系統

又叫做 input 子系統

一. 簡介:

輸入子系統主要涉及到比如鍵盤上的按鍵,滑鼠的按鍵,滾輪等的事件,在linux核心中相應部分叫做輸入子系統,按照子系統的規則,可以複用核心中代碼,很簡單的實作開發闆上的按鍵實作滑鼠的左鍵,鍵盤上的按鍵等的動作。

        Linux之輸入子系統分析(詳解)

總結:

1. 注冊輸入子系統,進入input_init():

建立主裝置号為13的"input"字元裝置

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

2. 在open打開驅動時,進入input_open_file():

1)更新裝置的file_oprations

file->f_op = fops_get(handler->fops);

2)執行file_oprations->open函數

err = new_fops->open(inode, file);

3. 注冊input_handler,進入input_register_handler():

1)添加到input_table[]處理數組中

input_table[handler->minor >> 5] = handler;

2)添加到input_handler_list連結清單中

list_add_tail(&handler->node, &input_handler_list);

3)判斷input_dev的id,是否有支援這個驅動的裝置

list_for_each_entry(dev, &input_dev_list, node) //周遊查找input_dev_list連結清單裡所有input_dev

input_attach_handler(dev, handler); //判斷兩者id,若兩者支援便進行連接配接。

4.注冊input_dev,進入input_register_device():

1)放在input_dev_list連結清單中

list_add_tail(&dev->node, &input_dev_list);

2)判斷input_handler的id,是否有支援這個裝置的驅動

list_for_each_entry(handler, &input_handler_list, node) //周遊查找input_handler_list連結清單裡所有input_handler

input_attach_handler(dev, handler); //判斷兩者id,若兩者支援便進行連接配接。

5.判斷input_handler和input_dev的id,進入input_attach_handler():

1)比對兩者id,

input_match_device(handler->id_table, dev); //比對input_handler和dev的id,不成功退出函數

2)比對成功調用input_handler ->connect

handler->connect(handler, dev, id); //建立連接配接

6.建立input_handler和input_dev的連接配接,進入input_handler->connect():

1)建立全局結構體,通過input_handle結構體連接配接雙方

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //建立兩者連接配接的input_handle全局結構體

list_add_tail(&handle->d_node, &handle->dev->h_list); //連接配接input_dev->h_list

list_add_tail(&handle->h_node, &handler->h_list); // 連接配接input_handle->h_list

7.有事件發生時,比如按鍵中斷,在中斷函數中需要進入input_event()上報事件:

1)找到驅動處理結構體,然後執行input_handler->event()

list_for_each_entry(handle, &dev->h_list, d_node) // 通過input_dev ->h_list連結清單找到input_handle驅動處理結構體

if (handle->open) //如果input_handle之前open 過,那麼這個就是我們的驅動處理結構體(有可能一個驅動裝置在不同情況下有不同的驅動處理方式)

handle->handler->event(handle, type, code, value); //調用evdev_event()的.event事件函數

二、 幾點說明:

上邊的部落格寫的比較詳細了,隻是有幾點需要特殊說明:

分析主要涉及的檔案有 input.c evdev.c

event方法是在什麼時候調用的:

是通過 input_event --> input_handle_event --> input_pass_event --> handler->event ..

evdev.c 中提供了read方法,此方法提供給了使用者讀的支援,可以幫助使用者調試驅動程式,evdev_read中按照 struct input_event 結構發送給使用者層。

輸入子系統
輸入子系統
輸入子系統
輸入子系統
輸入子系統

三、用到的函數簡介:

1. 配置設定一個 struct input_dev :

struct input_dev *input_allocate_device(void)

2. 把位址上的nr位置為1:

static inline void set_bit(int nr, volatile unsigned long *addr)

3. 注冊裝置:

int input_register_device(struct input_dev *dev)

4. 報告一個新的輸入事件:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

type,事件類型

code,事件碼

value,事件的值

5. 立即上報:

static inline void input_sync(struct input_dev *dev)

{

        input_event(dev, EV_SYN, SYN_REPORT, 0);

}

struct input_dev :

struct input_dev {      

       void *private;
       const char *name;  //裝置名字
       const char *phys;  //檔案路徑,比如 input/buttons
       const char *uniq;   
       struct input_id id;

 
       unsigned long evbit[NBITS(EV_MAX)];  //表示支援哪類事件,常用有以下幾種事件(可以多選)
       //EV_SYN      同步事件,當使用input_event()函數後,就要使用這個上報個同步事件
       //EV_KEY       鍵盤事件
       //EV_REL       (relative)相對坐标事件,比如滑鼠
       //EV_ABS       (absolute)絕對坐标事件,比如搖杆、觸摸屏感應
       //EV_MSC      其他事件,功能
       //EV_LED       LED燈事件
       //EV_SND      (sound)聲音事件

       //EV_REP       重複鍵盤按鍵事件
  //(内部會定義一個定時器,若有鍵盤按鍵事件一直按下/松開,就重複定時,時間一到就上報事件)   

       //EV_FF         受力事件
       //EV_PWR      電源事件
       //EV_FF_STATUS  受力狀态事件

       unsigned long keybit[NBITS(KEY_MAX)];   //存放支援的鍵盤按鍵值
                                    //鍵盤變量定義在:include/linux/input.h, 比如: KEY_L(按鍵L)

       unsigned long relbit[NBITS(REL_MAX)];    //存放支援的相對坐标值
       unsigned long absbit[NBITS(ABS_MAX)];   //存放支援的絕對坐标值
       unsigned long mscbit[NBITS(MSC_MAX)];   //存放支援的其它事件,也就是功能
       unsigned long ledbit[NBITS(LED_MAX)];    //存放支援的各種狀态LED
       unsigned long sndbit[NBITS(SND_MAX)];    //存放支援的各種聲音
       unsigned long ffbit[NBITS(FF_MAX)];       //存放支援的受力裝置
       unsigned long swbit[NBITS(SW_MAX)];     //存放支援的開關功能

 ... ...
           

需要知道的一個圖:

input.c中兩個連結清單:

input_dev_list、input_handler_list

input_handle 是輸入裝置和輸入裝置handler的紐帶或者說是提供連結。

輸入子系統

四、 執行個體:

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>
#include <mach/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 < 4; 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 < 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");



           

Makefile

KERN_DIR = ~/your path

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= buttons.o
           

2. 分析:

定義全局變量:一個所有按鍵資源的集合,一個struct input_dev的指針、一個正在操作的的按鍵結構體指針、一個定時器

輸入子系統

子產品入口函數中

輸入子系統

中斷申請函數中,将按鍵的資源作為參數傳遞給中斷相應服務函數,中斷服務函數中,将傳入的參數,傳遞給全局的 正在操作的 資源,并開始更改定時器的時間,定時器到時後,擷取此時的按鍵值,根據按鍵值發送 event 事件

輸入子系統
輸入子系統

3. 系統中驗證方法:

将nfs中的根目錄挂載到闆子的/mnt目錄下

mount -t nfs -o nolock,vers=2 192.168.1.214:/source /mnt

将編譯出來的子產品拷貝到共享目錄下

cp buttons.ko /source/

在闆子上加載子產品

> cat /dev/tty1 // 需要按入enter後才能顯示

> exec 0</dev/tty1 // 将/dev/tty1挂載到-sh程序描述符0下,此時的鍵盤驅動就會直接列印在tty1終端上

我們看一下 event 的讀的方法,之前介紹了,讀的方法是将struct input_event結構拷貝給使用者

struct input_event {

        struct timeval time; // 2個32位數

        __u16 type;

        __u16 code;

        __s32 value;

};

[email protected]:/dev/input$ od -x event1

0000000 45b1 386d 1d5a 0005 0001 001c 0001 0000

0000020 45b1 386d 1d86 0005 0000 0000 0000 0000

0000040 45b1 386d 7abd 0007 0001 001c 0000 0000

0000060 45b1 386d 7adf 0007 0000 0000 0000 0000

我們看前兩行:

0000000 表示 編号 od指令自己的

45b1 386d 表示的是32位的s ---> 值是 0x 386d 45b1

1d5a 0005 us

0001 表示的是 type

001c 表示的是code

0001 0000 表示的是值 1

第二行表示的是 input_sync

下一篇: ctrl+alt+del

繼續閱讀