linux arm irq (2): interrupt handling
2021/5/17 7:25:59
本文主要是介绍linux arm irq (2): 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 (2): interrupt handling的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-18git仓库有更新,jenkins 自动触发拉代码怎么配置的?-icode9专业技术文章分享
- 2024-12-18Jenkins webhook 方式怎么配置指定的分支?-icode9专业技术文章分享
- 2024-12-13Linux C++项目实战入门教程
- 2024-12-13Linux C++编程项目实战入门教程
- 2024-12-11Linux部署Scrapy教程:新手入门指南
- 2024-12-11怎么将在本地创建的 Maven 仓库迁移到 Linux 服务器上?-icode9专业技术文章分享
- 2024-12-10Linux常用命令
- 2024-12-06谁看谁服! Linux 创始人对于进程和线程的理解是…
- 2024-12-04操作系统教程:新手入门及初级技巧详解
- 2024-12-04操作系统入门:新手必学指南