Linux驅(qū)動(dòng)學(xué)習(xí)(四) input 子系統(tǒng)實(shí)驗(yàn)

Linux驅(qū)動(dòng)學(xué)習(xí)(四) input 子系統(tǒng)實(shí)驗(yàn)

前言

  • 按鍵、鼠標(biāo)、鍵盤、觸摸屏等都屬于 input 設(shè)備,內(nèi)核專門設(shè)計(jì)了 input 子系統(tǒng)的框架來管理這些輸入設(shè)備;
  • 輸入設(shè)備本質(zhì)上還是字符設(shè)備,只是在字符設(shè)備的基礎(chǔ)上又套了 input 框架;
  • 用戶只需上報(bào)輸入事件(比如按鍵值、坐標(biāo)等信息),由 input 核心層負(fù)責(zé)處理這些事件;

input 子系統(tǒng)講解

簡(jiǎn)介

  • input 子系統(tǒng)與 pinctl、gpio 子系統(tǒng)處于同一地位,都是 Linux 內(nèi)核針對(duì)某一類設(shè)備而設(shè)計(jì)的框架;顧名思義,input 子系統(tǒng)是管理輸入設(shè)備的;
  • 不同輸入設(shè)備的事件信息含義不同,例如按鍵和鍵盤表示的是按鍵信息,鼠標(biāo)和觸摸屏表示的是坐標(biāo)信息,因此在應(yīng)用層的處理就不同;
  • 我們做驅(qū)動(dòng)的,暫時(shí)不用操心應(yīng)用層的事情,只需要按照要求上報(bào)輸入設(shè)備的輸入事件即可;
  • input 子系統(tǒng)框架又分為如下3層:
    • input 驅(qū)動(dòng)層:輸入設(shè)備的具體驅(qū)動(dòng)程序,比如按鍵驅(qū)動(dòng)程序,向內(nèi)核層報(bào)告輸入內(nèi)容
    • input 核心層:承上啟下,為驅(qū)動(dòng)層提供設(shè)備的注冊(cè)和操作接口;通知事件層對(duì)輸入事件進(jìn)行
      處理;
    • input 事件處理層:主要和用戶空間進(jìn)行交互
  • 通過編寫輸入設(shè)備的驅(qū)動(dòng),給用戶空間提供可訪問的設(shè)備節(jié)點(diǎn),input 子系統(tǒng)框架結(jié)構(gòu)圖如所示:
input 子系統(tǒng)結(jié)構(gòu)圖.png
  1. 左邊(硬件輸入設(shè)備)就是最底層的具體設(shè)備,比如按鍵、USB 鍵盤/鼠標(biāo)等;
  2. 中間部分屬于 Linux 內(nèi)核空間(分為驅(qū)動(dòng)層、核心層、事件處理層);
  3. 最右邊的就是用戶空間,所有輸入設(shè)備都會(huì)對(duì)應(yīng)一個(gè)設(shè)備訪問節(jié)點(diǎn),用戶以訪問文件的方式訪問設(shè)備節(jié)點(diǎn),進(jìn)而做一些數(shù)據(jù)處理;
  4. 我們編寫驅(qū)動(dòng)只需要關(guān)注驅(qū)動(dòng)層即可(核心層和事件處理層雖然不用修改,但也要理解)

驅(qū)動(dòng)編寫流程

  • input 核心層會(huì)向內(nèi)核注冊(cè)一個(gè)字符設(shè)備;
  • input 核心層源碼: drivers/input/input.c
// input 子系統(tǒng)的所有設(shè)備主設(shè)備號(hào)都為 13, 已經(jīng)固定,不需要我們?cè)偃ピO(shè)置
#define INPUT_MAJOR     13  /* include/uapi/linux/major.h */

/**
 * @brief  定義一個(gè) input 類
 * @param  name 名稱
 * @param  devnode 設(shè)備節(jié)點(diǎn)名
 */
struct class input_class = {
    .name       = "input",
    .devnode    = input_devnode,
};
EXPORT_SYMBOL_GPL(input_class);

