使用轮询的方法检测按键会得的程序占用CPU的使用率。因此使用中断的方式编写设备驱动。
本次编写字符驱动采用linux内核版本为3.0.1,板子为OK6410
在linux系统中不同设备对应不同的中断,不同中断则通过唯一的数字标志。这些中断值称为中断请求(IRQ)线。每个IRQ线都会关联一个数值量——比如经典的PC机上,IRQ 1是键盘中断。
申请中断
在编写驱动若需要使用中断,则必须注册一个中断处理程序。使用request_irq()函数注册,该函数在<linux/interrupt.h>头文件中。利用该函数分配并且激活一条的中断线,以处理中断。
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
其中
irq:中断号。linux系统通过irq中分辨不同的中断IRQ线,在头文件中有相关的宏定义。外部中断宏定义为IRQ_EINT(x)。该宏定义位于arch/arm/mach-s3c64xx/include/mach/irqs.h中。
handler:中断处理程序。该函数的可能返回值有IRQ_NONE和IRQ_HANDLED,或者返回宏定义IRQ_RETVAL(val)。程序声明如下:
static irqreturn_irq_handler(int irq, void *dev)
flags:标志位,可以为零,也可以为多个标志的位掩码。定义在文件<linux/interrupt.h>中。可利用该标志位定义如按键中上升沿触发或者下降沿触发。
name:中断名,可随意设置
dev:dev是一个通用指针,该值具有唯一确定性,可以用于区分共享同一个中断处理程序的多个设备
释放中断处理程序
利用函数free_irq注销相应的中断处理程序,并且释放中断线。
void free_irq(unsigned int irq, void *dev)
字符驱动设备如下:
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <mach/gpio.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <plat/gpio-cfg.h>
struct class *key_class;
struct class_device *key_class_device;
volatile unsigned long *gpmcon = NULL;
volatile unsigned long *gpmdat = NULL;
struct cdev cdev;
dev_t dev;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0;
unsigned int key_val;
struct pin_desc
{
unsigned pin;
unsigned key_val;
};
struct pin_desc pins_desc[4] = {
{S3C64XX_GPN(0), 0x01},
{S3C64XX_GPN(1), 0x02},
{S3C64XX_GPN(2), 0x03},
{S3C64XX_GPN(3), 0x04},
};
static irqreturn_t button_irq(int irq, void *dev)
{
unsigned int pval;
struct pin_desc *pdesc = (struct pin_desc*)dev;
pval = gpio_get_value(pdesc -> pin); //读取相应引脚的值
if(pval)
{
key_val = 0x80 | pdesc -> key_val;
}
else
key_val = pdesc -> key_val;
ev_press = 1;
wake_up_interruptible(&button_waitq); //当中断发生时该函数用于唤醒
printk("press check\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
unsigned int key_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait);//将button_waitq放入队列中。到这里还没有进入休眠
if (ev_press)
mask |= POLLIN | POLLWRNORM;//mask是返回应用程序poll的返回值,mask一定要和应用程序中的events相对应,不然还会进行超时
return mask;
}
int key_open(struct inode *inode,struct file *file)
{
request_irq(IRQ_EINT(0), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s0",&pins_desc[0]); /* 这里的irq指的不是中断源 */
request_irq(IRQ_EINT(1), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s1",&pins_desc[1]);
request_irq(IRQ_EINT(2), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s2",&pins_desc[2]);
request_irq(IRQ_EINT(3), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s3",&pins_desc[3]);
printk("open!\n");
return 0;
}
ssize_t key_release(struct inode *inode,struct file *file)
{
free_irq(IRQ_EINT(0),&pins_desc[0]);
free_irq(IRQ_EINT(1),&pins_desc[1]);
free_irq(IRQ_EINT(2),&pins_desc[2]);
free_irq(IRQ_EINT(3),&pins_desc[3]);
return 0;
}
ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
if(count != 1)
return -EINVAL;
printk("$$$$$$$$$$key_read$$$$$$$$$\n");
wait_event_interruptible(button_waitq, ev_press);
/* 当ev_press为1且wake_up_interruptible()函数执行后read函数才能继续进行,否则堵塞在此处*/
copy_to_user(buf,&key_val,1);
ev_press = 0;
return count;
}
ssize_t key_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
char mbuf[10];
printk("write!\n");
copy_from_user(mbuf,buf,count);
switch(mbuf[0])
{
case 1:
writel(0x01, gpmdat);
break;
case 0:
writel(0x0F,gpmdat);
break;
default:
break;
}
return count;
}
struct file_operations key = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_release,
.poll = key_poll,
};
int mykey_init(void)
{
cdev_init(&cdev,&key);
alloc_chrdev_region(&dev,0,1,"int_key_chrdev");
cdev_add(&cdev,dev,1);
key_class = class_create(THIS_MODULE,"int_key");
/*if(IS_ERR(led_class))
return PTR_ERR(led_class);*/
key_class_device = device_create(key_class,NULL,dev,NULL,"int_key1");
/* if(unlikely(IS_ERR(led_class)))
return PTR_ERR(led_class); */
return 0;
}
void key_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(dev,1);
device_unregister(key_class_device);
class_destroy(key_class);
}
MODULE_LICENSE("GPL");
module_init(mykey_init);
module_exit(key_exit);
其中模块的init函数不能命名为key_init()。因为在相关文件中有宏定义,因此这里命名为mykey_init。
另外飞凌官方资料给的linux系统中似乎已经使用了按键中断线,因此在request_irq()中flag要加上IRQF_SHARED,该宏定义允许表示多个设备共用一个中断线,否则会报错。
测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
char val;
fd = open("/dev/int_key1",O_RDWR);
if(fd < 0)
{
printf("Can't open\n");
}
while(1)
{
read(fd,&val,1);
printf("key_val = 0x%x",val);
}
close(fd);
return 0;
}
Makefile
KERDIR := /home/wade/arm/cha_driver/linux-3.0.1.new
all:
make -C $(KERDIR) M=`pwd` modules
clean:
make -C $(KERDIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += int_drv.o