输入设备是典型的字符驱动,其工作原理一般是在按键、触摸等动作发生时在底层产生一个中断(或者驱动通过Timer定时查询),然后CPU通过SPI、I2C或外部存储器总线读取键值、坐标等数据,并将它们放到一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取它们的键值、坐标等数据
在这些工作中只有中断、读取键值(坐标值)是与设备相关的,而输入设备的缓冲区管理以及字符设备驱动的file_operations接口对于输入设备来说都是通用的,所以Linux内核专门做了一个INPUT子系统来处理输入事件
框架图如下:
上图就展示了INPUT子系统(也就是输入核心)在整个系统中所处的位置
系统的结构如下图:
input核心层会向Linux内核注册一个字符设备
input子系统的所有设备主设备号为13,在使用input子系统处理输入设备时就不需要去注册字符设备了,只需要向系统注册一个input_device即可
表示input设备
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)];//LED相关的位图
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];//sound有关的位图
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//压力反馈的位图
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//开关状态的位图
….
bool devres_managed;
};
evbit表示输入设备类型,可选的类型如下
#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 /* 压力状态事件 */
比如使用按键就写EV_KEY,如果要使用连按功能的话还需要注册EV_REP时间
input_dev中的那些位图都是存放不同时间对应的值的,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 KEY_MINUS 12
#define KEY_EQUAL 13
…
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7
我们编写input设备驱动的时候需要先申请一个input_dev结构体变量,使用input_allocate_device,原型如下:
struct input_dev __must_check *input_allocate_device(void);
如果要注销input设备的话需要使用input_free_device,原型如下:
void input_free_device(struct input_dev *dev);
申请好一个input_dev后需要初始化,需要初始化的内容主要为事件类(evbit)和事件值(keybit)这两种,input_dev初始化完成后就需要向Linux内核注册input_dev,需要用到 input_register_device,原型如下:
int __must_check input_register_device(struct input_dev *);
注销时使用input_unregister_device,原型如下:
void input_unregister_device(struct input_dev *);
这个是在Linux的bitmap(位数组)数据结构中实现的,用于设置某一位,使用汇编来实现,原子性的操作
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
实际上底层也是来调用__set_bit函数
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
我们需要获取到具体的输入值或者说是输入事件,然后将输入事件上报给Linux内核
比如按键,我们需要在按键中断处理函数或者消抖定时器中断函数中将按键值上报给Linux内核,这样Linux内核才能获取到正确的输入值
不同的时间其上报事件的API函数不同
此函数用于上报指定的事件以及对应的值
void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value)
dev:需要上报的input_dev
type:上报事件的类型,比如EV_KEY
code:时间码,也就是我们注册的按键值,比如KEY_0,KEY_1
value:事件值,比如1表示按键按下,0表示按键松开
input_event可以上报所有的事件类型和事件值,Linux内核提供的其他针对具体事件的上报函数其实都用到了input_event函数
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)
当我们上报事件以后还需要使用input_sync函数来告诉Linux内核input子系统上报结束,input_sync本质是一个同步事件
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time:事件发生的时间 timeval类型
type:事件类型
code:事件码
value:值
struct irqkey_dev
{
dev_t devid;
...
struct input_dev *inputdev;
};
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct irqkey_dev *dev = (struct irqkey_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
printk(KERN_EMERG "GPIO is %d\r\n",value);
if(value == 0)
{
printk(KERN_EMERG "gpio == 1 \r\n");
/*report key value*/
input_report_key(dev->inputdev, keydesc->value, 1);
input_sync(dev->inputdev);
}
else
{
printk(KERN_EMERG "gpio == 0 \r\n");
input_report_key(dev->inputdev, keydesc->value, 0);
input_sync(dev->inputdev);
}
}
static int keyio_init(void)
{
...
/*get gpio*/
...
/*request interrupt*/
...
/*create timer*/
...
/*request input_dev*/
key.inputdev = input_allocate_device();
key.inputdev->name = INPUTKEY_NAME;
#if 0
/*init input_dev,set event*/
__set_bit(EV_KEY, key.inputdev->evbit);//key event
__set_bit(EV_REP, key.inputdev->evbit);//repeat event
/*init input_dev,set keys*/
__set_bit(KEY_0, key.inputdev->keybit);
#endif
#if 0
key.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
key.inputdev->keybit[bit_WORD(KEY_0)] |= bit_MASK(KEY_0);
#endif
key.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(key.inputdev, EV_KEY, KEY_0);
/*register input device*/
ret = input_register_device(key.inputdev);
if(ret)
{
printk(KERN_EMERG "register input device failed!\n");
return ret;
}
return 0;
}
static int __init input_key_init(void)
{
printk(KERN_EMERG "input_key_init enter!\n");
keyio_init();
return 0;
}
static void __exit input_key_exit(void)
{
unsigned int i =0;
printk(KERN_EMERG "input_key_exit enter!\n");
/*delete timer*/
del_timer_sync(&key.timer);
/*release interrupt*/
for(i=0; i<KEY_NUM; i++)
{
free_irq(key.irqkeydesc[i].irqnum, &key);
}
/*release input_dev*/
input_unregister_device(key.inputdev);
input_free_device(key.inputdev);
}
static struct input_event inputevent;
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[2];
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开led驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
while(1)
{
retvalue = read(fd, &inputevent, sizeof(inputevent));
if(retvalue > 0)
{
switch(inputevent.type)
{
case EV_KEY:
if(inputevent.code < BTN_MISC)
{
printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
}
else
{
printf("button %d %s\r\n",inputevent.code, inputevent.value ? "press" : "release");
}
break;
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
}
}
else
{
printf("read error!\r\n");
}
}
return 0;
}
在自定义的dev结构体中我们添加了一个input_dev类型的成员变量
驱动部分的代码可以明显看出与之前的字符设备驱动是有明显的不同的,我们找不到之前的分配设备号、初始化cdev、注册cdev、创建class、创建注册、获取GPIO这样的流程
我们可以看到在Init函数中调用了keyio_init()函数,这个函数完成了与按键驱动相关的中断初始化、定时器初始化以及INPUT设备的创建与初始化的过程,INPUT设备代替了我们之前的设备号、cdev、class、device、file_operations相关的操作,这就是Linux的驱动分层
在keyio_init中,我们首先完成了GPIO的获取、中断的申请以及定时器的创建与初始化,接着就进行INPUT设备相关的操作
当按键事件被触发时,要完成上报操作,驱动要做如下的操作(在本例子中是在按键消抖的定时器回调函数中执行的)
在exit函数中我们还要做如下的操作
在应用程序的死循环中使用read函数来读取input事件值,当input事件值的类型为EV_KEY时进行打印