前面的博客中,有一篇通过按键玩中断的文章,不过那里的程序是裸机,也就是没有加系统下设计的程序,也就和在单片机中设计的程序一样比较简单。现在我们来看看按键的驱动程序在linux系统下是如何设计的。
1 混杂设备驱动模型**
1 混杂设备驱动描述
首先我们先来了解一下什么是混杂设备驱动模型。混杂设备其实是字符设备中的一种,主设备号是10,次设备号不同的设备称为混杂设备,在linux中,用struct miscdevice来描述一个混杂设备,从内核源码中复制过来结构原型为
struct miscdevice {
int minor; /*次设备号*/
const char *name; /*设备名*/
const struct file_operations *fops; /*文件操作*/
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
2 注册混杂设备驱动
linux中使用misc_register函数来注册一个混杂设备驱动
int misc_register(struct miscdevice *misc)
3 注销混杂设备驱动
misc_deregister(struct miscdevice *misc)
通过上面的概述,可以通过下面的导图来搭建一个简单的按键模型驱动key.c。
#include<linux/module.h>
#include<linux/init.h>
#include<linux/miscdevice.h>
MODULE_LICENSE("GPL");
int key_open(struct inode *node,struct file *filp)
{
return 0;
}
struct file_operations key_fops=
{
.open = key_open,
};
struct miscdevice key_miscdev = {
.minor = 200,
.name = "key",
.fops = &key_fops,
};
static int key_init()
{
misc_register(&key_miscdev);
return 0;
}
static void key_exit()
{
misc_deregister(&key_miscdev); //注销混杂设备
}
module_init(key_init);
module_exit(key_exit);
2 中断处理流程分析
按键驱动程序中,一般采用中断的方式去处理,那我们就先来看看中断处理的步骤是什么样的,在裸机程序部分当中,步骤可分为以下三步。
1 中断存在统一入口,这个入口在start.S汇编文件中,每当中断发生的时候,都会把irq发送给pc。
2 注册中断处理程序。
3 根据中断源的编号来调用中断处理程序。
在linux中,统一的入口也是存在的,在entry-arm.S文件当中的irq_svc。这里我就来简单说一下这个流程是怎么工作的
1 首先,程序通过irq_svc找到中断入口。
2 其次拿到产生中断源的编号,也就是中断号。在裸机中,直接调取中断源和中断号就直接去操作了,但是在linux系统中,引入了irq_desc数据结构,其中含有已经有注册好的处理函数。
3 最后根据取出来事先注册好的中断处理函数来运行。
上面分析的就是为了说明在驱动中如果要用中断,驱动程序该干嘛。驱动程序有两个作用,第一是实现中断处理程序,第二个是注册中断到linux系统中。
中断处理程序设计步骤为:
1 注册中断
这步是在按键中断初始化中进行的,request_irq函数用于注册中断。
int request_irq(unsigned int irq,void (*handler)(int, void*, struct pt_regs *),
unsigned long flags,
const char *devname,
void *dev_id)
参数说明:
unsigned int irq :中断号
void(handler)(int,void ):中断处理函数
unsigned long flags:与中断管理有关的各种选项
const char *devname:设备名
void *dev_id:共享中断时使用
在flags参数中, 可以选择一些与中断管理有关的选项,如:
. IRQF_DISABLED(SA_INTERRUPT) 快速中断
如果设置该位,表示是一个“快速”中断处理程序;如果没有设置该位,那么就是一个“慢速”中断处理程序。
. IRQF_SHARED(SA_SHIRQ) 共享中断该位表明该中断号是多个设备共享的。对于共享中断,dev_id不同对应不同的设备中断,这就是dev_id 的作用。
快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其他类型的中断仍可以得到服务。
2 中断处理
中断处理函数的原型是
irqreturn_t(int ib_irq, void *dev_id,)
这步是独立定义的,处理程序还包括
1检查设备是否产生了中断
2 清除中断产生的标志
3 相应的硬件操作
3 注销处理
当设备不再需要使用中断时(通常在驱动卸载时), 应当把它们注销, 使用函数:
void free_irq(unsigned int irq, void *dev_id)
这步是在按键模块卸载时进行的
3 按键驱动程序设计
在Linux中,硬件的初始化通常在两个地方进行。一个是在open函数实现,另外一个是在模块的初始化中实现。习惯性的在模块的初始化中实现硬件的初始化void key_hw_init()。按键的初始化要参考之前裸机程序,硬件原理图以及相关GPIO设置这里先贴上OK6410开发板上的按键硬件原理图部分:
从图中可以看出ok6410的按键中断是通过GPNCON控制寄存器来确定其功能的。
首先定义GPNCON的宏#define GPNCON 0x7f008830,S2按键,也就是KEYINT对应的位为GPN0,设置为10为外部中断。由于在linux中,不能直接使用物理地址,需要先将其转化为虚拟地址,使用的函数是gpio_config=ioremap(GPNCON,4),参数4意为分配给地址的虚拟地址4个字节,从原有寄存器中读取值使用readw(gpio_config)。
在request_irq()注册中,因为当按键按下去的时候触发中断,所以这里把第三个参数设置为下降沿触发中断,IRQF_TRIGGER_FALLING,那么第一个参数中断号irqnumber是怎么获得的呢?
在内核源码irqs.h中可以找到与芯片手册中一一对应的中断号,这个中断号就是注册函数中第一个参数。
从图中可以看出,外部中断0—4使用的中断源为INT_EINT0。
在OK6410的内核源码irqs.h可以看出,中断号需要加上一个偏移,这个偏移是32,当然在2410上也需要加上一个偏移,只不过2410上的偏移是16。这样的方式称为软中断。硬件产生的中断号只是一个序号,和芯片手册上是对应的序号,这个序号需要加上32才成为linux当中的中断号。不过内核代码已经帮我们加上了这个偏移,所以我们在填写参数的时候只需要把相应的中断名放进去就行了,由于这里采用的是S2按键对应的是EINT0中断,所以第一个参数就是IRQ_EINT0。
根据以上分析,可以在原有代码上增加中断处理部分,以下为增加按键中断功能后的代码。
#include<linux/module.h>
#include<linux/init.h>
#include<linux/miscdevice.h>
#include<linux/interrupt.h>
#include<linux/io.h>