Linux之Input驅動總結
--本貼所貼代碼均在mtk系統上完成測試
對於嵌入式產品而言,人機交互的一部分“輸入設備”扮演了一個不可或缺的角色,下面來分析在Linux下的輸入設備。
一、概述Linux下的input設備
所有的用於對設備進行輸入操作的設備,如:鼠標、觸摸屏、搖桿、鍵盤等都是輸入設備,這種設備在Linux都是典型的字符設備。其工作機制一般都是輸入操作產生一個外部中斷或通過查詢掃描當前狀態,然後啓動某種通信方式來獲取按鍵值或座標值或狀態值放在一個緩衝區,字符設備驅動管理該緩衝區,而驅動的read()接口讓用戶可以讀取鍵值,座標等數據。
貼一下ipnut設備在Linux下的官方結構圖,方便理解:
(這些都是官方的表述,雖然枯燥,但是仔細體會後能有所收穫)。
這是輸入子系統的組成結構,它是由輸入子系統設備驅動層、輸入子系統核心層(Input Core)和輸入子系統事件處理層(Event Handler)組成。其中設備驅動層提供對硬件各寄存器的讀寫訪問和將底層硬件對用戶輸入訪問的響應轉換爲標準的輸入事件,再通過核心層提交給事件處理層;而核心層對下提供了設備驅動層的編程接口,對上又提供了事件處理層的編程接口;而事件處理層就爲我們用戶空間的應用程序提供了統一訪問設備的接口和驅動層提交來的事件處理。所以這使得我們輸入設備的驅動部分不在用關心對設備文件的操作,而是要關心對各硬件寄存器的操作和提交的輸入事件。
下圖是Linux下的input系統的分層圖:
二、input驅動程序分析
Input設備的最重要的結構體是input_dev結構體,它定義在input.h文件中,它包含了你所要該輸入設備驅動的所有信息,包括一些api的接口;下面按照下面的步驟我們創建一個在混雜設備驅動中註冊的input設備,在源碼中分析一些重要接口函數
1)、分配一個輸入子設備;
2)、在驅動模塊加載函數中設置Input設備支持input子系統的哪些事件;
3)、將Input設備註冊到input子系統中;
4)、在Input設備發生輸入操作時(如:鍵盤被按下/擡起、觸摸屏被觸摸/擡起/移動、鼠標被移動/單擊/擡起時等),提交所發生的事件及對應的鍵值/座標等狀態;
5)、釋放和卸載。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <mach/hardware.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/kobject.h>
#include <linux/earlysuspend.h>
#include <linux/platform_device.h>
#include <asm/atomic.h>
#define DEVICE_NAME "Driver_Modules" //在dev下的,該misc設備節點名稱
#define ON 1
#define OFF 0
static struct input_dev *Modules_input_dev;
//在這個ioctl函數中做的事情就是發送一個 音量+的press和reless兩個狀態。
static long Driver_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
long err = 0;
printk("[Modules]Driver_ioctl\n");
input_report_key(Modules_input_dev, KEY_VOLUMEUP, 1);
input_sync(Modules_input_dev);
//發送按鍵狀態就這兩個,報告按鍵狀態及按鍵值,然後報告同步事件。。
input_report_key(Modules_input_dev, KEY_VOLUMEUP, 0);
input_sync(Modules_input_dev);
return err;
}
static int Driver_open(struct inode *inode, struct file *file)
{
int err = 0;
printk("[Modules]Driver_open\n");
return err ;
}
static int Driver_close(struct inode *inode, struct file *file)
{
int err = 0;
printk("[Modules]Driver_close\n");
return err ;
}
static int Modules_input_open(struct input_dev *dev)
{
printk("[Modules]Modules_input_open\n");
return 0;
}
//在這個結構體我想就不用介紹了,熟悉字符設備驅動的人都知道這個東西,所有應用層
//對驅動層的操作都是通過這裏實現的。
static struct file_operations DriverModules_ops = {
.owner = THIS_MODULE,
.open = Driver_open,
.release = Driver_close,
.unlocked_ioctl = Driver_ioctl,
};
static struct miscdevice DriverModules_misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &DriverModules_ops,
};
static int __init Driver_init(void)
{
int ret,r;
//完成對input設備的正常操作,下面的註冊是最重要的。 Modules_input_dev = input_allocate_device(); //分配一個輸入設備
if (!Modules_input_dev)
return -ENOMEM;//描述一些input設備的信息,通過cat /proc/bus/input/devices 命令可以查看所有的input設備的信息
Modules_input_dev->name = "Modules_input"; //input設備的名稱
Modules_input_dev->id.bustype = BUS_HOST; //總線類型
Modules_input_dev->id.vendor = 0x8888; //產家編號
Modules_input_dev->id.product = 0x6575; //產品編號
Modules_input_dev->id.version = 0x0010; //版本信息
Modules_input_dev->open = Modules_input_open; //指定一個該設備的接口
__set_bit(EV_KEY, Modules_input_dev->evbit); //添加事件的支持,告訴系統將要支持哪些事件,下面再詳細介紹。
//按鍵事件下所支持的按鍵值
__set_bit(KEY_VOLUMEDOWN, Modules_input_dev->keybit);
__set_bit(KEY_VOLUMEUP, Modules_input_dev->keybit);
//完成對一個輸入設備的註冊
r = input_register_device(Modules_input_dev);
if (r)
{
printk("[Modules]register input device failed (%d)\n", r);
input_free_device(Modules_input_dev);//釋放輸入設備
return r;
}
//註冊一個混雜的字符設備,這種註冊方式會自動創建設備節點(屬於一種特殊的字符設備驅動),它的主設備號是10,註冊成功會返回0,註冊失敗會返回一個錯誤碼;
ret = misc_register(&DriverModules_misc_dev);
if (ret)
{
printk("[Modules]register misc_register failed (%d)\n", ret);
input_unregister_device(Modules_input_dev);
return ret;
}
printk("[Modules]Driver Modules init \n");
}
static void __exit Driver_exit(void)
{
misc_deregister(&DriverModules_misc_dev); //卸載混雜設備
input_unregister_device(Modules_input_dev); //卸載輸入設備
printk("[Modules]Driver Modules exit \n");
}
module_init(Driver_init);
module_exit(Driver_exit);
MODULE_AUTHOR("tangh in szsimtech");
MODULE_DESCRIPTION("char Driver modules");
MODULE_LICENSE("GPL");
下面的補充我也是找了貼上去的,至於這個兩種上報方式有啥區別,我沒去深究,在以後用的時候再做補充吧(或者好心網友幫我補充也行)。。。。
**__set_bit(EV_KEY, Modules_input_dev->evbit);
Struct input_dev中有兩個成員,一個是evbit;一個是keybit.分別用來表示設備所支持的事件類型和按鍵類型。
Linux中輸入設備的事件類型有(這裏只列出了常用的一些,更多請看linux/input.h中):EV_SYN 0x00 同步事件
EV_KEY 0x01 按鍵事件
EV_REL 0x02 相對座標
EV_ABS 0x03 絕對座標
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 聲音
EV_REP 0x14 Repeat
EV_FF 0x15 力反饋
//報告指定type,code的輸入事件
Void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);
//報告鍵值
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_sync(struct input_dev *dev);//報告同步事件
在觸摸屏驅動設計中,一次座標及按下狀態的整個報告過程如下:
Input_report_abs(input_dev,ABS_X,x);//X座標
Input_report_abs(input_dev,ABS_Y,y);//Y座標
Input_report_abs(input_dev,ABS_PRESSURE,pres);//壓力
input_sync(struct input_dev *dev);//同步
三、虛擬按鍵的處理
四、測試