/**
 * @brief  input 類初始化
 * @note   
 */
static int __init input_init(void)
{
    int err;

    /* 注冊(cè) input 類 */
    err = class_register(&input_class);
    if (err) {
        pr_err("unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();
    if (err)
        goto fail1;

    /* 注冊(cè)一個(gè)字符設(shè)備,主設(shè)備號(hào)為 INPUT_MAJOR, 定義在 include/uapi/linux/major.h */
    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
                     INPUT_MAX_CHAR_DEVICES, "input");
    if (err) {
        pr_err("unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

    return 0;

 fail2: input_proc_exit();
 fail1: class_unregister(&input_class);
    return err;
}
  • 如上代碼,注冊(cè)一個(gè) input 類,這樣系統(tǒng)啟動(dòng)以后就會(huì)在 /sys/class 目錄下有一個(gè) input 子目錄;
  • 注冊(cè)一個(gè)字符設(shè)備,主設(shè)備號(hào)為 INPUT_MAJOR,固定值13,所以 input 子系統(tǒng)注冊(cè)的所有設(shè)備,其主設(shè)備號(hào)都是13,所以我們不再需要去注冊(cè)字符設(shè)備,只需注冊(cè) input_device 即可;

注冊(cè) input_dev

  • 在使用 input 子系統(tǒng)框架時(shí),我們只需要注冊(cè)一個(gè) input device,非常省事兒;
  • input_dev 定義如下,省略不關(guān)心的代碼:
struct input_dev {
    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     /* 事件類型的位圖 */
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   /* 按鍵值的位圖 */
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   /* 相對(duì)坐標(biāo)的位圖 */
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   /* 絕對(duì)坐標(biāo)的位圖 */
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   /* 雜項(xiàng)事件的位圖 */
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   /* LED 相關(guān)的位圖 */
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   /* sound 有關(guān)的位圖 */
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];     /* 壓力反饋的位圖 */
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];     /* 開關(guān)狀態(tài)的位圖 */

    unsigned int num_vals;
    unsigned int max_vals;
    struct input_value *vals;

    bool devres_managed;
};
#define to_input_dev(d) container_of(d, struct input_dev, dev)
  • 其中 evbit 表示輸入事件類型,可選的事件類型定義在 include/uapi/linux/input.h 文件
    中,事件類型如下:
/*
 * Event types
 */
#define EV_SYN          0x00        /* 同步事件 */
#define EV_KEY          0x01        /* 按鍵事件 */
#define EV_REL          0x02        /* 相對(duì)坐標(biāo)事件 */
#define EV_ABS          0x03        /* 絕對(duì)坐標(biāo)事件 */
#define EV_MSC          0x04        /* 雜項(xiàng)(其他)事件 */
#define EV_SW           0x05        /* 開關(guān)事件 */
#define EV_LED          0x11        /* LED */
#define EV_SND          0x12        /* sound(聲音) */
#define EV_REP          0x14        /* 重復(fù)事件 */
#define EV_FF           0x15        /* 壓力事件 */
#define EV_PWR          0x16        /* 電源事件 */
#define EV_FF_STATUS    0x17        /* 壓力狀態(tài)事件 */
#define EV_MAX          0x1f
#define EV_CNT          (EV_MAX+1)
  • 比如我們使用的是按鍵,就注冊(cè) EV_KEY 事件,如果使用連按功能還需要注冊(cè) EV_REP 事件;
  • evbit、keybit、relbit 等都是存放不同事件對(duì)應(yīng)的值,比如我們現(xiàn)在要使用按鍵事件,所以會(huì)用到 keybit,即按鍵事件使用的位圖;
  • Linux 內(nèi)核定義了很多按鍵值,它們定義在 include/uapi/linux/input.h ,按鍵值如下所示:
/*
 * Keys and buttons
 *
 * Most of the keys/buttons are modeled after USB HUT 1.12
 * (see http://www.usb.org/developers/hidpage).
 * Abbreviations in the comments:
 * AC - Application Control
 * AL - Application Launch Button
 * SC - System Control
 */
#define KEY_RESERVED        0
#define KEY_ESC         1
#define KEY_1           2
#define KEY_2           3
#define KEY_3           4
#define KEY_4           5
#define KEY_5           6
#define KEY_6           7
#define KEY_7           8
#define KEY_8           9
#define KEY_9           10
#define KEY_0           11
.......
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7
  • 我們可以將按鍵值設(shè)置為以上中的任意一個(gè),比如設(shè)置為 KEY_0
  • 編寫 input 設(shè)備驅(qū)動(dòng)時(shí),首先需要定義 input_dev 結(jié)構(gòu)體變量,然后分配內(nèi)存(同理,使用完后,需要釋放內(nèi)存資源),函數(shù)接口如下:
struct input_dev *input_allocate_device(void);

void input_free_device(struct input_dev *dev);
  • 申請(qǐng)好 input_dev 之后,需要對(duì)其進(jìn)行初始化,初始化內(nèi)容主要有 事件類型(evbit)和事件值(keybit)這兩項(xiàng);
  • 初始化 input_dev 之后,需要將其注冊(cè)到內(nèi)核中(同理,不使用的時(shí)候也要注銷),函數(shù)接口如下:
/**
 * @brief  注冊(cè) input_dev
 * @param  dev 指向要進(jìn)行注冊(cè)的 input_dev
 * @return 0,input_dev 注冊(cè)成功;負(fù)值,input_dev 注冊(cè)失敗
 */
int input_register_device(struct input_dev *dev);

void input_unregister_device(struct input_dev *dev);
  • 綜上所述,input_dev 注冊(cè)過程如下:
    ①、使用 input_allocate_device 函數(shù)申請(qǐng)一個(gè) input_dev。
    ②、初始化 input_dev 的事件類型以及事件值。
    ③、使用 input_register_device 函數(shù)向 Linux 系統(tǒng)注冊(cè)前面初始化好的 input_dev。
    ④、卸載input驅(qū)動(dòng)的時(shí)候需要先使用 input_unregister_device 函數(shù)注銷掉注冊(cè)的 input_dev,
    然后使用 input_free_device 函數(shù)釋放掉前面申請(qǐng)的 input_dev。input_dev 注冊(cè)過程示例代碼如下所示:
struct input_dev *inputdev; /* input 結(jié)構(gòu)體變量 */

/* 驅(qū)動(dòng)入口函數(shù) */
static int __init xxx_init(void)
{
    ......;
    inputdev = input_allocate_device(); /* 申請(qǐng) input_dev */
    inputdev->name = "test_inputdev";   /* 設(shè)置 input_dev 名字 */

    /*********第一種設(shè)置事件和事件值的方法***********/
    __set_bit(EV_KEY, inputdev->evbit); /* 設(shè)置產(chǎn)生按鍵事件 */
    __set_bit(EV_REP, inputdev->evbit); /* 重復(fù)事件 */
    __set_bit(KEY_0, inputdev->keybit); /*設(shè)置產(chǎn)生哪些按鍵值 */
    /************************************************/

    /*********第二種設(shè)置事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
    keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= T_MASK(KEY_0);
    /************************************************/

    /*********第三種設(shè)置事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
    /************************************************/

    /* 注冊(cè) input_dev */
    input_register_device(inputdev);
    ......;
    return 0;
}

static void __exit xxx_exit(void)
{
    input_unregister_device(inputdev);  /* 注銷 input_dev */
    input_free_device(inputdev);          /* 釋放input_dev */
}

上報(bào)輸入事件

  • 注冊(cè)完 input_dev 后,還不能使用,因?yàn)?input 設(shè)備是具有輸入功能的,輸入值是什么還不確定;
  • 我們需要獲取到具體的輸入值(或輸入事件),然后將輸入事件上報(bào)給內(nèi)核,比如按鍵,我們需要在按鍵中斷處理函數(shù)(或消抖定時(shí)器中斷函數(shù))中,將按鍵值上報(bào)給內(nèi)核,這樣內(nèi)核才能獲取到正確的輸入值;
  • 不同事件,其上報(bào)事件的 API 函數(shù)不同,以下是一些常用的事件上報(bào)API函數(shù):
