分类:
2012-11-08 09:32:13
原文地址:基于ARM Linux中断、异常的处理分析 作者:BENNYSNAKE
本文是基于ARM S3C2410X系统的Linux 2.6中断、异常和系统调用的处理分析。主要有以下几个部分:
1. ARM的硬件中断机制
2. Linux 2.6对ARM中断向量表的初始化
3. Linux 2.6对ARM中断、异常的处理(从汇编-->C语言函数;asm_do_IRQ)
一、 ARM的硬件中断机制 1、中断的基本概念在嵌入式系统中外部设备的功能实现主要是依靠中断机制来实现的,即将设备功能程序的实现以中断服务子程序的形式进行组织。
中断(interrupt)通常被定义为一个事件,该事件改变处理器执行的指令顺序。这样的事件与CPU芯片外部硬件电路产生的电信号相对应。
中断的产生 每个能够发出中断请求的硬件设备控制器都有一条称为IRQ(Interrupt ReQuest)的输出线。所有的IRQ线都与一个中断控制器的输入引脚相连,中断控制器与CPU的INTR引脚相连。
设备 |
设备 控制器 |
中断 控制器 |
IRQ |
CPU |
INTR
|
中断向量 每个中断和异常由0~255之间的一个数(8位)来标识,Intel称其为中断向量。
中断描述符表(Interrupt Descriptor Table ,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址。内核在允许中断发生前,必须适当地初始化IDT。表中的每一项对应一个中断或异常向量,每个向量由8个字节组成。因此,最多需要256*8=2048字节来存放IDT。CPU的idtr寄存器指向IDT表的物理基地址。
2、中断和异常的硬件处理在内核被Init进程初始化后,CPU运行在保护模式下。当执行了一条指令后,cs和eip这对寄存器包含了下一条将要执行的指令的逻辑地址。在执行这条指令之前,CPU控制单元会检查在运行前一条指令时是否发生了一个中断或者异常。如果发生了一个中断或异常,那么CPU控制单元执行下列操作:
(1) 确定与中断或者异常关联的向量i(0~255)。
(2) 读由idtr寄存器指向的IDT表中的第i项。
(3) 从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符,这个描述符指定中断或异常处理程序所在段的基地址。
(4) 确定中断是由授权的发生源发出的。
中断:中断处理程序的特权不能低于引起中断的程序的特权(当前特权级CPL—对应CS寄存器中的低两位 其值应该小于段描述符—对应GDT表项中的描述符特权级DPL,特权级高于DPL,即当前代码是能够访问相应的段的,产生一个“”异常);
编程异常:还需进一步比较CPL与对应IDT表项中的门描述符的DPL。
即当CPL的特权级高于GDT表项中的描述符特权级DPL,但低于IDT表项中的门描述符的DPL,就是异常。
(5) 检查是否发生了特权级的变化,一般指是否由用户态陷入了内核态。也就是说CPL是否不同于所选择的段描述符的DPL,如果是,控制单元必须开始使用与新的特权级相关的堆栈,通过以下操作来做到这点:
A、读tr寄存器,访问运行进程的TSS段;
B、用与新特权级相关的栈段和栈指针装载ss和esp寄存器。这些值可以在进程的TSS段中找到;
C、在新的栈中保存ss和esp以前的值,这些值指明了与旧特权级相关的栈的逻辑地址。
(6) 若发生的是故障,用引起异常的指令地址修改cs和eip寄存器的值,以使得这条指令在异常处理结束后能被再次执行。
(7) 在栈中保存eflags、cs和eip的内容。
(8) 如果异常产生一个硬件出错码,则将它保存在栈中。
(9) 装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这对寄存器的值指出中断或者异常处理程序的第一条指令的逻辑地址。
控制单元所执行的最后一步就是跳转到中断或异常处理程序,换句话说,处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。
中断/异常处理完后,相应的处理程序会执行一条iret汇编指令,把控制权转交给被中断的进程,这条汇编指令让CPU控制单元做如下事情:
(1) 用保存在栈中的值装载cs、eip和eflags寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么执行iret指令前必先弹出这个硬件出错码。
(2) 检查处理程序的特权级CPL是否等于cs中最低两位的值(这意味着进程在被中断的时候是运行在内核态还是用户态,即被中断的进程与处理程序是否运行在同一特权级)。若是,iret终止执行;否则,转入下一步。
(3) 从栈中装载ss和esp寄存器。这步意味着返回到与旧特权级相关的栈。
(4) 检查ds、es、fs和gs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且特权级比当前特权级高(DPL的值小于CPL的值),则清除相应的寄存器。控制单元这么做是防止怀有恶意的用户程序利用内核以前所用的寄存器访问内核空间。
二、Linux对ARM中断向量表的初始化 1、中断向量表的作用如上在中断的硬件处理的分析中可知:中断首先是一个硬件行为, 而处理中断呢, 显然又是一个软件行为,那么当硬件触发中断的时候,怎么调用到中断处理函数的呢?要搞清楚这个问题,就要搞清楚发生中断的时候 CPU做了什么。以S3C2410X系统的异常中断为例:
S3C2410X是基于ARM920T内核处理器。该系统提供的FIQ和IRQ异常中断用于外部设备向CPU请求服务,一般情况下都是采用IRQ。S3C2410X系统中通常在存储区的低端固化了一个32字节的硬件中断向量表,用来指定各异常中断与其处理程序的对应关系。CPU知道一个source触发了中断,怎么调用执行一些函数(汇编,或者c语言),就是靠异常向量表(事实上,exception vector table 也是由汇编组成的)
异常 |
模式 |
向量表偏移 |
复位(reset) |
SVC |
+0x00 |
未定义指令 |
UND |
+0x04 |
软件中断(SWI) |
SVC |
+0x08 |
预取指终止 |
ABT |
+0x0c |
数据终止 |
ABT |
+0x10 |
未分配 |
-- |
+0x14 |
IRQ |
IRQ |
+0x18 |
FIQ |
FIQ |
+0x1c |
arm异常表,对应模式及向量表偏移 (摘自arm体系结构与编程一书)
当一个异常/中断出现后, S3C2410X系统中ARM处理器对其的响应过程如下:
(1) 保存处理器当前状态、中断屏蔽位以及各条件标志位。将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中。
(2) 设置当前程序状态寄存器CPSR中相应的位。包括设置CPSR中的位,使处理器进入相应的执行模式;设置CPSR中的位,禁止IRQ中断,当进入FIQ模式时,禁止FIQ中断。
(3) 将寄存器lr_mode设置成返回地址。
(4) 将程序计数器值(PC),设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序执行。即处理器跳转到异常向量表中相应的入口(对于IRQ , 显然pc=0x18) 。
所以当触发IRQ后,CPU会最后跳入0x18 这个入口,定制kernel时只需在这个入口填入自己的指令(当然是汇编语句) ,即可调用中断处理函数,可能这样:
触发IRQ—CPU jump 到0x18,同时要把irqno传入相应的寄存器调用一个中断通用处理函数如:asm_do_IRQ(unsigned int irqno) asm_do_IRQ() 这个函数根据irqno 就可以找到对应的中断描述符,然后调用中断描述符里面的handler()了。
2、中断向量表的初始化v arch/arm/kernel/entry-armv.S ——中断向量表放在这个文件里:
__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:
ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表。
v linux/init/main.c Start_kernel中的中断向量表初始化
asmlinkage void __init start_kernel(void)
{
.....
trap_init();
init_IRQ();
....
中断的初始化主要和这两个函数相关
(1) trap_init()
v linux/arch/arm/kernel/traps.c
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
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);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
CONFIG_VECTORS_BASE是一个宏,用来获取ARM异常向量的地址,该宏在include/arch/asm-arm/system.h中定义:
v include/arch/asm-arm/system.h
#define CPU_ARCH_ARMv5 4
#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */
extern unsigned long cr_no_alignment; /* defined in entry-armv.S */
extern unsigned long cr_alignment; /* defined in entry-armv.S */
#if __LINUX_ARM_ARCH__ >= 4
#define vectors_high() (cr_alignment & CR_V)
#else
#define vectors_high() (0)
#endif
对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)
v arch/arm/kernel/entry-armv.S—找到cr_alignment的定义:
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
v linux/arch/arm/kernel/head.S—当内核启动时,进入head.S文件:
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags pointer.
*
…… …… …… …… ……
*/
.section ".text.head", "ax"
.type stext, %function
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __vet_atags
bl __create_page_tables //创建arm启动临时使用的前4M页表
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_machine_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, __switch_data @ address to jump to after //99行
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC //102行
…… …… …… …… …… …… ……
__turn_mmu_on:
mov r0, r0 //填充armv4中的三级流水线:mov r0,r0 对//应一个nop,所以对应2个nop和一个mov pc,lr刚好三个"无用"操作
mcr p15, 0, r0, c1, c0, 0 @ write control reg //193行
mrc p15, 0, r3, c0, c0, 0 @ read id reg
…… …… …… …… …… …… ……
mov pc, lr //327行
在s3c2410平台中,它将跳转到arch/arm/mm/proc-arm920.S中执行__arm920 _setup函数。即第102行“add pc, r10, #PROCINFO_INITFUNC”: 执行b _arm920_setup
v linux/arch/arm/mm/proc-arm920.S: MMU functions for ARM920
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long PMD_TYPE_SECT | /
PMD_SECT_BUFFERABLE | /
PMD_SECT_CACHEABLE | /
PMD_BIT4 | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | /
PMD_BIT4 | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
b __arm920_setup
// add pc, r10, #PROCINFO_INITFUNC”:将执行b _arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
…… …… …… …… …… …… ……
.size __arm920_proc_info, . - __arm920_proc_info
…… …… …… …… …… …… …… …… …… …… …… …… …… …… ……
.type __arm920_setup, #function
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
.size __arm920_setup, . - __arm920_setup
当在arm920_setup设置完协处理器和返回寄存器r0之后,跳回到linux/arch/arm/kernel/head.S文件中的第99行,在lr寄存器中放置__switch_data中的数据__mmap_switched,第327行程序会跳转到__mmap_switched处。在__turn_mmu_on:后,第193,194行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。 接下来再来看一下跳转到__mmap_switched处的代码:
v linux/arch/arm/kernel/head-common.S
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags pointer
* r9 = processor ID
*/
.type __mmap_switched, %function
__mmap_switched: //40行
adr r3, __switch_data + 4 //41行
ldmia r3!, {r4, r5, r6, r7} //43行
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b //53行
ldmia r3, {r4, r5, r6, r7, sp} // sp ~ (init_task_union)+8192
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values //60行
b start_kernel // 进入内核C程序
41~43行的结果是:r6=__bss_start,r7=__end,...,r7=cr_alignment,..,这里r7保存的是cr_alignment变量的地址。
到了60行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r7指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。
让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
v linux/arch/arm/kernel/entry-armv.S
/*============================================================
* Address exception handler
*-----------------------------------------------------------------------------
* These aren't too critical.
* (they're not supposed to happen, and won't happen in 32-bit data mode).
*/
vector_addrexcptn:
b vector_addrexcptn
/*
* We group all the following data together to optimise
* for CPUs with separate I & D caches.
*/
.align 5
.LCvswi:
.word vector_swi
.globl __stubs_end
__stubs_end:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__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:
.data
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。
(2) init_IRQ()
v linux/arch/arm/kernel/irq.c
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++) // NR_IRQS代表中断数目
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
// irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个
//irq_desc结构,组成了一个数组。
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
init_arch_irq();
}
v include/linux/irq.h
/**
* struct irq_desc - interrupt descriptor
*
* @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()]
* @chip: low level interrupt hardware access
* @msi_desc: MSI descriptor
* @handler_data: per-IRQ data for the irq_chip methods
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
* @action: the irq action chain
* @status: status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @last_unhandled: aging timer for unhandled count
* @lock: locking for SMP
* @affinity: IRQ affinity on SMP
* @cpu: cpu index useful for balancing
* @pending_mask: pending rebalanced interrupts
* @dir: /proc/irq/ procfs entry
* @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
unsigned long last_unhandled; /* Aging timer for unhandled count */
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
三、Linux对ARM中断、异常的处理 1、硬件发生中断后,找到中断入口函数
从entry-armv.S中的中断向量表定义可以看到,发生中断时系统会跳到 vector_irq + stubs_offset处运行,这个位置实际上就是中断入口函数。
vector_irq已经是中断的入口函数了,为什么又要加上 stubs_offset?是因为b指令实际上是相对当前PC的跳转,也就是说当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。
我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断向量中的中断入口地址的偏移量就是:200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC +vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的 vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的
2、根据原来发生中断时CPU所处的工作模式找到相应的入口函数v arch/arm/kernel/entry-armv.S—vector_irq是通过宏来vector_stub定义的
/*
* Vector stubs.
*
* This code is copied to 0xffff0200 so we can use branches in the
* vectors, rather than ldr's. Note that this code must not
* exceed 0x300 bytes.
*
* Common stub entry macro:
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.macro vector_stub, name, mode, correction=0
.align 5
vector_/name:
.if /correction
sub lr, lr, #/correction
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(/mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.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
......
从上面这段代码可以看出,vector_irq把发生中断时的r0,PC-4以及CPSR压栈(注意,压入的的irq模式的堆栈),把中断栈的栈指针赋给 r0,最后根据原来发生中断时CPU所处的工作模式(CPSR的低4位)找到相应的入口函数,在进入svc模式后进一步处理中断。
3、__irq_usr完成的工作它主要通过调用宏usr_entry进一步保存现场,然后调用irq_handler进行中断处理,保存的栈结构如下:
-1 |
CPSR |
PC-4 |
LR |
SP |
R12 |
... |
R2 |
R1 |
R0 |
其中的LR,SP是USR模式下的,R0,CPSR,PC-4是从中断栈中拷贝的
4、irq_handler的作用它首先通过宏 get_irqnr_and_base获得中断号,存入r0,然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了处理下一个中断),然后调用 asm_do_IRQ进一步处理中断,以上这些操作都在建立在获得中断号的前提下,也就是有中断发生,
即当某个外部硬件触发中断的时候,kernel最终会调用到:asm_do_IRQ
v linux/arch/arm/kernel/entry-armv.S
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr //获得中断号并存入r0
movne r1, sp //将sp的值赋给r1,即pt_regs结构的指针
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b //把调用宏 get_irqnr_and_base的位置作为返回地址
bne asm_do_IRQ
v linux/arch/arm/kernel/irq.c
/*
* do_IRQ handles all hardware IRQ's. Decoded IRQs should not
* come via this function. Instead, they should provide their
* own 'handler'
*/
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq;
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc);
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
v linux/include/asm-generic/irq_regs.h
static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)
{
struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);
old_regs = *pp_regs;
*pp_regs = new_regs;
return old_regs;
}
v linux/kernel/softirq.c
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
#ifdef CONFIG_NO_HZ
int cpu = smp_processor_id();
if (idle_cpu(cpu) && !in_interrupt())
tick_nohz_stop_idle(cpu);
#endif
__irq_enter();
#ifdef CONFIG_NO_HZ
if (idle_cpu(cpu))
tick_nohz_update_jiffies();
#endif
}
v linux/include/asm-arm/mach/irq.h
/*
* Obsolete inline function for calling irq descriptor handlers.
*/
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
v include/asm-arm/arch-at91/irqs.h
/*
* Acknowledge interrupt with AIC after interrupt has been handled.
* (by kernel/irq.c)
*/
#define irq_finish(irq) do { at91_sys_write(AT91_AIC_EOICR, 0); } while (0)
v linux/kernel/softirq.c
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())
tick_nohz_stop_sched_tick();
rcu_irq_exit();
#endif
preempt_enable_no_resched();
}
参考文献:
1、Bovet & Cesati著,陈莉君等译,深入理解Linux内核,中国电力出版社,2007
2、刘淼,嵌入式系统接口设计与Linux驱动程序开发,北京航天航空大学出版社,2006.
3、ARM Architecture Reference Manual, ARM limited,2000.
4、http://jimmy-lee.blog.hexun.com/cate.aspx?cateid=7311&cate=ARM%26Linux 2008
5、http://blog.chinaunix.net/u/22617/showart_470098.html
因水平有限,定有不足之处,还望多多交流, 。