要想用中断方式编写应用程序,首先需要理解中断的流程。之前我们分析过中断流程的,现在再来分析一边,以便加深印象。
内核在start_kernel函数中调用trap_init、init_IRQ两个函数来设置异常的处理函数,首先我们想来看看trap_init函数,部分代码如下:
void __init trap_init(void)
{
.......................................................
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
.......................................................
}
分析:ARM构架CPU的异常向量基址可以是0x00000000,也可以是0xffff0000,linux内核使用后者。而trap_init函数就是用于将异常向量拷贝到0xffff0000处。上面的代码里,vectors为0xffff0000,__vectors_start和__vector_end之间存放的就是异常向量,他们被拷贝到地址
0xffff0000处,
但那只是一些简单的跳转指令,更加复杂的指令存放在__stubs_start和__stubs_end之间,他们被拷贝到
0xffff0000+200处。
下面我们把目光转到
__vectors_start处(arch/arm/kernel文件里),看一下代码:
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
其中:.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
分析:当相应的某个异常发生的时候,他会根据上面对应的异常向量跳转指令跳转到相应的位置进行处理。以b vector_irq + stubs_offset为例,当发生中断时,会执行它,然后跳转到vector_irq处(sut
bs_offset用于从定位跳转的位置,暂时忽略也没有关系
),可是我们搜遍代码页没有发现vector_irq这个标号。不过我们发现了如下代码:
vector_stub irq, IRQ_MODE, 4
经分析我们发现这是一条宏定义,相关代码如下:
.macro
vector_stub, name, mode, correction=0 @
vector_stub irq, IRQ_MODE, 4
.align
5
vector_\name: @
vector_irq
.if \correction
sub
lr, lr, #\correction
.endif
stmia sp, {r0, lr} @
save r0, lr
mrs
lr, spsr
str lr, [sp, #8] @
save spsr
mrs
r0, cpsr
eor
r0, r0, #(\mode ^ SVC_MODE)
msr
spsr_cxsf, r0
and
lr, lr, #0x0f
mov
r0, sp
ldr
lr, [pc, lr, lsl #2]
movs pc, lr @
branch to handler in SVC mode
.endm
我们进行一下置换就得到了:
vector_irq这个标号,其中最后一段代码是跳转指令,用于跳转到具体的中断入口。我们可以看看这个标号下面的内容:
vector_irq:
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
我们以.long __irq_usr为例进一步分析:
我们找到__irq_usr 这个标号,看一下其下面的代码:
__irq_usr:
usr_entry @
保存寄存器
................................................................................
irq_handler@
这也是一个宏,它会最终调用函数asm_do_IRQ,这是个c函数
...............................................................................
接下来是c语言了,我们列出主要框架:
asm_do_IRQ
struct irq_desc *desc = irq_desc + irq;
//以中断号为下标得到中断描述结构体数组中的一项
desc_handle_irq(irq, desc);
//进入中断处理
desc->handle_irq(irq, desc);
//要想知道这个函数是怎么回事,我们可以反推回去,不过为了便于分析,我们采取正叙的方法
void __init s3c24xx_init_irq(void)//初始化中断
set_irq_chip(irqno, &s3c_irqext_chip);
desc = irq_desc + irq;
desc->chip = chip;
//设置一些底层硬件操作函数
set_irq_handler(irqno, handle_edge_irq);
__set_irq_handler(irq, handle, 0, NULL);
desc = irq_desc + irq;
desc->handle_irq = handle;
到这一步我们将desc->handle_irq=handle_edge_irq,在结合上面的desc->handle_irq(irq, desc),我们知道上面就是执行handle_edge_irq这条指令,那么handle_edge_irq有做了什么呢?我们接着看代码,搭建框架:
handle_edge_irq
handle_IRQ_event(irq, action);
do {
ret = action->handler(irq, action->dev_id);
//这就是中断处理函数
action = action->next;
//这是中断链表
} while (action);
由此我们看出发生中断后会跳转到入口函数
asm_do_IRQ,之后会根据中断号找到中断处理函数来执行。关于上面提到的三个结构体,我们还要在来分析一下:
struct irq_desc {
irq_flow_handler_t handle_irq;
//当前中断的处理入口函数,我们会将中断处理函数赋给它
struct irq_chip *chip;
//底层的硬件访问
.................................
struct irqaction
*action;
//中断处理函数列表
unsigned int status;
//中断状态
................................
const char
*name;
//中断名称
}
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);
//启动中断,默认是enable
void (*shutdown)(unsigned int irq);
//关闭中断,默认是disable
void (*enable)(unsigned int irq);
//使能中断,默认是unmask
void (*disable)(unsigned int irq);/
/禁止中断,默认是mask
void (*ack)(unsigned int irq);
//响应中断,通常是清除当前中断使得可以接收下一个中断
void (*mask)(unsigned int irq);
//屏蔽中断源
void (*mask_ack)(unsigned int irq);
//屏蔽和响应中断
void (*unmask)(unsigned int irq);
//开启中断
..................................................
};
struct irqaction {
irq_handler_t handler;
//用户注册的中断处理函数
unsigned long flags;
//中断标志
cpumask_t mask;
//用于SMP(对称多处理器系统)
const char *name;
//用户注册的中断名称
void *dev_id;
//用户上面传过来的handler参数,还可以用来区分共享中断
struct irqaction *next;
int irq;
//中断号
struct proc_dir_entry *dir;
};
通过上面的分析我们知道了中断发生后最终调用到desc->action->handler,于是我们就知道注册中断的话必须根据中断号将中断处理函数赋给
desc->action->handler,那么到底是不是呢?我们来查看代码:
以一个中断注册函数为例:
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
action->handler = handler;
//中断处理函数放在这里了
retval = setup_irq(irq, action);
//设置中断
struct irq_desc *desc = irq_desc + irq;
//以中断号为下标构造中断描述结构体数组中的一项,发生中断的时候我们会根据中断号取出相应 //的结构体
到此为止,中断的大体流程我们就分析明白了,下面我们就以中断方式来编写按键驱动,请看代码:
驱动程序代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "keys"
volatile unsigned long *gpfcon=NULL;
volatile unsigned long *gpfdat=NULL;
volatile unsigned long *gpgcon=NULL;
volatile unsigned long *gpgdat=NULL;
static struct class *keys_class;
static struct class_device *keys_class_devs;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
key_val = 0x80 | pindesc->key_val;
}
else
{
key_val = pindesc->key_val;
}
ev_press=1;
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int s3c24xx_keys_open(struct inode *inode, struct file *file)
{
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2",&pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3",&pins_desc[1]);
request_irq(IRQ_EINT11,buttons_irq, IRQT_BOTHEDGE, "S4",&pins_desc[2]);
request_irq(IRQ_EINT19,buttons_irq, IRQT_BOTHEDGE, "S5",&pins_desc[3]);
return 0;
}
static int s3c24xx_keys_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
wait_event_interruptible(button_waitq, ev_press);
copy_to_user(buff, &key_val, 1);
ev_press=0;
return 1;
}
int s3c24xx_keys_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11,&pins_desc[2]);
free_irq(IRQ_EINT19,&pins_desc[3]);
return 0;
}
static struct file_operations s3c24xx_keys_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_keys_open,
.read = s3c24xx_keys_read,
.release = s3c24xx_keys_close,
};
int major;
static int __init s3c24xx_keys_init(void)
{
major=register_chrdev(0, DEVICE_NAME, &s3c24xx_keys_fops);//auto distribute major
keys_class = class_create(THIS_MODULE, "keys_class");
keys_class_devs = class_device_create(keys_class, NULL, MKDEV(major, 0), NULL, "keys");
gpfcon=(volatile unsigned long *)ioremap(0x56000050,16);
gpfdat=gpfcon+1;
gpgcon=(volatile unsigned long *)ioremap(0x56000060,16);
gpgdat=gpgcon+1;
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit s3c24xx_keys_exit(void)
{
unregister_chrdev(major, DEVICE_NAME);
class_device_unregister(keys_class_devs);
class_destroy(keys_class);
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(s3c24xx_keys_init);
module_exit(s3c24xx_keys_exit);
MODULE_LICENSE("GPL");
测试程序代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
fd = open("/dev/keys", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
while (1)
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
return 0;
}
关于这个程序我们简单讲解一下它的功能:当没有按键按下的时候,会进入休眠,当有按键按下的时候,会进入中断服务程序,在中断服务程序里把进程唤醒。这样的话就不会老是查询,以致cpu占用过多。
有几个小知识点要讲解一下:
(1) DECLARE_WAIT_QUEUE_HEAD(button_waitq),用于定义一个等待队列
(2)wait_event_interruptible(button_waitq, ev_press):当ev_press为0时将当前进程加入等待队列button_waitq中,进入休眠,否则不休眠
(3)wake_up_interruptible(&button_waitq):唤醒等待队列button_waitq中的成员
(4)
s3c2410_gpio_getpin(pindesc->pin):获取引脚状态
(5)命令行:./keytest & :表示在后台运行keytest程序
(6)命令行:kill -9 xxx :表示强制杀死进程号为xxx的进程