/**
 * input_event() - report new input event
 * @dev: device that generated the event -- 產(chǎn)生事件的設(shè)備
 * @type: type of the event -- 事件類型,比如 EV_KEY
 * @code: event code -- 事件碼,也就是我們注冊(cè)的按鍵值,比如 KEY_0
 * @value: value of the event -- 事件值,比如 1 表示按鍵按下,0 表示按鍵松開
 *
 * This function should be used by drivers implementing various input
 * devices to report input events. See also input_inject_event().
 *
 * NOTE: input_event() may be safely used right after input device was
 * allocated with input_allocate_device(), even before it is registered
 * with input_register_device(), but the event will not reach any of the
 * input handlers. Such early invocation of input_event() may be used
 * to 'seed' initial state of a switch or initial position of absolute
 * axis, etc.
 */
void input_event(struct input_dev *dev,
                unsigned int type,
                unsigned int code,
                int value);
  • input_event 函數(shù)可以上報(bào)所有的事件類型和事件值;
  • 內(nèi)核也提供了其他針對(duì)具體事件的上報(bào)函數(shù),比如針對(duì)按鍵事件的上報(bào)函數(shù)(本質(zhì)上還是使用 input_event ):
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}
  • 同理,還有一些其他上報(bào)其他具體事件的函數(shù),如下:
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_report_ff_status(struct input_dev *dev, unsigned int code, int value);
void input_report_switch(struct input_dev *dev, unsigned int code, int value);
void input_mt_sync(struct input_dev *dev);
  • 上報(bào)事件完成后,還需要使用 input_sync () 函數(shù)高速內(nèi)核 input 子系統(tǒng)上報(bào)結(jié)束,其本質(zhì)上是一個(gè)同步事件,函數(shù)原型:
