<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 系统即可。