在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层、核心层、事件层共同组成的。其中核心层提供一些设备层与事件层公用的函数,比如说注册函数、反注册函数、事件到来的处理函数等等;事件层其实在Linux内核里面已经帮我们写好了很多有关的事件;而设备层就跟我们新添加到输入系统的具体设备相关了。这里以JZ2440开发板上的4个按键作为输入子系统的按键:它定义的功能分别为:KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT。这几个值是在includelinuxinput.h中被定义的。接下来就是编写程序:
直接贴出源程序:
#include
#include
#include
#include
#include //含有iomap函数iounmap函数
#include //含有copy_from_user函数
#include
#include //含有S3C2410_GPF0等相关的
#include
#include //含有IRQT_BOTHEDGE触发类型
#include
#include
#include //含有各种错误返回值
#include
//#include
struct pin_desc
{
char * name; //名称
unsigned int pin; //管脚定义
unsigned int irq; //中断号
unsigned int key_val; //按键值
};
static struct pin_desc pins_desc[4] = //初始化四个按键
{
{'S2',S3C2410_GPF0,IRQ_EINT0,KEY_L},
{'S3',S3C2410_GPF2,IRQ_EINT2,KEY_S},
{'S4',S3C2410_GPG3,IRQ_EINT11,KEY_ENTER},
{'S5',S3C2410_GPG11,IRQ_EINT19,KEY_LEFTSHIFT}
};
static struct pin_desc *pin_des=NULL;
static struct timer_list inputbuttons_timer;//新建一个定时器
static struct input_dev *buttons_input; //新建一个输入子系统的设备层结构
/*
*利用dev_id的值为pins_desc来判断是哪一个按键被按下或松开
*中断处理程序主要是将发生中断的按键记录下来,然后修改定时器的定时时间为10ms
*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
pin_des = (struct pin_desc *)dev_id; //取得哪个按键被按下的状态
mod_timer(&inputbuttons_timer, jiffies+HZ/100);//10ms之后调用定时器处理函数
return IRQ_HANDLED;
}
/*
*定时器的处理程序,主要根据按下的按键,通过input_event函数上传事件
*/
static void inputbuttons_timer_timeout(unsigned long a)
{
unsigned int pin_val;
if(pin_des==NULL)
return;
else
{
pin_val = s3c2410_gpio_getpin(pin_des->pin);
/*0松开,1按下*/
if(pin_val) //按键松开
{
input_event(buttons_input,EV_KEY, pin_des->key_val, 0);
//input_sync(buttons_input);//上传同步事件,似乎没什么作用
}
else
{
input_event(buttons_input,EV_KEY, pin_des->key_val, 1);
//input_sync(buttons_input);//上传同步事件
}
}
}
/*
*模块入口函数
1、分配buttons_input结构体并初始化
2、注册buttons_input结构,一旦注册会产生一个/dev/event1设备节点文件
3、初始化一个定时器
4、申请4个中断
*/
static int seven_drv_init(void)
{
unsigned char i;
int ret;
/*1、分配一个buttons_input结构体*/
buttons_input = input_allocate_device();
if (!buttons_input)
return -ENOMEM;
/*2、设置输入事件类型*/
set_bit(EV_KEY, buttons_input->evbit);
set_bit(EV_REP, buttons_input->evbit);//重复事件类型
/*3、输入事件类型的哪一种按键*/
set_bit(KEY_L, buttons_input->keybit);
set_bit(KEY_S, buttons_input->keybit);
set_bit(KEY_ENTER, buttons_input->keybit);
set_bit(KEY_LEFTSHIFT, buttons_input->keybit);
/*4、注册它*/
input_register_device(buttons_input);//注册设备驱动
/*5、硬件相关操作*/
/*增加一个定时器用于处理按键抖动*/
init_timer(&inputbuttons_timer);
inputbuttons_timer.expires = 0;
// buttons_timer->data = (unsigned long) cs;
inputbuttons_timer.function = inputbuttons_timer_timeout;
add_timer(&inputbuttons_timer);
/*申请中断*/
for(i=0;i<4;i++)
{
ret = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, (void * )&pins_desc[i]);
if(ret)
{
printk('open failed %dn',i);
return -(i+1);
}
}
return 0;
}
/*
*模块出口函数
1、反注册buttons_input
2、释放buttons_input结构所占内存
3、删除定时器
4、释放4个中断申请
*/
static void seven_drv_exit(void)
{
unsigned char i;
input_unregister_device(buttons_input);
input_free_device(buttons_input);
del_timer(&inputbuttons_timer);
for(i=0;i<4;i++)
{
free_irq(pins_desc[i].irq, (void * )&pins_desc[i]);
}
}
module_init(seven_drv_init);
module_exit(seven_drv_exit);
MODULE_LICENSE('GPL');
a、先看seven_drv_init函数,因为它负责对设备层进行初始,并且注册它,将它与事件层联系起来。看到这个函数:
1、分配一个buttons_input结构体
/*1、分配一个buttons_input结构体*/
buttons_input = input_allocate_device();
if (!buttons_input)
return -ENOMEM;
2、设置输入事件类型
set_bit(EV_KEY, buttons_input->evbit);
set_bit(EV_REP, buttons_input->evbit);//重复事件类型
事件类型位于includelinuxinput.h中
/*
* Event types
*/
#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
3、设置输入事件类型的哪一种按键
/*3、输入事件类型的哪一种按键*/
set_bit(KEY_L, buttons_input->keybit);
set_bit(KEY_S, buttons_input->keybit);
set_bit(KEY_ENTER, buttons_input->keybit);
set_bit(KEY_LEFTSHIFT, buttons_input->keybit);
按键码同样定义在includelinuxinput.h中,截取其中一小部分:
#define KEY_ENTER 28//enter的按键码
#define KEY_S 31//S的按键码
#define KEY_L 38//L的按键码
#define KEY_LEFTSHIFT 42//leftshift的按键码
4、注册它buttons_input结构体
注册的功能其实就是将当前的设备与事件层的结构进行匹配,这里会匹配到evdev_handler,它位于driversinputevdev.c,匹配后会产生/dev/event1设备节点文件。
/*4、注册它*/
input_register_device(buttons_input);//注册设备驱动
b、接着看到inputbuttons_timer_timeout函数,最终的按键的按键值是通过它上传的。
/*
*定时器的处理程序,主要根据按下的按键,通过input_event函数上传事件
*/
static void inputbuttons_timer_timeout(unsigned long a)
{
unsigned int pin_val;
if(pin_des==NULL)
return;
else
{
pin_val = s3c2410_gpio_getpin(pin_des->pin);
/*0松开,1按下*/
if(pin_val) //按键松开
{
input_event(buttons_input,EV_KEY, pin_des->key_val, 0);
//input_sync(buttons_input);//上传同步事件,似乎没什么作用
}
else
{
input_event(buttons_input,EV_KEY, pin_des->key_val, 1);
//input_sync(buttons_input);//上传同步事件
}
}
}
c、接着编写测试程序,源码如下:
#include
#include
#include
#include
#include
#include
static int fd;
int main(int argc, char **argv)
{
char* filename='/dev/event1';
int oflags,ret=0;
unsigned char key_val[16];
fd = open(filename, O_RDWR);//|O_NONBLOCK);//打开dev/firstdrv设备文件,阻塞方式打开
if (fd < 0)//小于0说明没有成功
{
printf('error, can't open %sn', filename);
return 0;
}
if(argc !=1)
{
printf('Usage : %s ',argv[0]);
return 0;
}
while(1)
{
ret = read(fd, key_val, 16);//读取的个数必须大于16字节
printf('ret = %d,code: %02d value:%dn',ret,key_val[10],key_val[12]);
}
return 0;
}
可以看到这个测试程序是以阻塞方式打开的。read函数读的个数必须大于16个字节,看到driversinputevdev.c下的evdev_read函数,最后是它是将event这个结构发送给应用程序的。
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
int retval;
if (count < evdev_event_size())
return -EINVAL;
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))//如果是非阻塞方式打开的文件,并且现在缓存中不存在数据,直接返回