void input_sync(struct input_dev *dev);
  • 綜上所述,按鍵上報(bào)按鍵值得參考代碼如下:
/* 用于按鍵消抖的定時(shí)器服務(wù)函數(shù) */
void timer_function(unsigned long arg)
{
    unsigned char value;

    value = gpio_get_value(keydesc->gpio); /*  讀取 IO 值 */
    if (value == 0) /* 按下按鍵 */
    {
        /*  上報(bào)按鍵值 */
        input_report_key(inputdev, KEY_0, 1); /*  最后一個(gè)參數(shù) 1 , 按下 */
        input_sync(inputdev);                 /* 同步事件 */
    }
    else /* 按鍵松開 */
    {
        input_report_key(inputdev, KEY_0, 0); /*  最后一個(gè)參數(shù) 0 , 松開 */
        input_sync(inputdev);                 /* 同步事件 */
    }
}

input_event 結(jié)構(gòu)體

  • Linux 內(nèi)核使用 input_event 這個(gè)結(jié)構(gòu)體來表示所有的輸入事件,定義在 include/uapi/linux/input.h 文件;
  • tv_sec 和 tv_usec 這兩個(gè)成員變量都為 long 類型,也就是 32
    位,這個(gè)一定要記住,后面我們分析 event 事件上報(bào)數(shù)據(jù)的時(shí)候要用到;
typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;

struct timeval
{
    __kernel_time_t tv_sec;       /* 秒  */
    __kernel_suseconds_t tv_usec; /* 微秒 */
};


struct input_event
{
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};
  • input_evetn 這個(gè)結(jié)構(gòu)體非常重要,所有輸入設(shè)備都是按照 input_event 結(jié)構(gòu)體格式呈現(xiàn)給用戶的;用戶通過 input_event 來獲取具體的輸入事件和對(duì)應(yīng)的事件值(比如按鍵事件的按鍵值);

實(shí)驗(yàn)

待更新。。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容