1 中断流程
1.1 基本流程
中断的基本流程是:发生中断,跳转到异常向量入口,执行中断函数,然后返回。
ARM架构CPU的异常向量基地址可以是0x0或0xFFFF0000,linux使用后者。ARMv4及以上版本,ARM中断向量表的地址由CP15协处理器c1寄存器中V位(bit[13])控制。
arch/cam/kernel/traps.c中通过early_trap_init()设置异常处理相量
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE; //0xFFFF0000
....
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
....
}
将 __vectors_start~__vectors_end之间的异常向量复制到0xffff0000处。在arch/arm/kernel/entry_armv.S中
__vectors_start:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset
发生异常后,查找异常相量,跳转到异常处理程序。以vector_irq为例,
vector_stub是一个标号,将上面进行展开后得到
.macro vector_stub, name, mode, correction=0 //定义vector_stub有3个参数
.align 5
vector_stub irq, IRQ_MODE, 4 //这三个参数值代入 vector_stub中
vector_ irq: //定义 vector_ irq
/*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */
sub lr, lr, #4
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@保存r0和lr和spsr
stmia sp, {r0, lr} //存入sp栈里
mrs lr, spsr //读出spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@ 进入管理模式
mrs r0, cpsr //读出cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f //lr等于进入模式之前的spsr,&0X0F就等于模式位
mov r0, sp
ldr lr, [pc, lr, lsl #2] //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc
movs pc, lr //跳转到下面某处,且目标寄存器是pc,指令S结尾,最后会恢复cpsr.
.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
根据中断前不同的模式,跳转到相应的处理函数,以用户模式下irq异常为例
__irq_usr --->
irq_handler --->
asm_do_IRQ ---> /arch/arm/kernel/irq.c中
generic_handle_irq
asm_do_IRQ是中断处理的C入口函数。
#define irq_to_desc(irq) (&irq_desc[irq]) //从irq中断号,找到哪个中断
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc); //执行handle_irq
}
static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));
}
从irq_desc数组中找到了中断处理结构体,并调用handle_irq处理。
从哪里设置了irq_desc:kernel/irq/chip.c中__set_irq_handler设置handle_irq,__set_irq_handler又被set_irq_handler调用。
看一下irq_desc数据结构
struct irq_desc {
unsigned int irq;
irq_flow_handler_t handle_irq; //中断函数
struct irq_chip *chip; //底层访问
struct irqaction *action; /* IRQ action list */
...
} ____cacheline_internodealigned_in_smp;
中断(例如共享中断)可有多个处理函数,当中断产生后,会依次调用action链表中函数进行处理。

从上面分析看出,中断基本流程:中断发生–>查找异常向量–>asm_do_IRQ–>desc.hand_irq–>.chip硬件相关处理,.action处理函数。
参考:ARM Linux中断源码分析(2)——中断处理流程
1.2 外部中断流程
以外部中断0为例,看下基本流程。
在arch/arm/plat-s3c24xx中,s3c24xx_init_irq()通过set_irq_handler设置中断处理函数,其中
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) { //IRQ_EINT0=16
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
当中断发生,从irq_desc[16]数组中得到handle_irq,即handle_edge_irq。
void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
...
desc->chip->ack(irq);//用来清除中断
...
action_ret = handle_IRQ_event(irq, action);//真正的处理函数
...
}
handle_edge_irq–>handle_IRQ_event,从action链表中,处理中断函数。
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
...
do {
...
ret = action->handler(irq, action->dev_id);
...
action = action->next;
} while (action);
...
}
2 程序
s3c24xx_buttons.c
//在 linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach 目录下
#include<mach/regs-gpio.h> // 和GPIO相关的宏定义
#include<mach/hardware.h> //S3C2410_gpio_cfgpin
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
//在 linux-2.6.32.2/arch/arm/include/asm 目录下
#include<asm/irq.h>
#include<asm/uaccess.h>
#include<asm/atomic.h>
#include<asm/unistd.h>
#define DEVICE_NAME "buttons_test" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define BUTTON_MAJOR 232 /* 主设备号 */
struct button_irq_desc {
int irq;
unsigned long flags;
char *name;
};
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT8, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
{IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
{IRQ_EINT13, IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
{IRQ_EINT14, IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
};
static volatile int press_cnt [] = {0, 0, 0, 0};
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
volatile int *press_cnt = (volatile int *)dev_id;
printk(DEVICE_NAME " press_cnt:%d\n",press_cnt);
*press_cnt = *press_cnt + 1; /* 按键计数加1 */
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
int i;
int err;
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--;
for (; i >= 0; i--)
free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
return -EBUSY;
}
return 0;
}
static int s3c24xx_buttons_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;
}
static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
unsigned long err;
wait_event_interruptible(button_waitq, ev_press);
ev_press = 0;
err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
memset((void *)press_cnt, 0, sizeof(press_cnt));
return err ? -EFAULT : 0;
}
static struct file_operations s3c24xx_buttons_fops = {
.owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
};
static int __init s3c24xx_buttons_init(void)
{
int ret;
ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit s3c24xx_buttons_exit(void)
{
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);
MODULE_LICENSE("GPL"); // 遵循的协议
button_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int i;
int ret;
int fd;
int press_cnt[4];
fd = open("/dev/buttons_test", 0); // 打开设备
if (fd < 0) {
printf("Can't open /dev/buttons\n");
return -1;
}
while (1) {
// 读出按键被按下的次数
ret = read(fd, press_cnt, sizeof(press_cnt));
if (ret < 0) {
printf("read err!\n");
continue;
}
for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {
// 如果被按下的次数不为0,打印出来
if (press_cnt[i])
printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]);
}
}
close(fd);
return 0;
}
测试时要先卸载内核中原有按键驱动。