Linux驱动开发学习笔记-MISC设备驱动 & INPUT子系统

<MISC设备驱动>
即杂项驱动,采用platform+misc的方式编写驱动。是Linux驱动中很常用的方法。

1. 所有的MISC设备驱动的主设备号都是10,不同设备使用不同的从设备号。

2. MISC设备会自动创建cdev,采用MISC设备驱动可以简化字符设备驱动的编写。

3. 采用MISC设备驱动,需要注册一个miscdevice设备。
    struct miscdevice {
    int minor;                //子设备号
    const char *name;            //设备名字
    const struct file_operations *fops;    //设备操作集
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
    } ;

4. 定义一个MISC设备(miscdevice类型)后需要设置minor,name和fops。
    Linux系统已经预定义了一些MISC设备的子设备号,定义在miscdevice.h中。
    #define PSMOUSE_MINOR     1
    #define MS_BUSMOUSE_MINOR     2 /* unused */
    #define ATIXL_BUSMOUSE_MINOR     3 /* unused */
    #define ATARIMOUSE_MINOR     5 /* unused */
    #define SUN_MOUSE_MINOR     6 /* unused */
    ......
    #define MISC_DYNAMIC_MINOR     255

    在使用的时候可以从这些预定义的子设备号中挑选一个,
    当然也可以自己定义,只要这个子设备号没有被其他设备使用接口。

5. 使用misc_register函数向系统中注册MISC设备。
    int misc_register( struct miscdevice *misc )
   
    misc_register会自动完成申请设备号/初始化cdev/添加cdev/创建类/创建设备等步骤。

6. 使用misc_deregister函数注销MISC设备。
    int misc_deregister( struct miscdevice *misc )

    misc_deregister会自动完成删除cdev/注销设备号/删除设备/删除类/等步骤。


<INPUT子系统>
即管理输入的子系统,分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点。
使用input子系统的设备文件会被统一存放在/dev/input 目录中。

1. 编写input子系统驱动程序时只需要关注驱动层、核心层和事件层。
   驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
   核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
   事件层:主要和用户空间进行交互。

2. input子系统所有设备的主设备号都是13(INPUT_MAJOR)。
    在使用input子系统处理输入设备时不需要注册字符设备,只需向系统注册一个input_device即可。

3. input_dev结构体表示input设备,定义在include/linux/input.h中。
    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]];        /* 相对坐标的位图 */
    unsigned long absbit[BITS_TO_LONGS[ABS_CNT]];    /* 绝对坐标的位图 */
    unsigned long mscbit[BITS_TO_LONGS[MSC_CNT]];    /* 杂项事件的位图 */
    unsigned long ledbit[BITS_TO_LONGS[LED_CNT]];    /* 杂项事件的位图 */
    unsigned long sndbit[BITS_TO_LONGS[SND_CNT]];    /* sound有关的位图 */
    ......
    bool devres_managed;
    };

    其中,evbit表示输入事件类型,可选的事件类型定义在 include/uapi/linux/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 /* sound(声音) */
    #define     EV_REP         0x14 /* 重复事件 */
    #define     EV_FF         0x15 /* 压力事件 */
    #define     EV_PWR               0x16 /* 电源事件 */
    #define     EV_FF_STATUS    0x17 /* 压力状态事件 */

    evbit、keybit、relbit 等等都是存放不同事件对应的值。

    以keybit为例,Linux 内核定义了很多按键值,可以将实际的按键值设置为以下任意一个。    
    按键值如下:
    #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
    
4. 使用input_allocate_device 函数来申请一个 input_dev。
    struct input_dev *input_allocate_device(void)

5. 使用 input_free_device 函数来释放掉前面申请到的input_dev。
    void input_free_device(struct input_dev *dev)

6. 使用 input_register_device 函数向 Linux 内核注册 input_dev。
    int input_register_device(struct input_dev *dev)

7. 使用 input_unregister_device函数来注销掉前面注册的 input_dev。

