0.0上一个按键驱动使用查询方式,占用cpu为99%,根本不实用,因此使用中断方式按键驱动。
0.1驱动功能:记录按键按下次数并发往用户端。读取按键状态时,如果按键未按下则休眠进程,按键按下则进入中断服务函数,在isr中唤醒进程并将对应按键按下的次数加1.
宏定义设备名称和主设备号,定义中断描述结构体及初始化结构体参数,按键次数静态全局数组,按键状态变量(0表示未按下,1表示按下),注册等待队列。
#define DEVICE_NAME "buttons" /* 设备名称 */
#define BUTTON_MAJOR 232 /* 主设备号 */
static volatile unsigned int ev_press = 0; /* 按键状态量 */
struct button_irq_des {
unsigned int irq;
unsigned long flags;
char *name;
};
static volatile int press_cnt[4];
static struct button_irq_des button_irqs[4]=
{
{IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"},/* K1 */
{IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"},/* K2 */
{IRQ_EINT2, IRQF_TRIGGER_FALLING, "KEY3"},/* K3 */
{IRQ_EINT0, IRQF_TRIGGER_FALLING, "KEY4"},/* K4 */
};
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);/* 注册等待队列 */
定义并初始化file_operations,其中的tird_dvr_open,third_drv_read函数在测试程序中的open,read函数会用的到。
open中主要是对按键中断注册,一共四个按键中断,逐个注册,若有一个注册失败,则释放之前所有已经注册的中断。
read负责传输按键次数数组的值到用户层。
close释放所有已经注册的中断。
static struct file_operations third_drv_fops = {
.owner = THIS_MODULE,
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};
static int third_drv_open(struct inode *inode, struct file *file)
{
int i;
int err;
printk("enter open fun\n");
/* 逐个注册中断 */
for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
button_irqs[i].name, (void*)&press_cnt[i]);
if(err)
break;
}
/* 如果最后一个注册失败,释放全部已经注册的中断 */
if(err)
{
i--;
while(i>=0)
{
free_irq(button_irqs[i].irq, (void*)&press_cnt[i]);
i--;
}
return -EBUSY;
}
return 0;
}
static int third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long err;
printk("enter read fun\n");
/* 等待创建的队列,当条件为1时,唤醒进程,否则进程一直休眠 */
wait_event_interruptible(buttons_waitq, ev_press);
/* 执行到此处说明ev_press已经为1,已经执行完中断处理程序,将ev_press清零 */
ev_press = 0;
/* 复制按键状态到用户*/
printk("copy to usr\n");
err = copy_to_user(buf, (const void *)press_cnt, min(sizeof(press_cnt), size));
/** 此处是关键找了好久bug,若想增加按键按下的次数,此处不能对数组清零 **/
//memset((void *)press_cnt, 0, sizeof(press_cnt));
//printk("%d\n",err);
return (!err ? 0 : -EFAULT);
}
static int third_drv_close(struct inode * inode, struct file * file)
{
int i;
for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
free_irq(button_irqs[i].irq, (void*)&press_cnt[i]);
}
return 0;
}
在中断服务程序中对按键计数数组对应值计数,dev_id是在注册中断时传入的press_cnt数组元素的地址,因此直接强制类型转换为press_cnt所指向的类型后自加。接着唤醒休眠的进程,返回上一次进入休眠的下一步操作,即清零按键状态和向用户传输数据。
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
/* 进入中断,按键按下 */
ev_press = 1;
*(volatile int *)dev_id += 1;
/* 唤醒休眠的进程 */
wake_up_interruptible(&buttons_waitq);
printk("wake up\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
注册file_operations和卸载file_operations,在注册时由于没有使用mdev自动创建设备节点的方法(即创建class和device_class的方法),因此之后需要手动创建设备节点(mknod buttons c 232 0).
static int __init third_drv_init(void)
{
int err;
err = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &third_drv_fops); // 注册, 告诉内核
//thirddrv_class = class_create(THIS_MODULE,DEVICE_NAME);
//thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(BUTTON_MAJOR, 0), NULL,DEVICE_NAME);
if(err < 0)
{
printk(DEVICE_NAME"can't register!\n");
return err;
}
printk(DEVICE_NAME "initialized\n");
return 0;
}
static void __exit third_drv_exit(void)
{
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME); // 卸载
//class_device_unregister(thirddrv_class_dev);
//class_destroy(thirddrv_class);
}
module_init(third_drv_init);
module_exit(third_drv_exit);
MODULE_LICENSE("GPL");
在按键程序中打开设备节点,读取传入user的数据放入数组,并打印数组值。
int main(int argc, char *argv[])
{
int fd;
int i;
int ret;
int val[4];
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
return -1;
}
while(1)
{
ret = read(fd, val, sizeof(val));
if (ret < 0) {
printf("read err!\n");
continue;
}
for(i = 0; i < 4; i++)
{
if(val[i])
{
printf("K%d has been pressed %d times!\n", i+1, val[i]);
}
}
}
return 0;
}
测试信息:成功显示按键次数,并且按键未按下时cpu只占用0%
使用函数printf()时,记得加上换行,否则数据在输出缓冲区中不立即输出至终端。
isr中对按键次数操作时由于dev_id是地址,所以不能忽略间接访问符 *
测试时必须手动创建设备节点。