版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/83065404
当一种异常发生的时候,ARM处理器会跳转到对应该异常的固定地址去执行异常处理程序,而这个固定的地址就称为异常向量
在裸机代码中,当irq发生时,程序会跳转到0x00000018地址处去,
即ldr pc,_irq.这张异常向量表在linux内核中也是一样的。当irq发生就会调转到0x00000018地址
统一入口
entry-armv.S
.align 5
__irq_svc:
svc_entry
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler
#ifdef CONFIG_PREEMPT
str r8, [tsk, #TI_PREEMPT] @ restore preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
ldr r4, [sp, #S_PSR] @ irqs are already disabled
#ifdef CONFIG_TRACE_IRQFLAGS
tst r4, #PSR_I_BIT
bleq trace_hardirqs_on
#endif
svc_exit r4 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r5, =handle_arch_irq
mov r0, sp
ldr r5, [r5]
adr lr, BSYM(9997f)
teq r5, #0
movne pc, r5
#endif
arch_irq_handler_default
9997:
.endm
entry-macro-multi.S
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro arch_irq_handler_default
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, BSYM(1b)
bne asm_do_IRQ
得到中断号:get_irqnr_and_base
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov \base, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr \irqstat, [ \base, #INTPND ]
teq \irqstat, #0
beq 1002f
ldr \irqnr, [ \base, #INTOFFSET ]
mov \tmp, #1
tst \irqstat, \tmp, lsl \irqnr
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov \irqnr, #0 @@ start here
@@ work out which irq (if any) we got
movs \tmp, \irqstat, lsl#16
addeq \irqnr, \irqnr, #16
moveq \irqstat, \irqstat, lsr#16
tst \irqstat, #0xff
addeq \irqnr, \irqnr, #8
moveq \irqstat, \irqstat, lsr#8
tst \irqstat, #0xf
addeq \irqnr, \irqnr, #4
moveq \irqstat, \irqstat, lsr#4
tst \irqstat, #0x3
addeq \irqnr, \irqnr, #2
moveq \irqstat, \irqstat, lsr#2
tst \irqstat, #0x1
addeq \irqnr, \irqnr, #1
@@ we have the value
asm_do_IRQ
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(irq >= nr_irqs)) {
if (printk_ratelimit())
printk(KERN_WARNING "Bad IRQ%u\n", irq);
ack_bad_irq(irq);
} else {
generic_handle_irq(irq);
}
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
根据中断号irq得到irq_desc结构,并从irq_desc找到注册好的中断处理函数handle_irq
static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));
}
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
驱动:
实现中断处理函数
注册中断
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/io.h>
#define GPFCON 0x56000050
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断
//2. 清除已经发生的按键中断
//3. 打印按键值
printk("key down!\n");
return 0;
}
void key_hw_init()
{
unsigned short data;
unsigned int *gpio_config;
gpio_config = ioremap(GPFCON,4);
data = readw(gpio_config);
data &= ~0b11;
data |= 0b10;
writew(data,gpio_config);
}
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 = "tq2440key",
.fops = &key_fops,
};
static int button_init()
{
misc_register(&key_miscdev);
//按键硬件初始化
key_hw_init();
//注册中断处理程序
request_irq(IRQ_EINT0,key_int,IRQF_TRIGGER_FALLING,"tq2440key",0);
return 0;
}
static void button_exit()
{
misc_deregister(&key_miscdev);
}
module_init(button_init);
module_exit(button_exit);
那么现在又有一个问题,当我们发生中断嵌套时,比如在慢速中断中,当我们在一个串口中断处理函数中执行时,突然又发生了一个串口中断,那么发生的这个串口中断就会被忽略,又或者在一个快速中断中,当我们在处理着一个串口中断函数时,突然来了一个其他的中断,那么这时发生的这个中断也会被忽略。那么有什么办法解决这些冲突吗?
我们想,我们让前面发生的中断早点结束,也就说中断处理函数执行的时间短一点,这样就能使中断早点结束了,我们想到的办法就是,让中断分层去处理,让与硬件相关的处理我们才把他放到中断处理程序中,我们把这部分叫做上半部;其他的处理我们就把它放到其他的地方进行处理,我们把这部分叫做下半部。
下面我们来开始讲解中断下半部,如果用一个词来形容下半部的功能,就是“延迟执行”,为什么要这样说呢,后面分析过后就会深刻理解这一点了。在中断的上半部,即中断处理程序结束前,当前的中断号在所有的处理器上都会被屏蔽,如果在申请中断号时使用了IRQF_DISABLED,那么情况会更加糟糕,在中断处理程序执行时会禁止所有的本地中断。因此尽可能地缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要。因此,要将耗时较长的任务放到下半部延迟执行。因为下半部并不禁止其他中断上半部的执行(哪怕是自己的中断处理函数)。对于中断底半部的实现方式一共有三种;
- 采用软中断的方式
Linux 之软中断softirq - 采用tasklet微线程
Linux内核之tasklet - 采用工作队列
linux内核之工作队列