8. 综上所述,input_dev 注册过程如下:
    ①使用 input_allocate_device 函数申请一个 input_dev。
    ②初始化 input_dev 的事件类型以及事件值。
    ③使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。
    ④卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,
       然后使用 input_free_device 函数释放掉前面申请的 input_dev。

9. 设置 input_dev的事件类型和事件值的三种方法。
    举例:
    ......
    struct input_dev *inputdev;      /* 声明input 结构体变量 */
    inputdev = input_allocate_device();      /* 申请 input_dev */
    inputdev->name = "test_inputdev";     /* 设置 input_dev 名字 */

    ①第一种设置事件和事件值的方法
       __set_bit(EV_KEY, inputdev->evbit);     /* 设置产生按键事件 */
       __set_bit(EV_REP, inputdev->evbit);     /* 重复事件 */
       __set_bit(KEY_0, inputdev->keybit);     /*设置产生哪些按键值 */

    ②第二种设置事件和事件值的方法
       inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
       inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);

    ③第三种设置事件和事件值的方法
       inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
       input_set_capability(inputdev, EV_KEY, KEY_0);

    /* 注册 input_dev */
    input_register_device(inputdev);  
    ......

10. 上报输入事件。
     向 Linux 内核注册好 input_dev 以后,需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。
     不同的事件,其上报事件的 API 函数不同。

      1). input_event 函数,此函数用于上报指定的事件以及对应的值。
    void input_event(    struct input_dev *dev,     //dev:需要上报的 input_dev。
             unsigned int type,         //type: 上报的事件类型,比如 EV_KEY。
            unsigned int code,         //code:事件码,也就是我们注册的按键值,比如 KEY_0、KEY_1 等等。
             int value    )        //value:事件值,比如 1 表示按键按下,0 表示按键松开。

      2). Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。
           如:static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)    //上报按键
       {
         input_event(dev, EV_KEY, code, !!value);
       }

      3). 上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束。
           input_sync 函数本质是上报一个同步事件。
           void input_sync(struct input_dev *dev)

      4). 综上所述,以按键的上报事件为例的参考代码如下:
          定时器中断服务函数中:
          ......
          input_report_key(inputdev, KEY_0, 1);    //按下
          input_sync(inputdev);
          ......

11.  input_event 结构体
     Linux 内核使用 input_event 这个结构体来表示所有的输入事件,定义在include/uapi/linux/input.h中。
     struct input_event {
    struct timeval time;        //事件发生时间
    __u16 type;        //事件类型,比如 EV_KEY,表示此次事件为按键事件
    __u16 code;        //事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0
    __s32 value;        //值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下
     };

     所有的输入设备最终都是按照 input_event 结构体呈现给用户。
     在用户应用程序端,可以通过定义一个 input_event类型变量来获取到具体的输入事件或相关的值。

12. Linux 自带按键驱动程序的使用
     Linux 内核自带了 KEY 驱动,如果要使用内核自带的 KEY 驱动的话需要配置 Linux 内核,不过 Linux 内核一般默认已经使能了 KEY 驱动。
     根据Linux的图形化配置界面,按照如下路径找到相应的配置选项:
    -> Device Drivers 
        -> Input device support 
             -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y]) 
                 -> Keyboards (INPUT_KEYBOARD [=y]) 
                    ->GPIO Buttons

    选中“GPIO Buttons”选项,将其编译进 Linux 内核中,选中后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIO=y”。
    Linux 内核自带的 KEY 驱动文件为drivers/input/keyboard/gpio_keys.c。
    gpio_keys.c 采用了 platform 驱动框架,在 KEY 驱动上使用了 input 子系统实现。

    要使用 Linux 内核自带的按键驱动程序很简单,只需要根据Documentation/devicetree/bindings/input/gpio-keys.txt 这个文件在设备树中添加指定的设备节点,
    然后重新编译设备树,用新编译出来的设备树文件启动 Linux 系统即可。


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值