linux arm irq : interrupt handling
Posted WangYangkai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux arm irq : interrupt handling相关的知识,希望对你有一定的参考价值。
linux arm irq (2)
2 interrupt handling
Author: Yangkai Wang
wang_yangkai@163.com
Coding in 2021/05/10
转载请注明author,出处.
linux version 3.4.39
s5p6818 soc
Cortex-A53 Octa core CPU
Interrupt Controller,GIC400
- idle进程(start_kernel)stack(svc)的设置
/* arch/arm/kernel/head-common.S */
...
/*
* 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/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
adr r3, __mmap_switched_data
ldmia r3!, {r4, r5, r6, r7}
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
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
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
b start_kernel
ENDPROC(__mmap_switched)
...
.align 2
.type __mmap_switched_data, %object
__mmap_switched_data:
.long __data_loc @ r4
.long _sdata @ 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
.size __mmap_switched_data, . - __mmap_switched_data
__mmap_switched:
adr r3, __mmap_switched_data
...
ARM( ldmia r3, {r4, r5, r6, r7, sp}) /* init_thread_union + THREAD_START_SP @ sp */
b start_kernel
/* arch/arm/kernel/init_task.c */
...
/*
* Initial thread structure.
*
* We need to make sure that this is 8192-byte aligned due to the
* way process stacks are handled. This is done by making sure
* the linker maps this in the .text segment right after head.S,
* and making head.S ensure the proper alignment.
*
* The things we do for performance..
*/
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
...
/* include/linux/init_task.h */
...
/* Attach to the init_task data structure for proper alignment */
#define __init_task_data __attribute__((__section__(".data..init_task")))
...
其他SMP core,stack怎么设置的,以后再分析;
- linux kernel arm exception stack init
- sets up the CPU(startup core) stacks
void __init start_kernel(void)
|
setup_arch(&command_line);
|
setup_processor();
|
cpu_init();
/* arch/arm/kernel/setup.c */
...
struct stack {
u32 irq[3];
u32 abt[3];
u32 und[3];
} ____cacheline_aligned;
static struct stack stacks[NR_CPUS];
...
/*
* cpu_init - initialise one CPU.
*
* cpu_init sets up the per-CPU stacks.
*/
void cpu_init(void)
{
unsigned int cpu = smp_processor_id();
struct stack *stk = &stacks[cpu];
if (cpu >= NR_CPUS) {
printk(KERN_CRIT "CPU%u: bad primary CPU number\\n", cpu);
BUG();
}
cpu_proc_init();
/*
* Define the placement constraint for the inline asm directive below.
* In Thumb-2, msr with an immediate value is not allowed.
*/
#ifdef CONFIG_THUMB2_KERNEL
#define PLC "r"
#else
#define PLC "I"
#endif
/*
* setup stacks for re-entrant exception handlers
*/
__asm__ (
"msr cpsr_c, %1\\n\\t"
"add r14, %0, %2\\n\\t"
"mov sp, r14\\n\\t"
"msr cpsr_c, %3\\n\\t"
"add r14, %0, %4\\n\\t"
"mov sp, r14\\n\\t"
"msr cpsr_c, %5\\n\\t"
"add r14, %0, %6\\n\\t"
"mov sp, r14\\n\\t"
"msr cpsr_c, %7"
:
: "r" (stk),
PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
"I" (offsetof(struct stack, irq[0])),
PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
"I" (offsetof(struct stack, abt[0])),
PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
"I" (offsetof(struct stack, und[0])),
PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
: "r14");
}
arm c代码中内嵌汇编,语法形式: asm(汇编语句: 输出操作数列表: 输入操作数列表: 破坏描述部分)
汇编语句,由汇编语句序列组成,语句之间使用 “;”、“\\n”或“\\n\\t”分开;
指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1,%2,…,%9;
指令中使用占位符表示的操作数,总被视为long型(4 byte);
输入和输出操作数列表的格式是一样的,是由一个或者多个操作数组成,多个操作数采用逗号隔开,单个操作数的格式:
[C变量在汇编中的访问名称] "限制性字符“ (C传递进来的变量名称或立即数)
常用限制性字符:
I: 立即数
m: 内存地址(变量地址)
r: 寄存器(r0-r15,传参)
/* ARM ® Architecture Reference Manual(ARM ® v7-A and ARM ® v7-R edition)
Application Level Programmers’ Model
Instruction Details*/
A8.6.102 MRS
A8.6.103 MSR (immediate)
A8.6.104 MSR (register)
Move to Special Register from ARM core register moves selected bits of a general-purpose register to the
APSR.
Encoding T1 ARMv6T2, ARMv7
MSR<c> <spec_reg>,<Rn>
...
<spec_reg> Is one of:
• APSR_<bits>
• CPSR_<fields>.
ARM,只有 MSR instruction可以设置APSR processor状态寄存器(CPSR/SPSR)
PS:
MRS
Move to Register from Special Register moves the value from the APSR into a general-purpose register.
MRS
只有 MRS instruction可以读取APSR processor状态寄存器(CPSR/SPSR)
"msr cpsr_c, %1\\n\\t" /* 将(PSR_F_BIT | PSR_I_BIT | IRQ_MODE) 赋值给CPSR, 进入IRQ mode */
"add r14, %0, %2\\n\\t" /* stk的地址值 + ((size_t) &((struct stack *)0)->irq[0]) 的值赋值给r14寄存器 */
"mov sp, r14\\n\\t" /* the value of r14 赋值给IRQ mode 的SP register */
依次设置IRQ,ABT,UND,模式的stack; 最后"msr cpsr_c, %7", 切回SVC mode;
- early_trap_init(vectors)
void __init start_kernel(void)
|
setup_arch(&command_line);
|
paging_init(mdesc);
|
map_lowmem();
|
/* Map all the lowmem memory banks. */
|
devicemaps_init(mdesc);
|
/*
* Allocate the vector page early.
*/
vectors = early_alloc(PAGE_SIZE);
early_trap_init(vectors);
...
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren\'t using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map, false);
if (!vectors_high()) {
map.virtual = 0;
map.type = MT_LOW_VECTORS;
create_mapping(&map, false);
}
...
/* arch/arm/kernel/traps.c */
...
void __init early_trap_init(void *vectors_base)
{
unsigned long vectors = (unsigned long)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;
vectors_page = vectors_base;
/*
* 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);
/*
* Do processor specific fixups for the kuser helpers
*/
kuser_get_tls_init(vectors);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),
sigreturn_codes, sizeof(sigreturn_codes));
memcpy((void *)(vectors + KERN_RESTART_CODE - CONFIG_VECTORS_BASE),
syscall_restart_code, sizeof(syscall_restart_code));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
...
call early_alloc(PAGE_SIZE)
|
memblock_alloc()
申请了一个page 4K大小的内存;
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这三段到这个page;这样分布:
__vectors_start /* 0x0*/
...
__vectors_end
...
__stubs_start /* 0x200 */
...
__stubs_end
...
__kuser_helper_start
...
__kuser_helper_end
...
之后,将这个page 映射到虚拟地址0xffff0000;
reference:
/* ARM ® Architecture Reference Manual(ARM ® v7-A and ARM ® v7-R edition)
Part B System Level Architecture
The System Level Programmers’ Model
B1.6 Exceptions
B1.6.1 Exception vectors and the exception base address */
If the Security Extensions are not implemented there is a single exception base address. This is controlled
by the SCTLR.V bit:
V == 0 Exception base address = 0x00000000. This setting is referred to as normal vectors, or as low
vectors.
V == 1 Exception base address = 0xFFFF0000. This setting is referred to as high vectors, or Hivecs.
linux ARM32 不配置为Exception base address = 0x00000000,因为0x00000000属于0G-3G,用户空间,就不去分割占用 用户空间了,让0-3G 连续地址空间都给应用程序用;
- exception entry
/* ARM ® Architecture Reference Manual(ARM ® v7-A and ARM ® v7-R edition)
Part B System Level Architecture
The System Level Programmers’ Model
B1.6 Exceptions */
B1.6.3 Exception entry
On taking an exception:
1. The value of the CPSR is saved in the SPSR for the exception mode that is handling the exception.
2. The value of (PC + exception-dependent offset) is saved in the LR for the exception mode that is handling the exception, see Table B1-4.
3. The CPSR and PC are updated with information for the exception handler:
• The CPSR is updated with new context information. This includes:
— Setting CPSR.M to the processor mode in which the exception is to be handled.
— Disabling appropriate classes of interrupt, to prevent uncontrolled nesting of exception handlers. For more information, see Table B1-6 on page B1-36, Table B1-7 on page B1-37, and Table B1-8 on page B1-37.
— Setting the instruction set state to the instruction set chosen for exception entry, see Instruction set state on exception entry on page B1-35.
— Setting the endianness to the value chosen for exception entry, see CPSR.E bit value on exception entry on page B1-38.
— Clearing the IT[7:0] bits to 0.
For more information, see CPSR M field and A, I, and F mask bit values on exception entry on page B1-36.
• The appropriate exception vector is loaded to the PC, see Exception vectors and the exception base address on page B1-30.
4. Execution continues from the address held in the PC.
...
B1.6.4 Exception return
...
- exception vectors
/* arch/arm/kernel/entry-armv.S */
...
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__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
.globl __vectors_end
__vectors_end:
...
interrupt request occur in svc/usr processor mode
-
CPSR is saved in the SPSR for the IRQ exception mode.
-
The value of PC is saved in the LR for the IRQ exception mode.
-
The CPSR is updated
— Setting CPSR.M to the IRQ exception mode.
— Disabling IRQ interrupt.
— Setting the instruction set state to the instruction set chosen for exception entry.
— Setting the endianness to the value chosen for exception entry.
— Clearing the IT[7:0] bits to 0.
For more information, see CPSR M field and A, I, and F mask bit values on exception entry on page B1-36.
(On exception entry, the CPSR.I bit is always set to 1, to disable IRQs)
(On IRQ exception entry, CPSR.A:1, CPSR.A:Unchanged)
The appropriate exception vector is loaded to the PC. -
Execution continues from the address held in the PC.
W(b) vector_irq + stubs_offset
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
/* arch/arm/kernel/entry-armv.S */
.align 2
@ handler addresses follow this label
1:
.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
.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
/*
* Data abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub dabt, ABT_MODE, 8
.long __dabt_usr @ 0 (USR_26 / USR_32)
.long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
.long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
.long __dabt_svc @ 3 (SVC_26 / SVC_32)
.long __dabt_invalid @ 4
.long __dabt_invalid @ 5
.long __dabt_invalid @ 6
.long __dabt_invalid @ 7
.long __dabt_invalid @ 8
.long __dabt_invalid @ 9
.long __dabt_invalid @ a
.long __dabt_invalid @ b
.long __dabt_invalid @ c
.long __dabt_invalid @ d
.long __dabt_invalid @ e
.long __dabt_invalid @ f
/*
* Prefetch abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub pabt, ABT_MODE, 4
.long __pabt_usr @ 0 (USR_26 / USR_32)
.long __pabt_invalid @ 1 (FIQ_26 / FIQ_32)
.long __pabt_invalid @ 2 (IRQ_26 / IRQ_32)
.long __pabt_svc @ 3 (SVC_26 / SVC_32)
.long __pabt_invalid @ 4
.long __pabt_invalid @ 5
.long __pabt_invalid @ 6
.long __pabt_invalid @ 7
.long __pabt_invalid @ 8
.long __pabt_invalid @ 9
.long __pabt_invalid @ a
.long __pabt_invalid @ b
.long __pabt_invalid @ c
.long __pabt_invalid @ d
.long __pabt_invalid @ e
.long __pabt_invalid @ f
/*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/
vector_stub und, UND_MODE
.long __und_usr @ 0 (USR_26 / USR_32)
.long __und_invalid @ 1 (FIQ_26 / FIQ_32)
.long __und_invalid @ 2 (IRQ_26 / IRQ_32)
.long __und_svc @ 3 (SVC_26 / SVC_32)
.long __und_invalid @ 4
.long __und_invalid @ 5
.long __und_invalid @ 6
.long __und_invalid @ 7
.long __und_invalid @ 8
.long __und_invalid @ 9
.long __und_invalid @ a
.long __und_invalid @ b
.long __und_invalid @ c
.long __und_invalid @ d
.long __und_invalid @ e
.long __und_invalid @ f
.align 5
/*=============================================================================
* Undefined FIQs
*-----------------------------------------------------------------------------
* Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
* MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
* Basically to switch modes, we *HAVE* to clobber one register... brain
* damage alert! I don\'t think that we can execute any code in here in any
* other mode than FIQ... Ok you can switch to another mode, but you can\'t
* get out of that mode without clobbering one register.
*/
vector_fiq:
subs pc, lr, #4
/*=============================================================================
* 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:
/* arch/arm/kernel/entry-armv.S */
/*
* 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_<exception> (parent PC) and spsr_<exception>
@ (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 | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\\name)
vector_stub irq, IRQ_MODE, 4
展开,去除THUMB() 相关代码,
(
THUMB() 和 ARM() 预处理宏 用于根据目标指令集 有条件地编译 源代码;
if target instruction set is ARM,则THUMB()宏为no ops;
if target instruction set is THUMB,则THUMB()宏将扩展为其参数;
/* arch/arm/include/asm/unified.h, arch/arm/include/asm/assembler.h for more conditionalized macros */
CONFIG_THUMB2_KERNEL这个宏是没定义的;
/* arch/arm/include/asm/unified.h */
...
#ifdef CONFIG_THUMB2_KERNEL
#if __GNUC__ < 4
#error Thumb-2 kernel requires gcc >= 4
#endif
/* The CPSR bit describing the instruction set (Thumb) */
#define PSR_ISETSTATE PSR_T_BIT
#define ARM(x...)
#define THUMB(x...) x
#ifdef __ASSEMBLY__
#define W(instr) instr.w
#define BSYM(sym) sym + 1
#endif
#else /* !CONFIG_THUMB2_KERNEL */
/* The CPSR bit describing the instruction set (ARM) */
#define PSR_ISETSTATE 0
#define ARM(x...) x
#define THUMB(x...)
#ifdef __ASSEMBLY__
#define W(instr) instr
#define BSYM(sym) sym
#endif
#endif /* CONFIG_THUMB2_KERNEL */
...
)
有:
.align 2
@ handler addresses follow this label
1:
.endm
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
/*vector_stub irq, IRQ_MODE, 4*/
/* .macro vector_stub, name, mode, correction=0 */
.align 5
vector_\\name:
.if \\correction
sub lr, lr, #\\correction
.endif
/* 进入IRQ中断,W(b) vector_irq + stubs_offset;跳转到sub lr, lr, #\\correction;
这里,IRQ mode,当前的context:
r0 到 r12,没操作,还是中断前的context;
lr,irq mode的lr, save被中断那刻的PC值;
sp,irq mode, save irq mode栈的地址;
cpsr, irq模式
spsr, save中断前的CPSR(svc/usr)
correction的值是4,
sub lr, lr, #\\correction; /*lr = lr - 4;*/
ARM 架构执行一条instruction的pipeline至少包含流程:取值,译码,执行,(访存),(回写);
PC寄存器保存的是取指PC值, 被中断时执行的指令的地址是:PC - 8; 以后中断处理完成,需要接着执行被中断的下一条指令的地址是PC - 4; 故lr 的值 减去 4; 进入不同异常模式correction 值不同;
*/
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
/* stmia sp, {r0, lr} ;将 r0 lr的数据存储到 sp(irq) 指向的地址上, sp 后没有带!,SP值不更新;
因为后面r0 会用到,故需要先保存到栈;这个栈是IRQ栈,只有12个字节大小;
lr 保存的是中断跳转前一刻的,pc(svc/usr) - 4值;
mrs lr, spsr; spsr(irq)保存中断前svc/usr模式的cpsr; spsr的值赋给lr,
str lr, [sp, #8]; sp = sp + 8; lr的值保存到sp 指向的地址;
IRQ的栈,依次入栈了:r0 lr(svc/usr pc) SPSR(svc/usr cpsr);
*/
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
/* mrs r0, cpsr; cpsr 的值 赋值给 r0;
eor r0, r0, #(\\mode ^ SVC_MODE | PSR_ISETSTATE); eor,异或操作指令; r0 = r0 eor
mode 的值是IRQ_MODE,/* arm/arm/include/asm/ptrace.h*/
#define IRQ_MODE 0x00000012
#define SVC_MODE 0x00000013
eor r0, r0, #(0x12 ^ 0x13 | 0x0)
eor r0 , r0, #(0x01)
r0(bit[0:4]:10010) eor 0x01; -> r0(bit[0:4]:10011), CSSR M[0:4]:svc mode;
msr spsr_cxsf, r0; parts of r0 的值save to SPSR_irq; 其中SPSR_irq 中的M[0:4],改为SVC mode
(msr here is SPSR_<fields>, where <fields> "Is a sequence of one or more of" c, x, s, f, which represent bits 7:0, 15:8, 23:16 and 31:24 respectively)
*/
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
/*ENDPROC(vector_\\name)*/
.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
/* and lr, lr, #0x0f; lr &= 0x0f; 进入中断前cpsr的值&0x0f,以确认是从svc 模式还是从usr模式进入中断的;
reference:B1.3.1 ARM processor modes
processor mode, mode encoding,
User 10000
FIQ 10001
IRQ 10010
Supervisor 10011
Monitor 10110
Abort 10111
Undefined 11011
System 11111
lr & 0x0f 值为0,从User模式进入中断; 为3,从svc模式进入中断;
mov r0, sp; sp(IRQ)的值赋值给r0;
ARM( ldr lr, [pc, lr, lsl #2] ); lr = [pc + (lr << 2)]; lsl, #2, 左移2bit;相当于与剩以4;
如果lr 值等于0, lr 的值为pc的值;
如果lr 值等于3, lr 的值为pc + 12;
ARM 架构执行一条instruction的pipeline至少包含流程:取值,译码,执行,(访存),(回写);
ARM core pc寄存器的值,存的是取指的地址;当执行到ARM( ldr lr, [pc, lr, lsl #2] ),这条指令时,pc 存的当前指令 地址值+8;
故:
如果lr 值等于0, lr 的值为pc的值;即:__irq_usr
如果lr 值等于3, lr 的值为pc + 12;即:__irq_svc
到这里IRQ mode, 当前的context:
r0 保存 sp(IRQ) 的值;IRQ的栈,保存中断跳转前一刻的r0, pc, cpsr;
r1 到 r12,进入中断后没操作,还是中断前的context;
sp指向 sp的栈;
cpsr, irq模式;
spsr, irq模式; CPSR_irq的cxsf,save to SPSR_irq,且其中的M[0:4],为SVC mode
movs pc, lr;lr 的值赋值给pc;
跳转到
__irq_usr or __irq_svc
movs,
s, If present, specifies that the instruction updates the flags. Otherwise, the instruction does not update the flags.
执行MOVS pc, lr时,SPSR_irq会copy(move) 到 CPSR_svc;也就是会切换到svc mode;
到这里切换到SVC mode, 当前的context:
r0 保存 sp(IRQ) 的值;sp_irq栈,保存中断跳转前一刻的r0, pc, cpsr;
r1 到 r12,进入中断后没操作,还是中断前的context;
sp指向 svc的栈;
cpsr, svc模式;
spsr, svc模式;
PS:
user mode context: svn mode context: irq mode context:
R0_usr R0_usr R0_usr
R1_usr R1_usr R1_usr
R2_usr R2_usr R2_usr
R3_usr R3_usr R3_usr
R4_usr R4_usr R4_usr
R5_usr R5_usr R5_usr
R6_usr R6_usr R6_usr
R7_usr R7_usr R7_usr
R8_usr R8_usr R8_usr
R9_usr R9_usr R9_usr
R10_usr R10_usr R10_usr
R11_usr R11_usr R11_usr
R12_usr R12_usr R12_usr
SP_usr SP_svc SP_irq
LR_usr LR_svc LR_irq
PC PC PC
CPSR CPSR CPSR
SPSR_svc SPSR_irq
*/
/*
* Data abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub dabt, ABT_MODE, 8
.long __dabt_usr @ 0 (USR_26 / USR_32)
.long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
.long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
.long __dabt_svc @ 3 (SVC_26 / SVC_32)
.long __dabt_invalid @ 4
.long __dabt_invalid @ 5
.long __dabt_invalid @ 6
.long __dabt_invalid @ 7
.long __dabt_invalid @ 8
.long __dabt_invalid @ 9
.long __dabt_invalid @ a
.long __dabt_invalid @ b
.long __dabt_invalid @ c
.long __dabt_invalid @ d
.long __dabt_invalid @ e
.long __dabt_invalid @ f
...
- __irq_usr
/* arch/arm/kernel/entry-armv.S */
.align 5
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
usr_entry宏,去掉THUMB(),code:
/* arch/arm/kernel/entry-armv.S */
/*
* User mode handlers
*
* EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
*/
#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7)
#error "sizeof(struct pt_regs) must be a multiple of 8"
#endif
.macro usr_entry
UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don\'t unwind the user space
sub sp, sp, #S_FRAME_SIZE
ARM( stmib sp, {r1 - r12} )
/*
#define S_FRAME_SIZE 72 /* sizeof(struct pt_regs) /* include/generated/asm-offsets.h */
这里是在svc mode;
sp = sp - 72; ARM 的栈是高地址往低地址长; 72 / 4 = 18,减去72,先腾出72个byte的栈空间;
为啥是18 * sizeof(unsigned long);这是上下文的大小;
arch/arm/include/asm/ptrace.h:
/*
* This struct defines the way the registers are stored on the
* stack during a system call. Note that sizeof(struct pt_regs)
* has to be a multiple of 8.
*/
#ifndef __KERNEL__
struct pt_regs {
long uregs[18];
};
#else /* __KERNEL__ */
struct pt_regs {
unsigned long uregs[18];
};
#endif /* __KERNEL__ */
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
ARM( stmib sp, {r1 - r12} )
STMIB sp!,{R1-r12} ;将 r1~r12 的数据保存到内存中,sp指针在保存第一个值之前增加,增长方向为向上增长。
这里sp后面没有!,sp 指向的地址没变;
low address
|_ sp save地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_
|_
|_
|_
|_
high address
*/
ldmia r0, {r3 - r5}
add r0, sp, #S_PC @ here for interlock avoidance
mov r6, #-1 @ "" "" "" ""
str r3, [sp] @ save the "real" r0 copied
@ from the exception stack
/* r0 保存IRQ mode栈的地址;
ldmia r0, {r3 - r5}
ldmia r0!,{r3-r5}; 加载 r0 指向的地址上的多字数据,保存到 r3~r9 中,r0 值更新,没有!,r0不更新;
这样r3到r5,依次保存: 中断跳转前一刻的r0, pc, cpsr;
dd r0, sp, #S_PC; r0 = sp + #S_PC;
include/generated/asm-offsets.h:
#define S_PC 60 /* offsetof(struct pt_regs, ARM_pc) @ */
60 / 4 = 15
mov r6, #-1
str r3, [sp]
到这里svc stack:
low address
|_r0 sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_
|_
|_ r0 save的地址值
|_
|_
high address
*/
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r4 - lr_<exception>, already fixed up for correct return/restart
@ r5 - spsr_<exception>
@ r6 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmia r0, {r4 - r6}
ARM( stmdb r0, {sp, lr}^ )
/*stmia r0, {r4 - r6}
r4,r5为 中断跳转前一刻的pc(lr_irq), cpsr(spsr_irq)值; r6的值为-1;
将 r4~r6 的数据存储到 r0 指向的地址上,增长方向为向上增长,r0 不更新;
low address
|_r0 sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_
|_
|_pc r0 save的地址值
|_cpsr
|_-1
high address
ARM( stmdb r0, {sp, lr}^ )
将 sp,lr 的数据保存到内存中,增长方向为向下增长, ^表示访问usr mode的寄存器;
这时有:
low address
|_r0 svc sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_lr_usr
|_sp_usr
|_pc r0 save的地址值
|_cpsr
|_-1
high address
到这里中断发生前一刻的context都保存到svc stack中了;
中断程序处理完成后,restore这个上下文,就接着执行中断被打断后的下一条指令;
mov r6 , #-1; 为啥要orig_r0 ?
*/
@
@ Enable the alignment trap while in kernel mode
@
alignment_trap r0
@
@ Clear FP to mark the first stack frame
@
zero_fp
#ifdef CONFIG_IRQSOFF_TRACER
bl trace_hardirqs_off
#endif
.endm
- irq_handler:
/* arch/arm/kernel/entry-armv.S */
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
/*
./include/generated/autoconf.h:813:#define CONFIG_MULTI_IRQ_HANDLER 1
ldr r1, =handle_arch_irq
mov r0, sp; /* sp_svc 地址值save to r0, 传参 */
adr lr, BSYM(9997f); /* adr 伪指令,将BSYM(9997f) 地址值放到lr, f应该是front的含义 */
./arch/arm/include/asm/unified.h:52:#define BSYM(sym) sym
irq_handler,是个宏, 这里将lable 9997f后的指令的地址值,给到lr;
为从handle_arch_irq回来做准备;
ldr pc, [r1]; /* 跳转到handle_arch_irq() */
*/
- IRQ exit
Note:
/* ./arch/arm/kernel/entry-header.S */
/*
* These are the registers used in the syscall handler, and allow us to
* have in theory up to 7 arguments to a function - r0 to r6.
*
* r7 is reserved for the system call number for thumb mode.
*
* Note that tbl == why is intentional.
*
* We must set at least "tsk" and "why" when calling ret_with_reschedule.
*/
scno .req r7 @ syscall number
tbl .req r8 @ syscall table pointer
why .req r8 @ Linux syscall (!= 0)
tsk .req r9 @ current thread_info
get_thread_info tsk:
/* ./arch/arm/kernel/entry-header.S */
.macro get_thread_info, rd
mov \\rd, sp, lsr #13
mov \\rd, \\rd, lsl #13
.endm
/*
SP_svc 保存的值,右移动13 bit,再左移13bit; 13bit,8K;
获取当前进程的thread_info的地址值,赋值给rd 寄存器;
*/
ret_to_user_from_irq:
/* arch/arm/kernel/entry-common.S */
/*
* "slow" syscall return path. "why" tells us if this was a real syscall.
*/
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts
ENTRY(ret_to_user_from_irq)
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne work_pending
no_work_pending:
#if defined(CONFIG_IRQSOFF_TRACER)
asm_trace_hardirqs_on
#endif
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr
restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)
/*
在call ret_to_user_from_irq,前有:
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
thread_info 地址放到r9(tsk);
r8(why) 赋值为0;
b ret_to_user_from_irq;
*/
#if 0
/* arch/arm/include/asm/thread_info.h */
/*
* low level task data that entry.S needs immediate access to.
* __switch_to() assumes cpu_context follows immediately after cpu_domain.
*/
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value;
struct crunch_state crunchstate;
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
struct restart_block restart_block;
};
#endif
/*
arch/arm/kernel/asm-offsets.c:51: DEFINE(TI_FLAGS, offsetof(struct thread_info, flags));
arch/arm//include/asm/thread_info.h:174:#define _TIF_WORK_MASK 0x000000ff
*/
#if 0
/* arch/arm/include/asm/thread_info.h */
/*
* We use bit 30 of the preempt_count to indicate that kernel
* preemption is occurring. See <asm/hardirq.h>.
*/
#define PREEMPT_ACTIVE 0x40000000
/*
* thread information flags:
* TIF_SYSCALL_TRACE - syscall trace active
* TIF_SYSCAL_AUDIT - syscall auditing active
* TIF_SIGPENDING - signal pending
* TIF_NEED_RESCHED - rescheduling necessary
* TIF_NOTIFY_RESUME - callback before returning to user
* TIF_USEDFPU - FPU was used by this task this quantum (SMP)
* TIF_POLLING_NRFLAG - true if poll_idle() is polling TIF_NEED_RESCHED
*/
#define TIF_SIGPENDING 0
#define TIF_NEED_RESCHED 1
#define TIF_NOTIFY_RESUME 2 /* callback before returning to user */
#define TIF_SYSCALL_TRACE 8
#define TIF_SYSCALL_AUDIT 9
#define TIF_POLLING_NRFLAG 16
#define TIF_USING_IWMMXT 17
#define TIF_MEMDIE 18 /* is terminating due to OOM killer */
#define TIF_RESTORE_SIGMASK 20
#define TIF_SECCOMP 21
#define TIF_SWITCH_MM 22 /* deferred switch_mm */
#define _TIF_SIGPENDING (1 << TIF_SIGPENDING)
#define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED)
#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME)
#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)
#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT)
#define _TIF_POLLING_NRFLAG (1 << TIF_POLLING_NRFLAG)
#define _TIF_USING_IWMMXT (1 << TIF_USING_IWMMXT)
#define _TIF_RESTORE_SIGMASK (1 << TIF_RESTORE_SIGMASK)
#define _TIF_SECCOMP (1 << TIF_SECCOMP)
/* Checks for any syscall work in entry-common.S*/
#define _TIF_SYSCALL_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT)
/*
* Change these and you break ASM code in entry-common.S
*/
#define _TIF_WORK_MASK 0x000000ff
#endif
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne work_pending
判断thread_info->flags & 0xff,是否不为0;
ne 标志Z:0 不相等
eq 标志Z:1 相等
不为0,b work_pending;
这里判断当前task是否有signal pending,resched等标志,有的话进入work_pending,进行相关处理;
这里可以看出,内核对signal的处理是在,异常发生后,从异常返回用户态 前(内核)处理的;
arch_ret_to_user r1, lr:
/* arch/arm/kernel/entry-common.S */
#ifdef CONFIG_NEED_RET_TO_USER
#include <mach/entry-macro.S>
#else
.macro arch_ret_to_user, tmp1, tmp2
.endm
#endif
/* CONFIG_NEED_RET_TO_USER 这个宏是没有定义的 */
restore_user_regs fast = 0, offset = 0
/* arch/arm/kernel/entry-header.S */
.macro restore_user_regs, fast = 0, offset = 0
ldr r1, [sp, #\\offset + S_PSR] @ get calling cpsr
ldr lr, [sp, #\\offset + S_PC]! @ get pc
msr spsr_cxsf, r1 @ save in spsr_svc
.if \\fast
ldmdb sp, {r1 - lr}^ @ get calling r1 - lr
.else
ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
.endif
mov r0, r0 @ ARMv5T and earlier require a nop
@ after ldm {}^
add sp, sp, #S_FRAME_SIZE - S_PC
movs pc, lr @ return & move spsr_svc into cpsr
.endm
/*
./kernel/asm-offsets.c:92: DEFINE(S_PSR, offsetof(struct pt_regs, ARM_cpsr));
./kernel/asm-offsets.c:91: DEFINE(S_PC, offsetof(struct pt_regs, ARM_pc));
ldr r1, [sp, #\\offset + S_PSR] @ get calling cpsr
ldr lr, [sp, #\\offset + S_PC]! @ get pc
msr spsr_cxsf, r1 @ save in spsr_svc
如注释;
这里有个地方要注意,妈呀,看了好久才发现:
ldr lr, [sp, #\\offset + S_PC]! @ get pc;
这里有个!;!用来控制基址变址寻址的最终新地址是否进行回写操作;
这里sp save的值更新为sp : sp + S_PS;
svc stack:
low address
|_r0 svc sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_lr_usr
|_sp_usr
|_pc
|_cpsr
|_-1
high address
ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
将SP_svc指向的内存地址,数据保存到{r0 - lr}中,增长方向为向上增长, ^表示访问usr mode的寄存器; sp 后面没有!;
add sp, sp, #S_FRAME_SIZE - S_PC; SP_svc save的地址值 回到中断发生前的位置
movs pc, lr @ return & move spsr_svc into cpsr
*/
- __irq_svc
/* arch/arm/kernel/entry-armv.S */
.align 5
__irq_svc:
svc_entry
irq_handler
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get 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
#ifdef CONFIG_TRACE_IRQFLAGS
@ The parent context IRQs must have been enabled to get here in
@ the first place, so there\'s no point checking the PSR I bit.
bl trace_hardirqs_on
#endif
svc_exit r5 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
- handle_arch_irq()
void __init start_kernel(void)
|
/* arch/arm/kernel/setup.c */
void __init setup_arch(char **cmdline_p)
|
...
/* arm/kernel/entry-armv.S irq_handler, call handle_arch_irq() */
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq; /* call gic_handle_irq() */
#endif
...
- void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
/* arch/arm/mach-s5p6818/gic.c */
asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
/*printk("~~~ %s() ARM_cpsr:0x%08x\\n", __func__, regs->ARM_cpsr);*/
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
//printk("~~~ %s() irq ack reg:0x%08x\\n", __func__, irqstat);
irqnr = irqstat & ~0x1c00;
if (likely(irqnr > 15 && irqnr < 1021)) {
if (irqnr >= IRQ_PHY_GPIOA)
printk("~~~ %s() hwirq:%d\\n", __func__, irqnr);
irqnr = irq_find_mapping(gic->domain, irqnr);
if (irqnr >= IRQ_PHY_GPIOA)
printk("~~~ %s() irqnr:%d\\n", __func__, irqnr);
handle_IRQ(irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
define GIC_CPU_INTACK 0x0c
ARM ® Generic Interrupt Controller Architecture version 2.0,
4.4.4 Interrupt Acknowledge Register, GICC_IAR;
Purpose:The processor reads this register to obtain the interrupt ID of the signaled interrupt. This read acts as an acknowledge for the interrupt.
Interrupt Acknowledge Register, GICC_IAR,[9:0], The interrupt ID.
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
handle_IPI(irqnr, regs);
continue;
}
define GIC_CPU_EOI 0x10
4.4.5 End of Interrupt Register, GICC_EOIR
Purpose A processor writes to this register to inform the CPU interface either:
• that it has completed the processing of the specified interrupt
• in a GICv2 implementation, when the appropriate GICC_CTLR.EOImode bit is set to 1, to indicate that the interface should perform priority drop for the specified interrupt.
[9:0] EOIINTID The Interrupt ID value from the corresponding GICC_IAR access.
读取GIC cpu interface, Interrupt Acknowledge Register,得到Interrupt ID;
如果id值小于16,是SGI中断; 用于core 之间通信;
把这个ID值写到GIC cpu interface,End of Interrupt Register,that it has completed the processing of the specified interrupt;
之后call handle_IPI(irqnr, regs); and continue,直到所有中断处理完成;
if (likely(irqnr > 15 && irqnr < 1021)) {
if (irqnr >= IRQ_PHY_GPIOA)
printk("~~~ %s() hwirq:%d\\n", __func__, irqnr);
irqnr = irq_find_mapping(gic->domain, irqnr);
if (irqnr >= IRQ_PHY_GPIOA)
printk("~~~ %s() irqnr:%d\\n", __func__, irqnr);
handle_IRQ(irqnr, regs);
continue;
}
如果id值大于15,是Private Peripheral Interrupt(PPI) 和 Share Peripheral Interrupt(SPI)中断;
irqnr = irq_find_mapping(gic->domain, irqnr);
输入gic->domain, GIC 硬件 interrupt number, 以确定是哪个struct irq_desc irq_desc;
得到对应中断,全局的struct irq_desc irq_desc[NR_IRQS]的下标; irqnr;
/* kernel/irq/irqdomain.c */
/**
* irq_find_mapping() - Find a linux irq from an hw irq number.
* @domain: domain owning this hardware interrupt
* @hwirq: hardware irq number in that domain space
*
* This is a slow path, for use by generic code. It\'s expected that an
* irq controller implementation directly calls the appropriate low level
* mapping function.
*/
unsigned int irq_find_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
unsigned int i;
unsigned int hint = hwirq % nr_irqs;
unsigned int irq;
/* Look for default domain if nececssary */
if (domain == NULL)
domain = irq_default_domain;
if (domain == NULL)
return 0;
/* legacy -> bail early */
if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) {
irq = irq_domain_legacy_revmap(domain, hwirq);
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() hwirq:%lu, irq:%u\\n", __func__, \\
hwirq, irq);
return irq;
}
/* Slow path does a linear search of the map */
if (hint == 0)
hint = 1;
i = hint;
do {
struct irq_data *data = irq_get_irq_data(i);
if (data && (data->domain == domain) && (data->hwirq == hwirq))
return i;
i++;
if (i >= nr_irqs)
i = 1;
} while(i != hint);
return 0;
}
EXPORT_SYMBOL_GPL(irq_find_mapping);
/* kernel/irq/irqdomain.c */
static unsigned int irq_domain_legacy_revmap(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
irq_hw_number_t first_hwirq = domain->revmap_data.legacy.first_hwirq;
int size = domain->revmap_data.legacy.size;
if (hwirq >= IRQ_PHY_GPIOA)
printk("~~~ %s() hwirq:%lu\\n", __func__, hwirq);
if (WARN_ON(hwirq < first_hwirq || hwirq >= first_hwirq + size))
return 0;
return hwirq - first_hwirq + domain->revmap_data.legacy.first_irq;
}
根据hw interrupt 和 irqnr 的映射关系,获取irqnr;
之后call handle_IRQ(irqnr, regs);
之后,and continue,直到所有中断处理完成;
- void handle_IRQ(unsigned int irq, struct pt_regs *regs)
/*
* handle_IRQ handles all hardware IRQ\'s. Decoded IRQs should
* not come via this function. Instead, they should provide their
* own \'handler\'. Used by platform code implementing C-based 1st
* level decoding.
*/
void handle_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);
}
- int generic_handle_irq(unsigned int irq)
/* kernel/irq/irqdesc.c */
/**
* generic_handle_irq - Invoke the handler for a particular irq
* @irq: The irq number to handle
*
*/
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq:%u, irq_desc->irq_data.irq:%u\\n", \\
__func__, irq, irq_desc->irq_data.irq);
if (!desc)
return -EINVAL;
generic_handle_irq_desc(irq, desc);
return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);
- inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
/* include/linux/irqdesc.h */
/*
* Architectures call this to let the generic IRQ layer
* handle an interrupt. If the descriptor is attached to an
* irqchip-style controller then we call the ->handle_irq() handler,
* and it calls __do_IRQ() if it\'s attached to an irqtype-style controller.
*/
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq:%u, irq_desc->irq_data.irq:%u\\n", \\
__func__, irq, irq_desc->irq_data.irq);
desc->handle_irq(irq, desc);
}
struct irq_desc desc = irq_to_desc(irq);
call desc->handle_irq(irq, desc); /call higt level interrupt handle */
gic_init(0, IRQ_GIC_PPI_START, dist_base, cpu_base);
|
void __init gic_init_bases(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base,
u32 percpu_offset, struct device_node *node)
|
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
|
ops->map(domain, irq, hwirq);
/ * call struct irq_domain_ops gic_irq_domain_ops, .map = gic_irq_domain_map
set desc->handle_irq = handle; / * high level irq-events handler */
如果是SPI中断,set desc->handle_irq:handle_fasteoi_irq();
*/
/* arch/arm/mach-s5p6818/gic.c */
...
const struct irq_domain_ops gic_irq_domain_ops = {
.map = gic_irq_domain_map,
.xlate = gic_irq_domain_xlate,
};
...
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
/*printk("~~~ %s() irq:%d, hw:%d, call irq_set_chip_and_handler()\\n", \\
__func__, irq, hw); */
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_set_chip_and_handler(irq, &gic_chip,
handle_percpu_devid_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
} else {
irq_set_chip_and_handler(irq, &gic_chip,
handle_fasteoi_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
}
irq_set_chip_data(irq, d->host_data);
return 0;
}
如果是SPI中断
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc); /* call handle_fasteoi_irq(); */
}
- void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
/* kernel/irq/chip.c */
/**
* handle_fasteoi_irq - irq handler for transparent controllers
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
*
* Only a single callback will be issued to the chip: an ->eoi()
* call when the interrupt has been serviced. This enables support
* for modern forms of interrupt handlers, which handle the flow
* details in hardware, transparently.
*/
void
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq:%d\\n", __func__, irq);
raw_spin_lock(&desc->lock);
if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
if (!irq_check_poll(desc))
goto out;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);
/*
* If its disabled or no action available
* then mask it and get out of here:
*/
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
mask_irq(desc);
goto out;
}
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);
preflow_handler(desc);
handle_irq_event(desc);
if (desc->istate & IRQS_ONESHOT)
cond_unmask_irq(desc);
out_eoi:
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq:%d, call chip->irq_eoi()\\n", \\
__func__, irq);
desc->irq_data.chip->irq_eoi(&desc->irq_data);
out_unlock:
raw_spin_unlock(&desc->lock);
return;
out:
if (!(desc->irq_data.chip->flags & IRQCHIP_EOI_IF_HANDLED))
goto out_eoi;
goto out_unlock;
}
主要有:
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc); /* disable this int */
handle_irq_event(desc); /* handle */
if (desc->istate & IRQS_ONESHOT)
cond_unmask_irq(desc); /* enable this int */ */
desc->irq_data.chip->irq_eoi(&desc->irq_data); /* call irq chip end of interrupt */
可以看到 IRQS_ONESHOT flag的用做处之一;
- static void gic_eoi_irq(struct irq_data *d)
/* arch/arm/mach-s5p6818/gic.c */
...
static struct irq_chip gic_chip = {
.name = "GIC",
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoi_irq,
.irq_set_type = gic_set_type,
.irq_retrigger = gic_retrigger,
#ifdef CONFIG_SMP
.irq_set_affinity = gic_set_affinity,
#endif
.irq_set_wake = gic_set_wake,
};
...
static void gic_eoi_irq(struct irq_data *d)
{
if (d->hwirq >= IRQ_PHY_GPIOA)
printk("~~~ %s() hwirq:%d\\n", __func__, d->hwirq);
if (gic_arch_extn.irq_eoi) {
raw_spin_lock(&irq_controller_lock);
gic_arch_extn.irq_eoi(d);
raw_spin_unlock(&irq_controller_lock);
}
writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI);
}
- irqreturn_t handle_irq_event(struct irq_desc *desc)
/* */
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;
if (desc->irq_data.irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq:%d, name:%s, irq_chip:%s\\n", \\
__func__, desc->irq_data.irq, desc->name, \\
(desc->irq_data.chip)->name);
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc, action);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
- irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq:%d, name:%s\\n", __func__, irq, desc->name);
do {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() after call action->handler(), name:%s, res:%d\\n", \\
__func__, action->name, res);
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
if (irq >= IRQ_PHY_GPIOA)
printk("~~~ %s() irq_wake_thread\\n", \\
__func__);
irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
flags |= action->flags;
break;
default:
break;
}
retval |= res;
action = action->next;
} while (action);
add_interrupt_randomness(irq, flags);
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
终于到达:
res = action->handler(irq, action->dev_id);
review:
linux arm irq (1)
- ARM SOC(GIC)发生中断主要处理流程(精简描述):
- 进入irq模式,切换到svc模式,保存现场;
- 读取GIC irq chip 获取是哪个硬件中断number,这个Hw int number是SOC硬件设计确定的;
对于GIC Share Peripheral Interrupt(SPI),一般是一个soc controller对应一个Hw int number; - 找到这个Hw int 对应的interrupt descriptor,(struct irq_desc);call 其handle_irq(high level irq-events handler);
handle_irq中call action->handler();
PS:这个Hw int如果是一个irq chip,(这个中断chained若干个中断,可以是int controller或一个GPIO控制器连接若干个IO),这个中断的handle_irq(),(high level irq-events handler)中就需要去读取这个irq chip 的寄存器,确定Hw init number;再走lable 3流程; - 执行GIC 的end of irq operations;(action->handler()中有clear irq(operate controller))
- b ret_to_user_from_irq or svc_exit r5;
以上是关于linux arm irq : interrupt handling的主要内容,如果未能解决你的问题,请参考以下文章
linux arm irq : interrupt driver interface