一、概述
- 输入子系统驱动模型也是字符设备驱动的一种模型,是对普通字符设备驱动的封装,专门用于输入类型的设备。
- 嵌入式linux系统中的输入类型设备:按键、鼠标、键盘、触摸屏、游戏手柄、手写板等等。
二、输入子系统驱动模型的优点
- 简化普通字符设备驱动的设计步骤
- 给应用程序提供统一的标准接口。硬件平台不同,驱动程序不同,但是驱动给应用程序的接口是相同的,应用程序是相同的。
三、查看输入设备
1、输入设备的设备文件
输入设备的主设备号都是13,次设备号不同。
2、设备名称
3、查看输入设备驱动信息
4.简单读取输入设备文件的内容
[root@GEC6818 /]#cat /dev/input/event0
四、应用程序如何读取驱动提供的数据
对于输入子系统驱动模型,驱动程序提供应用程序的是一个统一格式的结构体。
struct input_event {
struct timeval time; //输入时间发生的时间戳
__u16 type; //输入设备的类型
__u16 code; //
__s32 value;
};
结构体成员说明:
(1)time:输入事件发生的时间戳。
(2)type:输入设备的类型。
#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
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
(3)code:输入事件的编码
- 如果type==EV_ABS(绝对位移),测code表示的是坐标轴方向。
/*
* Absolute axes
*/
#define ABS_X 0x00----x坐标轴
#define ABS_Y 0x01----y坐标轴
#define ABS_Z 0x02
- 如果type==EV_REL(相对位移),那么code表示的是坐标轴方向
#define REL_X 0x00----x坐标轴
#define REL_Y 0x01----y坐标轴
#define REL_Z 0x02----z坐标轴
- 如果type==EV_KEY,那么code表示的是具体哪一个按键
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_A 30
#define KEY_S 31
#define KEY_D 32
#define KEY_F 33
(4)value
如果type==EV_KEY && code == KEY_A,那么value表示的是按键A的状态:0----松开,1----按下,2---长按
如果type==EV_ABS && code == ABS_X,那么value表示的是X轴的具体坐标值
&& code == ABS_Y,那么value表示的是Y轴的具体坐标值
五、输入子系统驱动设计---以开发板上按键为例
1、定义一个输入设备(input_dev结构体变量)
struct input_dev
{
const char *name;----输入设备的名字 #cat /proc/bus/input/devices
const char *phys;
const char *uniq;
struct input_id id;----I: Bus=0018 Vendor=12fa Product=2143 Version=0100
----------------------------------------------
struct input_id {
__u16 bustype;----总线类型Bus
__u16 vendor;-----厂商编码Vendor
__u16 product;----产品编号Product
__u16 version;----版本号Version
};
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)];----如果type==EV_KEY,keybit标识的是那些按键
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)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt_slot *mt;
int mtsize;
int slot;
int trkid;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
unsigned int users_private;
bool going_away;
bool disabled;
bool sync;
struct device dev;
struct list_head h_list;
struct list_head node;
}
例如:static struct input_dev *gec6818_key;
2、给输入设备结构体指针分配内存空间,并做基本的初始化。
/** * input_allocate_device - allocate memory for new input device * * Returns prepared struct input_dev or NULL. * * NOTE: Use input_free_device() to free devices that have not been * registered; input_unregister_device() should be used for already * registered devices. */
struct input_dev *input_allocate_device(void)
3、向内核注册输入设备结构体
/**
* input_register_device - register device with input core
* @dev: device to be registered
*
* This function registers device with input core. The device must be
* allocated with input_allocate_device() and all it's capabilities
* set up before registering.
* If function fails the device must be freed with input_free_device().
* Once device has been successfully registered it can be unregistered
* with input_unregister_device(); input_free_device() should not be
* called in this case.
*/
int input_register_device(struct input_dev *dev)
4、注册申请中断
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
5、中断处理程序
irqreturn_t xxx_handler(int, void *)
{
//上报输入事件
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
}
6、驱动模块的出口函数
(1)注销输入设备 :
void input_unregister_device(struct input_dev *dev)
(2)释放输入设备内存空间
void input_free_device(struct input_dev *dev)
六、驱动设计的注意事项
1、输入子系统模型中,自带等待队列。如果没有输入事件,应用程序中的read会阻塞;如果有输入事件,应用程序会被唤醒。
2、输入子系统模型中,自带了默认的文件操作集合,不需要提供文件操作集合
3、输入设备结构体中evbit keybit的位操作,要用到内核源码提供的位接口函数,以下几个位接口函数是原子操作,在位操作时不会 被打断;而”或“和”与“不是原子操作,在与或过程中会被打断。
/*
* NMI events can occur at any time, including when interrupts have been
* disabled by *_irqsave(). So you can get NMI events occurring while a
* *_bit function is holding a spin lock. If the NMI handler also wants
* to do bit manipulation (and they do) then you can get a deadlock
* between the original caller of *_bit() and the NMI handler.
*
* by Keith Owens
*/
/**
* set_bit - Atomically set a bit in memory
* @nr: the bit to set
* @addr: the address to start counting from
*
* This function is atomic and may not be reordered. See __set_bit()
* if you do not require the atomic guarantees.
*
* Note: there are no guarantees that this function will not be reordered
* on non x86 architectures, so if you are writing portable code,
* make sure not to rely on its reordering guarantees.
*
* Note that @nr may be almost arbitrarily large; this function is not
* restricted to acting on a single-word quantity.
*/
static inline void set_bit(int nr, volatile unsigned long *addr)
/**
* clear_bit - Clears a bit in memory
* @nr: Bit to clear
* @addr: Address to start counting from
*
* clear_bit() is atomic and may not be reordered. However, it does
* not contain a memory barrier, so if it is used for locking purposes,
* you should call smp_mb__before_clear_bit() and/or smp_mb__after_clear_bit()
* in order to ensure changes are visible on other processors.
*/
static inline void clear_bit(int nr, volatile unsigned long *addr)
/**
* change_bit - Toggle a bit in memory
* @nr: Bit to change
* @addr: Address to start counting from
*
* change_bit() is atomic and may not be reordered. It may be
* reordered on other architectures than x86.
* Note that @nr may be almost arbitrarily large; this function is not
* restricted to acting on a single-word quantity.
*/
static inline void change_bit(int nr, volatile unsigned long *addr)
4、按键驱动中必须有按下和松开两种状态,如果只检测按下(下降沿触发),没有松开,程序会卡死。所以,中断注册时,上升沿和下降沿都触发中断。
七、按键输入设备驱动代码
key_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <cfg_type.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/input.h>
struct key_gpio_t{
unsigned int irq;
unsigned int gpiono;
char irqname[20];
unsigned char keyvalue;
unsigned int code;
};
static struct input_dev *key_input=NULL;
static struct key_gpio_t key_gpio[]=
{
{IRQ_GPIO_A_START+28,PAD_GPIO_A+28,"KEY2_GPIOA28",2,KEY_UP},
{IRQ_GPIO_B_START+30,PAD_GPIO_B+30,"KEY3_GPIOB30",3,KEY_DOWN},
{IRQ_GPIO_B_START+31,PAD_GPIO_B+31,"KEY4_GPIOB31",4,KEY_LEFT},
{IRQ_GPIO_B_START+9, PAD_GPIO_B+9,"KEY6_GPIOB9",6,KEY_LEFT},
};
static irqreturn_t key_handler(int irq, void * dev)
{
int value;
struct key_gpio_t keytmp=*(struct key_gpio_t *)dev;
value=gpio_get_value(keytmp.gpiono);
input_report_key(key_input, keytmp.code, !value); //上报输入事件
input_sync(key_input); //每次上报事件结束,都需要上报一次同步事件
return IRQ_HANDLED;
}
static int __init key_init(void)
{
int ret,i;
printk(KERN_INFO"key_init\n");
key_input = input_allocate_device();
if(key_input==NULL)
{
printk(KERN_INFO"allocate input device failed.\n");
ret = -ENOMEM;
goto input_allocate_device_err;
}
key_input->name = "key_input_dev";
key_input->id.bustype = 0x0001;
key_input->id.vendor= 0x0002;
key_input->id.product= 0x0003;
key_input->id.version = 0x0004;
//该输入设备能够触发按键类型的事件
set_bit(EV_KEY,key_input->evbit);
//触发的是按键类型中 的KEY_UP KEY_DOWN KEY_LEFT KEY_RIGHT
for(i=0;i<4;i++)
{
set_bit(key_gpio[i].code,key_input->keybit);
}
//注册输入设备
ret = input_register_device(key_input);
if(ret < 0)
{
printk(KERN_INFO"register input dev failed.\n");
goto input_register_device_err;
}
for(i=0;i<4;i++)
{
//按键按下和释放都要检测到
ret = request_irq(key_gpio[i].irq, key_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,key_gpio[i].irqname,(void*)&key_gpio[i]);
if(ret < 0)
{
printk(KERN_INFO"request_irq fail.\n");
goto irq_request_err;
}
}
return 0;
irq_request_err:
while(i--)
{
free_irq(key_gpio[i].irq,NULL);
}
input_unregister_device(key_input);
input_register_device_err:
input_free_device(key_input);
input_allocate_device_err:
return ret;
}
static void __exit key_exit(void)
{
int i;
printk(KERN_INFO"key_exit\n");
for(i=0;i<4;i++)
{
free_irq(key_gpio[i].irq,(void *)&key_gpio[i]);
}
input_unregister_device(key_input);
input_free_device(key_input);
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
main.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
struct input_event keyinfo;
int main()
{
int fd,ret;
fd = open("/dev/input/event4",O_RDONLY);
if(fd<0)
{
perror("open event4 error!");
return -1;
}
while(1)
{
ret=read(fd,&keyinfo,sizeof(keyinfo)); //没有按键动作,阻塞。
/*
if(ret !=1)
{
perror("read error");
continue;
}*/
if(keyinfo.type==EV_KEY)
{
switch(keyinfo.code)
{
case KEY_UP:
if(keyinfo.value == 1)
{
printf("key_up press\n");
}
else if(keyinfo.value == 0)
{
printf("key_up release\n");
}
break;
case KEY_DOWN:
if(keyinfo.value == 1)
{
printf("KEY_DOWN press\n");
}
else if(keyinfo.value == 0)
{
printf("KEY_DOWN release\n");
}
break;
case KEY_LEFT:
if(keyinfo.value == 1)
{
printf("KEY_LEFT press\n");
}
else if(keyinfo.value == 0)
{
printf("KEY_LEFT release\n");
}
break;
case KEY_RIGHT:
if(keyinfo.value == 1)
{
printf("KEY_RIGHT press\n");
}
else if(keyinfo.value == 0)
{
printf("KEY_RIGHT release\n");
}
break;
default:
printf("other\n");
break;
}
}
}
close(fd);
}