韋東山視訊實驗之Input子系統分析之一
原理闡述:主要有驅動層,input核心層,handler處理層,應用層
當有按鍵觸發時, 從前往後一直傳送到應用層
先看一張韋老師提供的一張圖,下面會進行更詳細的介紹
除錯問題:
1. Unspecified device as /devices/virtual/input/input0
在裡面初始化的時候,初始name即可
加入 button_dev->name = "keys";之類的即可
2. 為什麼指向了/devices/virtual/input/input0這個檔案 ??
裝置模型裡面進行了詳盡的闡述
input子系統流程分析(以linux2.6.32核心闡述,其他版本的可能有區別)
看一下input_int()(drivers\input\input.c)
err = class_register(&input_class);
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
這裡面註冊input_dev 類,指向底層的kobject,這裡的作用需要看裝置模型有關內容,主要是為使用者空間操作驅動而用,
會在sysfs目錄下建立class檔案,包括裝置和屬性,以便熱插拔裝置的處理,這裡面init加入kobject就是熱插拔,
有興趣的可以檢視/sbin/hotplug有關內容,此處不在描述
err = input_proc_init();
這個函式會註冊proc/input/,目錄下有devices、handler兩個檔案,這裡面主要是對老舊的proc的操作
具體proc與sys的區別,就是新舊的區別,但是核心還是會支援proc老式的使用者與核心交流的介面
# cat devices
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="keys"
P: Phys=
S: Sysfs=/devices/virtual/input/input0
U: Uniq=
H: Handlers=kbd event0
B: EV=100003
B: KEY=440 90000000# cat handlers
N: Number=0 Name=kbd
N: Number=1 Name=mousedev Minor=32
N: Number=2 Name=evdev Minor=64
下面會進行input裝置的註冊
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
這裡可以看出input本身還是字元裝置,而且這裡
#define INPUT_MAJOR 13
# ls -l /dev/event0
crw-rw---- 1 0 0 13, 64 Jun 16 23:57 /dev/event0與上面顯示相對應,次裝置號為64
這裡出現了input_fops 看其定義為
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};操作函式為input_open_file,如何進行操作呢??看此函式操作如下
struct input_handler *handler = input_table[iminor(inode) >> 5];
取出當前處理的input_handler,為什麼會在得到次裝置號時>>5,現在不用著急,繼續看下面??
if (!handler || !(new_fops = fops_get(handler->fops)))
這裡面handler存在的話就要判斷後面,fops_get得到裡面的handler操作集合,為什麼要得到他??
看清楚這個是open操作函式,當然要取得fops集合了,然後才能操作handler裡面的open,
先看一下input_handler的定義,下面說明的時候會用到裡面的函式
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);const struct file_operations *fops;
int minor;
const char *name;...
};上面的函式不進行闡述,繼續返回看下面的程式碼,裡面有擁有者的改變以及錯誤的處理,都省略了,有興趣的可以自己檢視
old_fops = file->f_op;
file->f_op = new_fops;將new_fops賦值於file->f_op就是handler中的open,然後呼叫open,這裡面為什麼這樣呼叫?在下面input流程會進行闡述
err = new_fops->open(inode, file);
最終呼叫input_hander下面這個open函式去執行
input_register_handler分析
init函式分析完畢,試想應用程式如何open按鍵或者其他的input裝置?
應用程式肯定是呼叫open,然後呼叫驅動程式file->f_op->open來得到當前的資料。
首先考慮下open從那裡來?從上面分析可知,是從input_handler裡面的fops操作集裡提供的。
而fops怎麼得到的? 是由input_table[]陣列得到的,而這個陣列怎麼得到的??
我們進行搜尋,發現就兩處提供對這個陣列進行賦值操作,一個是input_register_handler,另一個是input_unregister_handler,
很明顯我們要註冊函式的,但是這個是怎麼來的,是由其他專門的處理類呼叫的,如evdev.c keyboard.c mousedev.c...
看一下evdev.c裡面有這個handler函式的呼叫
static int __init evdev_init(void)
{return input_register_handler(&evdev_handler);
}
暫且先不討論這個,先回到input_register_handler函式裡面看一下
INIT_LIST_HEAD(&handler->h_list);
這個是初始化一個專門的handler連結串列,用來儲存handler的連結串列,下面才對陣列進行賦值
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
先說一下,一般一個input_dev,可能同一時刻有多個input_handler可以處理
首先檢視此裝置號對應的位置是否有handler的存在,有的話可能有其他input裝置正在使用此handler,所以返回busy
如果沒有就將當前要處理的handler放入這個input_table[]陣列中,下面就要放入連結串列,然後尋找相匹配的裝置
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
這裡面會根據input_handler的id_table判斷能否支援這個input_dev
光註冊input_handler是不行的,還必須註冊input_dev才能匹配操作
下面看input_register_device,先看下注釋
* This function registers device with input core. The device must be
* allocated with input_allocate_device() and all it's capabilities
* set up before registering.
這樣我們就知道在初始化時,先使用input_allocate_device時分配空間,然後呼叫註冊函式建立input_dev裝置
先看一個傳入的引數input_dev中的結構體成員
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];...
void *keycode;
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);...
struct timer_list timer;...
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 device dev;
struct list_head h_list;
struct list_head node;
};
進入函式分析一下,裡面有定時器與鍵盤碼與掃描碼轉換的預設函式,不進行闡述
static atomic_t input_no = ATOMIC_INIT(0);
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1);
從這裡我們能得到剛才的input0
error = device_add(&dev->dev);
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
這裡建立了裝置,呼叫底層的kobject實現的,屬性檔案一併生成,
看清楚是dev->dev裡面的裝置,input_dev對它進行了二次封裝,這是一般做法
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);這個跟上面handler類似,也是去新增連結串列,裝置與handler的匹配,只是現在的input_dev去找到合適的input_handler
這裡面input_attach_handler如何進行匹配?
根據input_handler->id_table判斷能否支援這個input_dev,並且在匹配成功後,會呼叫input_handler->connect建立一個親密的戀愛關係
如何建立連線呢?看一下evdev_connect函式裡面如何進行連線
先看一下這evdev這個結構
struct evdev {
int exist;
int open;
int minor;
struct input_handle handle;
wait_queue_head_t wait;
struct evdev_client *grab;
struct list_head client_list;
...
struct device dev;
};
返回看程式碼裡面如何進行描述的
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
進行evdev空間的分配
dev_set_name(&evdev->dev, "event%d", minor);
設定dev的名字,就是剛才的event0
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev)
這裡對evdev進行了初始化,下面就該註冊這個evdev了
error = input_register_handle(&evdev->handle);
handle的註冊看清楚這個正好呼叫的是input.c中的input_register_handle, 後面單詞不是handler,請注意兩者的區別
error = evdev_install_chrdev(evdev);
error = device_add(&evdev->dev);
evdev裝置的註冊
下面就是應用程式在有資料時(就是按鍵被按下時)如何read? 如何喚醒相關程序進行資料的讀取呢 ?
驅動程式的框架是如何進行觸發併發送到使用者空間裡呢?
都將在input子系統分析二進行闡述!!