一、輸入子系統驅動層分析
在鍵盤驅動代碼分析的筆記中,接觸到了input子系統,鍵盤驅動,鍵盤驅動將檢測到的所有按鍵都上報給了input子系統。Input子系統是所有I/O設備驅動的中間層,爲上層提供了一個統一的界面。例如,在終端系統中,我們不需要去管有多少個鍵盤,多少個鼠標。它只要從input子系統中去取對應的事件(按鍵,鼠標移位等)就可以了。今天就對input子系統做一個詳盡的分析。
輸入子系統由驅動層、輸入子系統核心、事件處理層三部分組成。一個輸入事件,如鼠標移動、鍵盤按下等通過Driver->Inputcore->Event handler->userspace的順序到達用戶控件的應用程序,具體的流程可以用下圖描敘。
1、驅動層:將底層的硬件輸入轉化爲統一事件形式,向輸入核心(Input Core)彙報;6
2、輸入子系統核心:承上啓下,爲驅動層提供輸入設備註冊與操作接口,如:input_register_device,通知事件處理層對事件進行處理,在/Proc下產生相應的設備信息;
3、事件處理層:主要是和用戶空間交互。(Linux中在用戶空間將所有的設備都當初文件來處理,由於在一般的驅動程序中都有提供fops接口,以及在/dev下生成相應的設備文件nod,這些操作在輸入子系統中由事件處理層完成);
4、設備描述:
struct input_dev {
const char *name; //名字
const char *phys;
const char *uniq;
struct input_id id; //輸入id
unsigned long evbit[NBITS(EV_MAX)]; // 表示能產生哪類事件
unsigned long keybit[NBITS(KEY_MAX)]; // 表示能產生哪些按鍵
unsigned long relbit[NBITS(REL_MAX)]; // 表示能產生哪些相對位移事件, x,y,滾輪
unsigned long absbit[NBITS(ABS_MAX)]; // 表示能產生哪些絕對位移事件, x,y
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
...
}
實現設備驅動核心工作是:向系統報告按鍵、觸摸屏等輸入事件event,通過input_event結構描述,不再需要關心文件操作接口。驅動報告事件經過inputCore和Eventhandler到達用戶空間。
b、註冊輸入設備函數:int input_register_device(struct input_dev *dev)
c、註銷輸入設備函數:void input_unregister_device(struct input_dev *dev)
d、驅動實現——初始化(事件支持):--> probe函數
set_bit()告訴input輸入子系統支持哪些事件,哪些按鍵。例如:
set_bit()告訴input輸入子系統支持哪些事件,哪些按鍵。例如:
/* 設置按鍵能產生哪類事件 */
set_bit(EV_KEY,buttons_dev->evbit);
set_bit(EV_REP,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_LEFTSHIFT,buttons_dev->keybit);
struct input_dev中有兩個成員爲:
evbit:事件類型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
在input.h中有這些類的定義:
#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
#define EV_CNT (EV_MAX+1)
keybit:按鍵類型(當事件類型爲EV_KEY時包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)
e、驅動實現——報告事件:
用於報告EV_KEY,EV_REL,EV_ABS事件的函數分別爲
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)
f、驅動實現——報告結束:
input_sync()同步用於告訴input core子系統報告結束。
總結而言:
probe爲input子系統做了三件事:
(1)首先調用input_allocate_device創建一個input_dev對象;
(2)然後設置設備input_dev的各種屬性以告訴input core你將提供哪些事件;
(3)最後調用input_register_device把input_dev註冊到input core;
實例分析(按鍵中斷程序):
//按鍵初始化
static int __init button_init(void)
{//申請中斷
if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
return –EBUSY;
set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
set_bit(BTN_0,button_dev.keybit); //支持設備兩個鍵
set_bit(BTN_1,button_dev.keybit); //
input_register_device(&button_dev);//註冊input設備
}
/*在按鍵中斷中報告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//讀取寄存器BUTTON_PORT0的值
input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
input_sync(&button_dev);
}
總結:input子系統仍然是字符設備驅動程序,但是代碼量減少很多,input子系統只需要完成兩個工作:初始化和事件報告(這裏在linux中是通過中斷來實現的)。
二、input設備註冊分析
1、Input設備註冊的接口爲:input_register_device()
/kernel/driver/input/input.c
int input_register_device(struct input_dev *dev)
{
//這個原子變量,代表總共註冊的input設備,每註冊一個加1,因爲是靜態變量,所以每次調用都不會清零的
static atomic_t input_no = ATOMIC_INIT(0);
struct input_devres *devres = NULL;
struct input_handler *handler;
unsigned int packet_size;
const char *path;
int error;
if (dev->devres_managed) {
devres = devres_alloc(devm_input_device_unregister,
sizeof(struct input_devres), GFP_KERNEL);
if (!devres)
return -ENOMEM;
devres->input = dev;
}
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit);//EN_SYN是設備都要支持的事件類型所以要設置
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev);
packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size;
dev->max_vals = max(dev->hint_events_per_packet, packet_size) + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals) {
error = -ENOMEM;
goto err_devres_free;
}
/*
//rep主要是處理重複按鍵,如果沒有定義dev->rep[REP_DELAY]和dev->rep[REP_PERIOD],
//則將其賦值爲默認值。dev->rep[REP_DELAY]是指第一次按下多久算一次,這裏是250ms,
//dev->rep[REP_PERIOD]指如果按鍵沒有被擡起,每33ms算一次。
*/
init_timer(&dev->timer);//這個內核定時器是爲了重複按鍵而設置的
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
//如果沒有定義有關重複按鍵的相關值,就用內核默認的
}
/*如果dev沒有定義getkeycode和setkeycode,則賦默認值。他們的作用一個是獲得鍵的掃描碼,一個是設置鍵的掃碼*/
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
//設置input_dev中device的名字,這個名字會在/class/input中出現
dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no)-1);
//添加input設備,註冊到linux設備模型中,生成一系列的sys相關文件,udev會根據dev文件生成設備節點
error = device_add(&dev->dev);
if (error) goto err_free_vals;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",dev->name ? dev->name : "Unspecified device", path ?
path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error) goto err_device_del;
/* 如果input device沒有定義getkeycode和setkeycode,則將其賦默認值,這兩個操作函數就可以用來取鍵的掃描碼和設置鍵的掃描碼,然後調用device_add()將input_dev中封裝的device註冊到sysfs;*/
//將新分配的input設備連接到input_dev_list鏈表上
list_add_tail(&dev->node, &input_dev_list);
//遍歷input_handler_list鏈表,配對input_dev和input_handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();//與proc文件系統有關
mutex_unlock(&input_mutex);
if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
/* 這裏就是重點了,將input device 掛到input_dev_list鏈表上.然後,對每一個掛在input_handler_list的handler調用input_attach_handler(),在這裏的情況有好比設備模型中的device和driver的匹配,所有的input device都掛在input_dev_list鏈上。所有的handle都掛在input_handler_list上。*/
下面介紹詳細的匹配過程:。。。。。
return 0;
......
}
三、input_device和input_handle匹配過程
1、一般來說input_handler的註冊會在input_dev之前註冊,常見的input_handler有:mousedev handler(處理來自鼠標類的Input事件),joydev_handler(處理搖桿類事件),kdev_handler(出來來自鍵盤類事件),evdev_handler(響應絕大部分的事件,默認的input處理事件)。先看input的handler的註冊函數,Handler註冊的接口如下所示:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) { //添加到全局數組中
//爲什麼會這樣呢,因爲每個handler都會處理最大32個input_dev,所以要以minor的32爲倍數對齊,這個minor是傳進來的handler的MINOR_BASE
//每一個handler都有一個這一個MINOR_BASE,以evdev爲例,EVDEV_MINOR_BASE = 64,可以看出系統總共可以註冊8個handler
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
//連接到input_handler_list鏈表中
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler); //device和handle匹配函數
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
handler->minor表示對應input設備節點的次設備號,以handler->minor右移五位做爲索引值插入到input_table[ ]中.....之後再來分析input_talbe[ ]的作用,然後將handler掛到input_handler_list中,然後將其與掛在input_dev_list中的input device匹配,這個過程和input device的註冊有相似的地方,都是註冊到各自的鏈表,然後與另外一條鏈表的對象相匹配。
2、匹配是在input_attach_handler()中完成的。代碼如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
//這個是主要的配對函數,主要比較id中的各項
id = input_match_device(handler, dev);
if (!id) return -ENODEV;
//配對成功調用handler的connect函數,這個函數在事件處理器中定義,主要生成一個input_handle結構,並初始化,還生成一個事件處理器相關的設備結構
error = handler->connect(handler, dev, id);//調用evdev_connect
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
先來匹配handle->id和dev->id中的數據,如果匹配成功,則調用handler->connect()。每個handler在註冊的時候都有自己的id_table,如果設備和input handler能夠匹配成功的話,就會調用input handler的connect函數,在Input_match_device中會將input device的id.bus type vendor和id.version首先匹配,然後回去match的evbit等。
3、具體的數據匹配過程是在input_match_device()中完成的。代碼如下:
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev) {
const struct input_device_id *id;
//id->driver_info=1,表示可以配對所有
for (id = handler->id_table; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue;
if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue;
if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
continue;
if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
continue;
if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
continue;
if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
continue;
if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
continue;
if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
continue;
if (!handler->match || handler->match(handler, dev))//沒有match函數
return id;
}
return NULL;
}
在id->flags中定義了要匹配的項,定義INPUT_DEVICE_ID_MATCH_BUS,則是要比較input device和input handler的總線類型。INPUT_DEVICE_ID_MATCH_VENDOR,INPUT_DEVICE_ID_MATCH_PRODUCT,INPUT_DEVICE_ID_MATCH_VERSION分別要求設備廠商。設備號和設備版本。
如果id->flags定義的類型匹配成功,或者是id->flags沒有定義,就會進入到MATCH_BIT的匹配項了,從MATCH_BIT宏的定義可以看出,只有當iput device和input handler的id成員在evbit、keybit......swbit項相同纔會匹配成功。而且匹配的順序是從evbit,keybit到swbit.只要有一項不同,就會循環到id中的下一項進行比較。
簡而言之,註冊input device的過程就是爲input device設置默認值,並將其掛以input_dev_list.與掛載在input_handler_list中的handler相匹配,如果匹配成功,就會調用handler的connect函數。
如果handler和device能夠匹配上,就會創建一個evdev,就會調用handler的match的回調函數,它裏邊封裝了一個handle,會把input_dev和input_handler關聯到一起。
在evdev_connect,它設置設備名,初始化input_handle中各個數據成員(關鍵是其input_dev和input_handler),然後再調用input_register_handle把evdev中的input_handle添加到input_dev的h_lis鏈表中,並且把此input_handle添加到input_handler的h_list鏈表中。從此它們的三角關係建立完成。注:當用戶每打開一次它就要創建一個evdev_client,並加入到client_list鏈表中,當input_dev產生事件時,evdev_event函數將把此input_event放入evdev->client_list鏈表中的每個evdev_client的buffer中。它們的關係如下圖所示:
可以看到evdev是作爲一個通用的handler去處理input_device的事件,也就是說一旦有設備註冊就會去調用evdev的connect函數。
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error;
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配evdev空間
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
evdev->exist = true;
dev_no = minor;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE;
dev_set_name(&evdev->dev, "event%d", dev_no);//命名/dev/evdev_num
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
//初始化evdev->dev結構,所屬類型指向input_class
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
//註冊input->handle結構
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
//註冊字符設備,添加文件操作接口file_opertions
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle;
//將evdev->dev註冊到sysfs文件系統中
error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
err_free_minor:
input_free_minor(minor);
return error;
}
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
這裏面又有個設備註冊的過程,重新創建了一個evdev設備,存在於/dev/input/eventX。這是Android上層需要直接操作的文件節點,就是在這個時候註冊的,input_register_handle也是將匹配好的input設備和input handler 分別加到自己的input設備鏈表和handler鏈表,Evdev對應的設備節點一般位於/dev/input/event0 ~ /dev/input/event4,理論上可以對應32個設備節點,分別代表被handler匹配的32個input device。 可以用cat /dev/input/event0.然後移動鼠標或者鍵盤按鍵就會有數據輸出(兩者之間只能選一,因爲一個設備文件只能關能一個輸入設備),還可以往這個文件裏寫數據,使其產生特定的事件,。
到這裏整個kernel層的從設備的初始化到創建,再到與hal層交互的的工作就已經介紹完畢。
以上文字敘述的具體流程用UML圖繪製如下圖所示:
---------------------
作者:frank-zyp
來源:CSDN
原文:https://blog.csdn.net/u013604527/article/details/53432623
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!