1. 程式人生 > >韋東山視訊實驗之Input子系統分析之一

韋東山視訊實驗之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子系統分析二進行闡述!!