日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux--内核Uevent事件机制 与 Input子系统【转】

發布時間:2024/4/14 linux 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux--内核Uevent事件机制 与 Input子系统【转】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

閱讀目錄

  • Uevent在kernel中的位置
  • Uevent的內部邏輯解析

轉自:http://blog.csdn.net/lxl584685501/article/details/46379453

?

[-]

  • 一Uevent機制
  • Uevent在kernel中的位置
  • Uevent的內部邏輯解析
  • 二Input子系統?
  • 從應用層的角度出發看input子系統
  • 輸入設備上報事件的處理過程
  • 通過設備節點讀取輸入事件
  • 通過設備節點寫入輸入事件
  • 總結
  • 一、Uevent機制

    1.前提摘要

    (1)Sysfs文件系統?

    ??????? 內核設備模型主要的模塊和用戶之間能看到的相關部分就是sysfs文件系統了。內核在啟動的時候會注冊sysfs文件系統,并且在啟動系統的初期。通過mount命令掛載sysfs文件系統到/sys掛載點。

    ?????? Mount?-t?sysfs?sysfs?/sys

    ?

    ?????????? 那么sysfs文件系統的作用是什么呢。概括的說有三點:

    ??????????????????? 1)、建立系統中總線、驅動、設備三者之間的橋梁

    ??????????????????? 2)、像用戶空間展示內核中各種設備的拓撲圖

    ??????????????????? 3)、提供給用戶空間對設備獲取信息和操作的接口,部分取代ioctl功能。

    ?

    (2)Kobject:Sysfs文件系統中最基本的結構就是kobject,kobject可以代表一個設備,一條總線等。在sys目錄下直觀的以一個目錄表示出來。

    ?

    (3)Uevent機制

    ???? 上面的分析其實只是對Linux設備模型做了一些基礎性的了解。也就是一個穿針引線的作用,如果要細致了解,需要仔細閱讀代碼。有了上面對于sysfs的基礎。接下來我們來比較詳細的了解一下uevent機制。

    ???????? 什么是uevent機制。這個得從熱插拔設備開始說起。最簡單的一個例子就是U盤了。當我們在計算機上插上一個U盤的時候,系統的USB?hub會檢測到U盤設備接入,并且完成設備枚舉過程(從設備上讀出相應的設備信息),并在內核中創建相應的設備結構體。但是,usb設備千奇百態,內核不可能預先將所有usb設備驅動都增加到內存中來。也就是當插入U盤設備的時候,內核中不一定存在對應這個設備的usb驅動。這個時候USB驅動也許以模塊的形式保存在硬盤上。載入驅動必然只能從用戶態來進行,那這時候應該怎么辦呢?

    ????????? 看到這里的時候,有人一定會想,人工敲入命令載入驅動,呵呵。這必然是一種方法,但是是一種很古老的方法。Linux對類似的情況設計了一種uevent的機制。當有新的設備加入的時候,將設備的信息發送消息到用戶態。而用戶態有一個udev的進程監聽這個信息。當收到信息后做一定的解析,根據解析到的結果和用戶程序的配置做一些處理,也包括加載驅動程序。

    ?

    2.具體介紹(http://www.wowotech.net/linux_kenrel/uevent.html)

    ????????? Uevent是Kobject的一部分,用于在Kobject狀態發生改變時,例如增加、移除等,通知用戶空間程序。

    用戶空間程序收到這樣的事件后,會做相應的處理。

    ????????? 該機制通常是用來支持熱拔插設備的,例如U盤插入后,USB相關的驅動軟件會動態創建用于表示該U盤的device結構(相應的也包括其中的kobject),并告知用戶空間程序,為該U盤動態的創建/dev/目錄下的設備節點,

    更進一步,可以通知其它的應用程序,將該U盤設備mount到系統中,從而動態的支持該設備。

    回到頂部

    Uevent在kernel中的位置

    ??????????????

    ?

    ??????? 由此可知,Uevent的機制是比較簡單的,設備模型中任何設備有事件需要上報時,會觸發Uevent提供的接口。Uevent模塊準備好上報事件的格式后。

    ??????? 可以通過兩個途徑把事件上報到用戶空間:一種是通過kmod模塊,直接調用用戶空間的可執行文件;

    另一種是通過netlink通信機制,將事件從內核空間傳遞給用戶空間。

    ??????? 注1:有關kmod和netlink,會在其它文章中描述,因此本文就不再詳細說明了。

    回到頂部

    Uevent的內部邏輯解析

    ??????? Uevent的代碼比較簡單,主要涉及kobject.h和kobject_uevent.c兩個文件,如下:

    • include/linux/kobject.h
    • lib/kobject_uevent.c

    ??????? 前面有提到過,在利用Kmod向用戶空間上報event事件時,會直接執行用戶空間的可執行文件。而在Linux系統,可執行文件的執行,依賴于環境變量,因此kobj_uevent_env用于組織此次事件上報時的環境變量。

    ?

    ??? ? ?? 說明:怎么指定處理uevent的用戶空間程序(簡稱uevent helper)?

    ??? 上面介紹kobject_uevent_env的內部動作時,有提到,Uevent模塊通過Kmod上報Uevent時,會通過call_usermodehelper函數,調用用戶空間的可執行文件(或者腳本,簡稱uevent helper )處理該event。而該uevent helper的路徑保存在uevent_helper數組中。

    ?????? 可以在編譯內核時,通過CONFIG_UEVENT_HELPER_PATH配置項,靜態指定uevent helper。但這種方式會為每個event fork一個進程,隨著內核支持的設備數量的增多,這種方式在系統啟動時將會是致命的(可以導致內存溢出等)。因此只有在早期的內核版本中會使用這種方式,現在內核不再推薦使用該方式。因此內核編譯時,需要把該配置項留空。

    ????? 在系統啟動后,大部分的設備已經ready,可以根據需要,重新指定一個uevent helper,以便檢測系統運行過程中的熱拔插事件。這可以通過把helper的路徑寫入到"/sys/kernel/uevent_helper”文件中實現。實際上,內核通過sysfs文件系統的形式,將uevent_helper數組開放到用戶空間,供用戶空間程序修改訪問,具體可參考"./kernel/ksysfs.c”中相應的代碼,這里不再詳細描述。


    3.實例分析(http://blog.csdn.net/sunweizhong1024/article/details/7928530headphone_event 上報事件的分析

    ???? 本文章講解插入headphone的時候,向上層上報event函數的整個過程

    #ifdef?CONFIG_I_LOVE_PBJ30

    void?headphone_event(int?state)

    {

    ???????switch_set_state(&wired_switch_dev, state);

    }

    EXPORT_SYMBOL_GPL(headphone_event);

    #endif

    ?

    headphone_event 函數會調用switch_set_state函數進行上報事件

    ?

    接下來會調用kobject_uevent_env函數進行上報事件。

    ?

    最終調用add_uevent_var()將用戶空間需要的參數添加到環境變量中去,如

    retval = add_uevent_var(env,"ACTION=%s", action_string);

    ???????if?(retval)

    ??????????????goto?exit;

    ???????retval = add_uevent_var(env,"DEVPATH=%s", devpath);

    ???????if?(retval)

    ??????????????goto?exit;

    ???????retval = add_uevent_var(env,"SUBSYSTEM=%s", subsystem);

    ???????if?(retval)

    ??????????????goto?exit;

    ?

    4.實例分析2

    ???? Uevent?是內核通知Android有狀態變化的一種方法,比如USB線插入、拔出,電池電量變化等等。其本質是內核發送(可以通過socket)一個字符串,應用層(android)接收并解釋該字符串,獲取相應信息。

    ???? (一)、Kernel側:UEVENT的發起在Kernel端,主要是通過函數

    ???????????? intkobject_uevent_env(struct kobject*kobj, enum kobject_action action,char*envp_ext[])

    該函數的主要功能是根據參數組合一個字符串并發送。一個典型的字符串如下:change@/devices/platform/msm-battery/power_supply/usb纮ACTION=change纮DEVPATH=/devices/platform/msm-battery/power_supply/usb纮SUBSYSTEM=power_supply纮POWER_SUPPLY_NAME=usb纮POWER_SUPPLY_ONLINE=0纮SEQNUM=1486纮

    上面這塊來自網上,這段內容是否有問題,待考究。

    下面看這個函數: int kobject_uevent_env(structkobject *kobj, enum kobject_action action,char *envp_ext[])

    static const char *kobject_actions[] ={

    [KOBJ_ADD] = "add",

    [KOBJ_REMOVE] = "remove",

    [KOBJ_CHANGE] = "change",

    [KOBJ_MOVE] = "move",

    [KOBJ_ONLINE] = "online",

    [KOBJ_OFFLINE] = "offline",

    };

    //以上為kobject標準的動作,調用時需要傳入相應的enum值

    ?

    ?

    ///以下是獲取subsystem信息

    if (uevent_ops&&uevent_ops->name)

    subsystem =uevent_ops->name(kset, kobj);

    else

    subsystem =kobject_name(&kset->kobj);

    if (!subsystem) {

    pr_debug("kobject: '%s' (%p):%s: unset subsystem caused the "

    "event to drop!\n",kobject_name(kobj), kobj,

    __func__);

    return 0;

    }

    ?

    ?

    //下面準備要傳遞的信息數據

    retval =?add_uevent_var(env, "ACTION=%s",action_string);

    if (retval)

    goto exit;

    retval =?add_uevent_var(env, "DEVPATH=%s",devpath);

    if (retval)

    goto exit;

    retval =?add_uevent_var(env, "SUBSYSTEM=%s",subsystem);

    if (retval)

    goto exit;

    //envp_ext[i]是傳進來的參數,為該event時攜帶的一些自定義的信息

    if (envp_ext) {

    for (i = 0; envp_ext[i]; i++){

    retval =?add_uevent_var(env, "%s", envp_ext[i]);

    if (retval)

    goto exit;

    ?

    ?

    //下面通過網絡socket將數據發送出去

    mutex_lock(&uevent_sock_mutex);

    list_for_each_entry(ue_sk,&uevent_sock_list, list) {

    struct sock *uevent_sock =ue_sk->sk;

    struct sk_buff*skb;

    size_t len;

    ?

    NETLINK_CB(skb).dst_group =1;//下面開始發送數據

    retval =netlink_broadcast_filtered(uevent_sock, skb,

    0, 1, GFP_KERNEL,

    kobj_bcast_filter,

    kobj);

    }

    ?

    (二)、Android側:

    private finalUEventObserver?mUEventObserver = newUEventObserver(){

    @Override

    public void onUEvent(UEventObserver.UEventevent) {

    if(DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());

    ?

    String state =event.get("USB_STATE");

    String accessory =event.get("ACCESSORY");

    ?

    //Added for USB Develpment debug, more logfor more debuging help

    if(DEBUG) Log.w(TAG, "mUEventObserver:onUEvent: state = " + state);

    //Added for USB Develpment debug, more logfor more debuging help

    ?

    if(state != null) {

    mHandler.updateState(state);

    }else if ("START".equals(accessory)) {

    if(DEBUG) Slog.d(TAG, "got accessory start");

    setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY,false);

    }

    }

    };

    ?

    /在類初始化時會調用下面的動作,啟動監聽動作。

    mUEventObserver.startObserving(USB_STATE_MATCH);

    ?

    //最終會調用到UEventObserver的addObserver:

    privateArrayList<Object> mObservers = newArrayList<Object>();

    public voidaddObserver(String match, UEventObserver observer) {

    synchronized(mObservers){

    mObservers.add(match);

    mObservers.add(observer);

    }

    }

    private static final String?USB_STATE_MATCH?=

    "DEVPATH=/devices/virtual/android_usb/android0";

    ?

    ?

    該函數最終會將”DEVPATH=/devices/virtual/android_usb/android0”增加到匹配序列中,當kernel發送具有該字符串的數據時,就返回匹配成功,然后調用mUEventObserver的onUEvent函數;

    UeventObserver.Java

    private static class UEventThread extendsThread {

    ?

    privateArrayList<Object> mObservers = newArrayList<Object>();

    ?

    UEventThread() {

    super("UEventObserver");

    }

    ?

    public void run() {

    native_setup();

    ?

    byte[] buffer = new byte[1024];

    int len;

    while (true) {

    len = next_event(buffer);

    if(len > 0) {

    String bufferStr = new String(buffer, 0,len); // easier to search a String

    synchronized (mObservers) {

    for (int i = 0; i <mObservers.size(); i += 2) {

    if(bufferStr.indexOf((String)mObservers.get(i)) != -1) {

    ((UEventObserver)mObservers.get(i+1))

    .onUEvent(newUEvent(bufferStr));

    }

    }

    ?

    ?

    二、Input子系統??

    refer: http://www.cnblogs.com/myblesh/articles/2367648.html

    ????????? http://blog.csdn.NET/bingqingsuimeng/article/details/7950543

    ?

    //1.? Input 子系統--概述

    ???????Android、X windows、qt等眾多應用對于linux系統中鍵盤、鼠標、觸摸屏等輸入設備的支持都通過、或越來越傾向于標準的input輸入子系統。

    ??????? 因為input子系統已經完成了字符驅動的文件操作接口,所以編寫驅動的核心工作是完成input系統留出的接口,工作量不大。但如果你想更靈活的應用它,就需要好好的分析下input子系統了。


    ?

    ?

    1. 輸入子系統由驅動層、輸入子系統核心、事件處理層三部分組成:
    ?? 一個輸入事件,如鼠標移動、鍵盤按下等通過Driver->Inputcore->Event handler->userspace的順序到達用戶控件的應用程序。
    2. input子系統仍然是字符設備驅動程序,但是代碼量減少很多,input子系統只需要完成兩個工作:初始化和事件報告。

    3. (1)上報的大致過程:設備驅動層->核心層->事件處理層->應用層

    ?? (2)具體調用的函數(以evdev為例):
    ??? ??? ?input_event()->input_handle_event() ->input_pass_event() ->handle->handler->event(handle,type, code, value)
    ?? ??? ?->evdev_event() ->evdev_pass_event() ,
    ?? ??? ?然后通過client->buffer[client->head++]= *event賦值給上層client(是struct evdev_client)

    //2.??Input 子系統之一--框架結構(初始化)
    1. 第一層
    ===============================================================================
    用戶空間訪問? <User space>? 設備節點訪問(略)
    @@@@@@xx_test.c


    2. 第二層
    ===============================================================================
    事件處理層<Event Handler>
    /*主要是和用戶空間交互。
    (Linux中在用戶空間將所有的設備都當初文件來處理,由于在一般的驅動程序中都有提供fops接口,以及在/dev下生成相應的設備文件nod,這些操作在輸入子系統中由事件處理層完成)*/
    @@@@@@evdev.c等。(以evdev_handler為例)
    (1)===>>module_init(evdev_init);? module_exit(evdev_exit);
    (2)===>>static int __init evdev_init(void)
    {
    ?? ?return input_register_handler(&evdev_handler);///
    ?? ??? ??? ??? ??? ?/*static struct input_handler evdev_handler = {
    ?? ??? ??? ??? ??? ??? ?.event?? ??? ?= evdev_event,
    ?? ??? ??? ??? ??? ??? ?.connect?? ?= evdev_connect,
    ?? ??? ??? ??? ??? ??? ?.disconnect?? ?= evdev_disconnect,
    ?? ??? ??? ??? ??? ??? ?.fops?? ??? ?= &evdev_fops,?? evdev的文件操作方法
    ?? ??? ??? ??? ??? ??? ?.minor?? ??? ?= EVDEV_MINOR_BASE,
    ?? ??? ??? ??? ??? ??? ?.name?? ??? ?= "evdev",
    ?? ??? ??? ??? ??? ??? ?.id_table?? ?= evdev_ids,*/
    };




    }

    (3)===>>int input_register_handler(struct input_handler *handler)//注冊一個新的event handler
    {
    ?? ?struct input_dev *dev;
    ?? ?

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

    ?? ?list_for_each_entry(dev, &input_dev_list, node);
    ?? ??? ?
    ??????? input_attach_handler(dev, handler);將這個新的event handler 與其兼容的input dev綁定在一起

    ?? ?
    }

    (4)===>>static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
    {
    ?? ?

    ?? ?id = input_match_device(handler, dev);//匹配規則:Input_dev和input_handler匹配后調用input_handler的connect。
    ?? ?
    ?? ?error = handler->connect(handler, dev, id);//
    ?? ?
    }
    (5)===>>創建新的evdev字符設備節點
    static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
    ?? ??? ??? ? const struct input_device_id *id)
    {
    ?? ?struct evdev *evdev;
    ?? ?int minor;
    ?? ?

    ?? ?for (minor = 0; minor < EVDEV_MINORS; minor++)//尋找未使用的minor
    ?? ??? ?if (!evdev_table[minor])
    ?? ??? ??? ?break;

    ?? ?dev_set_name(&evdev->dev, "event%d", minor);
    ?? ?evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);//創建字符設備節點event%d
    ?? ?
    ?? ?
    }

    3. 第三層
    ===============================================================================
    核心層<Input Core>
    /*承上啟下。為驅動層提供輸入設備注冊與操作接口,如:input_register_device;通知事件處理層對事件進行處理;在/Proc下產生相應的設備信息*/
    @@@@@@input.c
    (1)===>>subsys_initcall(input_init); module_exit(input_exit);
    (2)===>>static int __init input_init(void)
    {
    ?? ?
    ??????? //創建 sysfs 系統文件/proc/bus/input:devices && handlers
    ?? ?err = class_register(&input_class);///? struct class input_class = {
    ?? ????????????????????????????????????????????? .name?? ??? ?= "input",
    ?? ????????????????????????????????????????????? .devnode?? ?= input_devnode,//kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));
    ?????????????????????? ?
    ??????????????????????????? };
    ?? ?//創建proc系統文件/proc/bus/input:devices && handlers
    ?? ?err = input_proc_init();
    ?? ?if (err)
    ?? ??? ?goto fail1;


    ?????? //注冊字符設備的文件操作input_fops
    ?? ?err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
    ?? ??? ??? ??? ??? ??? ?static const struct file_operations input_fops = {
    ?? ??? ??? ??? ??? ??? ??? ?.owner = THIS_MODULE,
    ?? ??? ??? ??? ??? ??? ??? ?.open = input_open_file,///
    ?? ??? ??? ??? ??? ??? ??? ?.llseek = noop_llseek,}
    }


    (3)===>>static int input_open_file(struct inode *inode, struct file *file)
    {
    ?? ?struct input_handler *handler;
    ?? ?
    ?? ?/* No load-on-demand here? */
    ?? ?handler = input_table[iminor(inode) >> 5];
    ?? ?if (handler)
    ?? ??? ?new_fops = fops_get(handler->fops);

    ?? ?
    ?? ?file->f_op = new_fops;/根據傳入的inode節點(即:event handler),設置相應的文件操作方法

    ?? ?err = new_fops->open(inode, file);打開文件操作方法
    ?? ?
    }



    4. 第四層
    ===============================================================================
    設備驅動層<Input driver>(略)
    /*將底層的硬件輸入轉化為統一事件形式,想輸入核心(Input Core)匯報。*/
    /*實現設備驅動核心工作是:向系統報告按鍵、觸摸屏等輸入事件(event,通過input_event結構描述),不再需要關心文件操作接口。
    ? 驅動報告事件經過inputCore和Eventhandler到達用戶空間。*/
    @@@@@@mtk_tpd.c? &&? ft5206_driver.c

    ===============================================================================

    ?

    /3.??? Input 子系統之二--使用方式1:來自驅動層(硬件出發,如觸屏等)向上層匯報事件
    1. 在第四層:Input driver中需要建立一個input設備以供使用(分配、注冊、注銷input設備)
    @@@mtk_tpd.c


    /* global variable definitions */----------必加內容1
    struct tpd_device? *tpd = 0;


    static int tpd_probe(struct platform_device *pdev)?
    {

    if((tpd=(struct tpd_device*)kmalloc(sizeof(struct tpd_device), GFP_KERNEL))==NULL) return -ENOMEM;
    ??? memset(tpd, 0, sizeof(struct tpd_device));

    /* allocate input device */
    if((tpd->dev=input_allocate_device())==NULL) { kfree(tpd); return -ENOMEM; }----------必加內容2

    /*設置input設備支持的事件類型、事件碼、事件值的范圍、input_id等信息*/
    /*一個設備可以支持一個或多個事件類型。每個事件類型下面還需要設置具體的觸發事件碼。比如:EV_KEY事件,需要定義其支持哪些按鍵事件碼。*/
    /*事件類型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
    ? 而當事件類型為EV_KEY時,按鍵類型keybit包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等
    */
    ?? ?set_bit(ABS_MT_TRACKING_ID, tpd->dev->absbit);
    ?? ??? ?set_bit(ABS_MT_TOUCH_MAJOR, tpd->dev->absbit);
    ?? ??? ?set_bit(ABS_MT_TOUCH_MINOR, tpd->dev->absbit);
    ?? ??? ?set_bit(ABS_MT_POSITION_X, tpd->dev->absbit);
    ?? ??? ?set_bit(ABS_MT_POSITION_Y, tpd->dev->absbit);

    ?? ?input_set_abs_params(tpd->dev, ABS_MT_POSITION_X, 0, TPD_RES_X, 0, 0);
    ?? ?input_set_abs_params(tpd->dev, ABS_MT_POSITION_Y, 0, TPD_RES_Y, 0, 0);
    ?? ?input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MAJOR, 0, 100, 0, 0);
    ?? ?input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MINOR, 0, 100, 0, 0); ?? ?

    ?if(input_register_device(tpd->dev))----------必加內容3
    ??????? TPD_DMESG("input_register_device failed.(tpd)\n");
    ?else
    ?? ??? ??? ?tpd_register_flag = 1;

    ?

    ?

    ????????? 初始化函數定義了input設備struct tpd_device? *tpd結構體,它用于描述一個輸入子系統設備。

    ?????? 任何驅動設備如果想標明自己是輸入設備,都應該通過初始化這樣的結構體,并且調用input_allocate_device()函數進行注冊。這個函數的功能是為新添加的輸入設備分配內存,如果成功,將返回input_dev *的指針結構,因此在寫驅動的時候應該接受返回值,作為驅動層獲得了一個新的輸入設備操作的接口。通過input_allocate_device()函數,我們設備驅動現在持有的input_dev里面就被賦予了input的“形象”,但是還需要我們去充實一下“內在”,因此,設備驅動程序,還需要為自己的設備增加自己的特性,才能創造獨有的設備“形象”。

    ??????input_allocate_device這部分完成了輸入設備的初始化工作。但是這僅是初始化自己的“特點”,還需要通知輸入子系統有這樣一個新設備誕生了,這就需要調用輸入子系統的注冊函數input_register_device(tpd來完成。input_register_device()用于注冊一個輸入設備。那么注冊過程是怎樣的呢?這是一個重點,在下面的代碼中進行注釋分析:

    ?????

    1 int input_register_device(struct input_dev *dev) 2 { 3 /* 用于記錄輸入設備名稱的索引值 */ 4 static atomic_t input_no = ATOMIC_INIT(0); 5 /* 輸入事件的處理接口指針,用于和設備的事件類型進行匹配 */ 6 struct input_handler *handler; 7 const char *path; 8 int error; 9 10 /* 默認所有的輸入設備都支持EV_SYN同步事件 */ 11 set_bit(EV_SYN, dev->evbit); 12 13 /* 14 * 如果設備驅動沒有指定重復按鍵(連擊),系統默認提供以下的支持 15 * 其中init_timer為連擊產生的定時器,時間到調用input_repeat_key函數 16 * 上報,REP_DELAY用于設置重復按鍵的鍵值,REP_PERIOD設置延時時間 17 */ 18 init_timer(&dev->timer); 19 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { 20 dev->timer.data = (long) dev; 21 dev->timer.function = input_repeat_key; 22 dev->rep[REP_DELAY] = 250; 23 dev->rep[REP_PERIOD] = 33; 24 } 25 26 /* 如果設備驅動沒有設置自己的獲取鍵值的函數,系統默認 */ 27 if (!dev->getkeycode) 28 dev->getkeycode = input_default_getkeycode; 29 30 /* 如果設備驅動沒有指定按鍵重置函數,系統默認 */ 31 if (!dev->setkeycode) 32 dev->setkeycode = input_default_setkeycode; 33 34 /* 重要,把設備掛到全局的input子系統設備鏈表input_dev_list上 */ 35 list_add_tail(&dev->node, &input_dev_list); 36 37 /* 動態獲取input設備的ID號,名稱為input*,其中后面的“*”動態獲得,唯一的 */ 38 snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id), 39 "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); 40 41 /* 如果這個值沒有設置,系統把輸入設備掛入設備鏈表 */ 42 if (!dev->cdev.dev) 43 dev->cdev.dev = dev->dev.parent; 44 45 /* 在/sys目錄下創建設備目錄和文件 */ 46 error = class_device_add(&dev->cdev); 47 if (error) 48 return error; 49 50 /* 獲取并打印設備的絕對路徑名稱 */ 51 path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL); 52 printk(KERN_INFO "input: %s as %s\n", 53 dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); 54 kfree(path); 55 56 /* 核心重點,input設備在增加到input_dev_list鏈表上之后,會查找 57 * input_handler_list事件處理鏈表上的handler進行匹配,這里的匹配 58 * 方式與設備模型的device和driver匹配過程很相似,所有的input 59 * 都掛在input_dev_list上,所有類型的事件都掛在input_handler_list 60 * 上,進行“匹配相親”*/ 61 list_for_each_entry(handler, &input_handler_list, node) 62 input_attach_handler(dev, handler); 63 64 input_wakeup_procfs_readers(); 65 66 return 0; 67 } 上面的代碼主要的功能有以下幾個功能,也是設備驅動注冊為輸入設備委托內核做的事情:
    • 進一步初始化輸入設備,例如連擊事件;
    • 注冊輸入設備到input類中;
    • 把輸入設備掛到輸入設備鏈表input_dev_list中;
    • 查找并匹配輸入設備對應的事件處理層,通過input_handler_list鏈表

    ?  我們需要再分析下這個匹配的過程,但是需要注意的是下面分析的代碼是我們暫時無法分析的,因為那樣會使得情況變得更加復雜,當我們從應用層往下分析的時候一切都會明白。input_attach_handler匹配過程如下:

    ?View Code

      先來看下input_match_device()函數,看一下這個匹配的條件是什么,如何匹配的過程是怎樣的,匹配的結果會是什么?

    ?View Code

      既然證明是合適的,接下來就應該登記注冊,并公證了。還記得handler->connect(handler, dev, id)函數吧。

      當input_match_device()找到最合適的事件處理層驅動時,便執行handler->connect函數進行公證了,看下面這部分代碼(假如說找到了evdev類型的驅動,在input/evdev.c中):

    ?View Code

      通過上述代碼的執行,最終,輸入設備在input_register_handle()的關聯下與已經匹配上的handler結合

    ?

      

    以上是輸入設備驅動注冊的全過程,牽涉的代碼比較多,需要從宏觀上理順。

      縱觀整個過程:

      輸入設備驅動最終的目的就是能夠與事件處理層的事件驅動相互匹配,但是在drivers/input目錄下有evdev.c事件驅動、mousedev.c事件驅動、joydev.c事件驅動等等,我們的輸入設備產生的事件應該最終上報給誰,然后讓事件驅動再去處理呢?

      知道了這么個原因再看上面代碼就會明白,其實evdev.c、mousedev.c等根據硬件輸入設備的處理方式的不同抽象出了不同的事件處理接口幫助上層去調用,而我們寫的設備驅動程序只不過是完成了硬件寄存器中數據的讀寫,但提交給用戶的事件必須是經過事件處理層的封裝和同步才能夠完成的,事件處理層提供給用戶一個統一的界面來操作。

      由于以上的這些原因,才有了上述代碼的關聯過程,看一下整個關聯注冊的過程:

    ???????????????????????????????????????????????

    ?

    ?

    通過上圖我們可以看到input輸入設備匹配關聯的關鍵過程以及涉及到的關鍵函數和數據。

      以上主要是從input設備驅動程序的角度去看輸入子系統的注冊過程和三層之間的關聯。


    ?

    ?

    ?

    2. 在第四層:在發生輸入事件時,向子系統報告事件
    @@@ft5206_driver.c

    static? void tpd_down(int x, int y, int p,int finger_id) {
    ?? ?// input_report_abs(tpd->dev, ABS_PRESSURE, p);
    ?? ? input_report_key(tpd->dev, BTN_TOUCH, 1);
    ?? ? input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 1);
    ?? ? input_report_abs(tpd->dev, ABS_MT_POSITION_X, x);
    ?? ? input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y);
    ?? ??
    ?? ? input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, finger_id);
    ?? ??
    ?? ? //printk("D[%4d %4d %4d] ", x, y, p);
    ?? ? input_mt_sync(tpd->dev);
    }



    ??? 用于報告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)


    @@@第三層:input.c
    ??? 注意:如果你覺得麻煩,你也可以只記住1個函數(因為上述函數都是通過它實現的):input core中的如下函數:
    ??? void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);


    ??? 最終調用到如下函數:
    /*
    ?* Pass event first through all filters and then, if event has not been
    ?* filtered out, through all open handles. This function is called with
    ?* dev->event_lock held and interrupts disabled.
    ?*/
    static void input_pass_event(struct input_dev *dev,
    ?? ??? ??? ????? unsigned int type, unsigned int code, int value)
    {
    ?? ?struct input_handler *handler;
    ?? ?struct input_handle *handle;

    ?? ?rcu_read_lock();

    ?? ?handle = rcu_dereference(dev->grab);
    ?? ?if (handle)
    ?? ??? ?handle->handler->event(handle, type, code, value);
    ?? ?else {
    ?? ??? ?bool filtered = false;

    ?? ??? ?list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
    ?? ??? ??? ?if (!handle->open)
    ?? ??? ??? ??? ?continue;

    ?? ??? ??? ?handler = handle->handler;
    ?? ??? ??? ?if (!handler->filter) {
    ?? ??? ??? ??? ?if (filtered)
    ?? ??? ??? ??? ??? ?break;

    ?? ??? ??? ??? ?handler->event(handle, type, code, value);///

    ?? ??? ??? ?} else if (handler->filter(handle, type, code, value))
    ?? ??? ??? ??? ?filtered = true;
    ?? ??? ?}
    ?? ?}

    ?? ?rcu_read_unlock();
    }






    @@@第二層:evdev.c
    static struct input_handler evdev_handler = {
    ?? ?.event?? ??? ?= evdev_event,///
    ?? ?.connect?? ?= evdev_connect,
    ?? ?.disconnect?? ?= evdev_disconnect,
    ?? ?.fops?? ??? ?= &evdev_fops,
    ?? ?.minor?? ??? ?= EVDEV_MINOR_BASE,
    ?? ?.name?? ??? ?= "evdev",
    ?? ?.id_table?? ?= evdev_ids,
    };
    /*
    ?* Pass incoming event to all connected clients.
    ?*/
    static void evdev_event(struct input_handle *handle,
    ?? ??? ??? ?unsigned int type, unsigned int code, int value)
    {


    ??????? rcu_read_lock();

    ?? ?client = rcu_dereference(evdev->grab);///得到上層的client

    ?? ?if (client)
    ?? ??? ?evdev_pass_event(client, &event, time_mono, time_real);
    ?? ?else
    ?? ??? ?list_for_each_entry_rcu(client, &evdev->client_list, node);
    ?? ??? ?evdev_pass_event(client, &event, time_mono, time_real);

    ?? ?rcu_read_unlock();


    }


    static void evdev_pass_event(struct evdev_client *client,
    ?? ??? ??? ????? struct input_event *event,
    ?? ??? ??? ????? ktime_t mono, ktime_t real)
    {
    ?? ?event->time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
    ?? ??? ??? ??? ??? ?mono : real);

    ?? ?/* Interrupts are disabled, just acquire the lock. */
    ?? ?spin_lock(&client->buffer_lock);

    ?? ?client->buffer[client->head++] = *event;將事件賦值給上層client(是struct evdev_client)

    }


    /4.??? Input 子系統之二--使用方式2:來自用戶空間? 對? 設備節點的訪問(/dev/input/event%d)

    (1)關于設備節點的查詢方式

    讀取并顯示/dev/input/eventX事件

    $getevent -l

    ????????????????????????????????

    ?

    查看設備的major:/proc/devices/*? &&? 訪問設備節點:/dev/*

    ????? /proc/devices/中的設備是通過insmod加載到內核的,它可產生一個major(主設備號)供mknod作為 參數。?

    ????? 這個文件列出字符和塊設備的主設備號,以及分配到這些設備號的設備名稱。

    ?

    ?????? /dev/* 是通過mknod加上去的(使用major),格式:mknod device1 c/b major minor 如:mknod /dev/ttyS0 c 4 64,用戶通過此設備名來訪問你的驅動。

    ?????? /*在每一行都可以看到設備文件、設備編號(主、次);
    ??? ?? 對于每種硬件設備,系統內核有相應的設備驅動程序負責對它的處理。而在Unix 中,使用設備文件的方式來表示硬件設備,每種設備驅動程序都被抽象 為設備文件的形式,這樣就給應用程序一個一致的文件界面,方便應用程序和操作系統之間的通信。?
    ?????? 習慣上,所有的設備文件 都放置在/dev?目錄下*/

    ?

    查看input設備節點(1):/dev/input/*

    ( /dev/input目錄下的事件都是在驅動中調用input_register_device(struct input_dev *dev)產生的。

    ?? 每個event將上報指定的事件,如G-Sensor、觸摸屏、Mouse、按鍵等。)

    查看input設備節點(2):與/dev/input/*的event對應的相關設備信息:/proc/bus/input/devices



    ?

    (2)下面將從應用層的角度分析事件的接受過程和處理過程以及三層之間是如何配合處理輸入事件的。

    從應用層的角度出發看input子系統

    ??????? 以上部分已經借助input子系統把input設備驅動層與事件驅動層進行了關聯,以s3c2440_ts.c(輸入設備層驅動)和evdev.c(事件處理層驅動)為例,來分析這一過程。

    ??????? 由于s3c2440_ts.c中上報的事件類型為按鍵、絕對值坐標,而evdev事件驅動程序是全匹配的,因此早在s3c2440_ts.c注冊的過程中,就會創建設備節點/dev/input/event0(假設內核中沒有其他的event類型的輸入設備,這里就是event0)

      我們知道,應用層使用設備的第一步,是open(“/dev/event0”),因此這里event0的主設備號成為關鍵,因為主設備號將表明你是什么設備,我們ls -l查看/dev/event0發現:

    crw-r-----1 root root 13, 64 2012-07-26 14:32 /dev/input/event0

      由此可見主設備是13,輸入命令cat? /proc/devices查看主設備為13的是input設備,因此可以確定當我們執行open函數打開event0設備的時候,會調用input設備的open驅動函數,這個函數在input.c中,為了說明這一問題,需要從input驅動注冊過程開始,還是input.c文件:

    ?View Code

    ?

      可以看到,輸入設備初始化的過程首先建立了input類,初始化input在proc下的節點,然后注冊input設備,設備名稱為input,操作接口是input_fops,主設備號是INPUT_MAJOR=13。

      由以上可知,只要是主設備號為13的設備驅動程序,都是用input_fops接口,即當event0設備使用open函數打開時,會調用到input_fops接口中的open驅動函數,這個結構體的初始化為:



      可以看到,只實現了一個open功能字段,再看input_open_file的實現:

      以上代碼的功能為找到對應事件驅動層的fops,即進行fops的接口轉換,指向對應設備的事件處理接口。

      其中input_table[iminor(inode)]>>5的input_table是一個全局的input_handler類型的數組,iminor(inode)取得次設備號,并且右移5位索引input_table表中對應的位置,為什么這樣做呢?這是因為這個表格中填寫的就是事件處理的指針,待會分析。

      繼續查看下面的代碼。if中將判斷是否為空并且事件處理層中的fops有沒有初始化,如果沒有就不能進行接口轉換,報出設備不存在的錯誤,如果設備存在則把input設備的f_op驅動接口指向input_table表中存在的接口,并調用其open函數。

      那么這個input_table里面到底存放了什么呢?我們還是拿觸摸屏驅動來講解。由于觸摸屏驅動已經完成了和evdev.c事件處理層的匹配,且次設備號為64,設備名稱為/dev/event0,這是我們通過分析驅動注冊中獲得的內容,既然input核心設備注冊了,s3c2440觸摸屏驅動也注冊了,那會不會evdev設備也會注冊了呢?答案是肯定的,要想知道input_table里面放了什么,必須要去查看evdev設備的注冊過程,打開input/evdev.c查看它的注冊過程:

    ?View Code

      由以上的內容可以知道evdev_handler也被作為一個設備來操作,但是它屬于input handler事件處理設備,然而我們在evdev_handler結構體的.fops字段又發現它的驅動接口為字符設備類型,在input中,如果input_table匹配到了evdev_handler,將會把file->f_op=&evdev_fops,那么如果使用read、write等函數操作,將會調用到evdev_fops中的read、write。

      為了進一步查看input_table表中的內容是如何填充的,還需要查看這個注冊的過程:

    ?View Code

    ?

      當然這個注冊過程并不是只有這么一句話,看到這條語句,相信應該知道什么意思了。

      在input的open函數執行之前,即我們的open代碼打開之前,input_table中的字段已經被事件處理層填充了。

      由于evdev的次設備號在初始化的時候就設置成了64,因此這里相當于:

    ?View Code

      回到input_open_file函數查看new_fops->open(inode, file)便知道了調用的是:

    ?View Code

      

      在分析open函數之前,解釋一下為什么要右移5位?

      這說明一個問題,次設備號的低5位被忽略,這說明evdev的最大支持的輸入設備驅動個數為2^5次方等于32個,你可能會看到你的/dev目錄下面有event0、event1、event2等設備,他們的次設備號分別為64、65、66等等。但最大是64+32-1,因此input_table為這些輸入設備增加的一個統一接口,通過上層打開設備時,只要次設備號在64+32-1之間的設備都會重新定位到evdev_handler中,即event*設備打開后執行的底層函數將被重新定義到evdev_handler中。

      相信上面的問題已經描述清楚,如果還是不明白,最起碼應該知道的是,input設備中的open函數只是一個接口,通過次設備號才找到了真正的事件處理接口。接下來要看新的open接口的實現了,evdev_handler-> fops->open實現如下:

    ?View Code

      上面截取了片段,并沒有執行到open函數,open進行自減操作,表示沒有調用過open,這個值主要是為了close中判斷open為0時釋放資源使用。

      不僅如此,我們在觸摸屏驅動中也沒有定義read、write,那當觸摸屏上報事件時,是如何處理的呢?

      我們需要先到觸摸屏驅動程序中找到上報事件的函數再做進一步分析。

    ?


    ?

    輸入設備上報事件的處理過程

    ?

      觸摸屏驅動程序上報事件的函數為:

    ?View Code

    ?

    ?

      然而他們其實是input_event函數的封裝,調用的都是input_event函數,這一函數在input.c中實現如下:

    ?View Code

      代碼被做了精簡,其中就是在匹配上報的事件,并根據事件的類型調用驅動程序中相應的函數來完成,但是由于我們并沒有定義過這些函數,因此執行最后的handle_handler_event函數,由事件處理層evdev_event函數來完成事件的保存工作,具體過程如下:

    ?View Code

      這里列舉了關鍵代碼,即上報的事件被保存到了client_buffer中,其中client_buffer是一個循環緩沖區,client->head表示當前數據的位置,因此每次都寫到client->head的位置,而讀數據時需要到client_tail中讀取。因為在open的時候,client已經被鏈入到了evdev->client_list中,因此通過可以通過list_for_each_entry重evdev->client_list中找到對應的client。

      事件的上報都會把數據保存到client->buffer中,以便上層通過read和write進行讀去和寫入。

    ?


    ?

    通過設備節點讀取輸入事件

    ??????? 事件的上報都會把數據保存到client->buffer中,以便上層通過read和write進行讀去和寫入。?

      還是以觸摸屏驅動程序和evdev事件處理層驅動來分析:

    ?View Code

    ?

      這里如果沒有數據,進程會睡眠,那由誰來喚醒呢?細心的話可以發現,當設備驅動層調用input_event上報事件調用相應的event函數進行事件寫入時,是會喚醒阻塞等待的進程的。

    ?


    ?

    通過設備節點寫入輸入事件

    ?????? 事件的上報都會把數據保存到client->buffer中,以便上層通過read和write進行讀去和寫入。

      寫入過程:

    ?View Code

      上述代碼中的event是input_event數組,包含了事件的類型、鍵值,通過input_inject_event把數據寫入循環數組client->buffer中,input_inject_event調用的是input_event函數。

    ?


    ?

    總結

    ?

      對input子系統的整個過程做了分析,并從兩個角度進行考慮.

      對于寫輸入設備驅動程序的來說,需要掌握的是設備應該上報事件的類型,這樣才能匹配到對應的事件層驅動幫助你保存對應的數據.

      而對于設備上層開發者來說,應該先使用cat /proc/bus/input/devices查看你操作的設備類型和處理接口,以幫助你更好的對設備操作。
















    本文轉自張昺華-sky博客園博客,原文鏈接:http://www.cnblogs.com/sky-heaven/p/6394267.html,如需轉載請自行聯系原作者

    總結

    以上是生活随笔為你收集整理的Linux--内核Uevent事件机制 与 Input子系统【转】的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。