The interruption process is a clich é, but I only know that after the interruption, I will protect the site, jump to the interruption vector table, execute the interruption, restore the site, and then return. As for more details, we don't know. This article aims to sort out a more complete linux interrupt processing flow. The main questions are as follows:
- Where is the interrupt vector table configured in the code
- When responding to an interrupt, there must be some operations to read and write the gic register. Where are these operations
- /How is the proc file proc/interrupts implemented
- Before entering the interrupt, linux will shield all other interrupts. How is this realized
- How does the system interrupt 0x80 of system call change from user mode to kernel mode_ Operations interface? What's the difference between an interrupt and a normal interrupt
The code of this article is from Linux 4.0 nineteen
Configure interrupt vector table
From arch / arm64 / kernel / vmlinux lds. As you can see in s, the kernel ENTRY is_ text,
/*arch/arm64/kernel/vmlinux.lds.S*/ ENTRY(_text) ... .head.text : { _text = .; HEAD_TEXT }
HEAD_TEXT is defined in the following position
/*include/asm-generic/vmlinux.lds.h*/ /* Section used for early init (in .S files) */ #define HEAD_TEXT KEEP(*(.head.text))
That is to say, the entry of linux is designated as head. Text sensing code, grep can find
/*include/linux/init.h*/ #define __HEAD .section ".head.text","ax"
__ The HEAD macro is only used in the following locations
/*arch/arm64/kernel/head.S*/ __HEAD _head: ... b stext ... ENTRY(stext) ... b __primary_switch ENDPROC(stext) __primary_switched: ... adr_l x8, vectors // load VBAR_EL1 with virtual msr vbar_el1, x8 // vector table address isb ... b start_kernel ENDPROC(__primary_switched)
From the above code, you can see the complete process from the linux entry to setting the vector table. Finally, the instruction to set the vector table is to write the address of vector into vbar_el1 register.
For vbar_ The function of the el1 register, Description of arm official documents as follows
For other vbars_ The writing of ELX is similar and will not be repeated.
Detailed explanation of interrupt vector table
/*arch/arm64/kernel/entry.S*/ ENTRY(vectors) ... kernel_ventry 1, sync // Synchronous EL1h kernel_ventry 1, irq // IRQ EL1h kernel_ventry 1, fiq_invalid // FIQ EL1h kernel_ventry 1, error // Error EL1h kernel_ventry 0, sync // Synchronous 64-bit EL0 kernel_ventry 0, irq // IRQ 64-bit EL0 kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0 kernel_ventry 0, error // Error 64-bit EL0 ...
kernel_ The ventry macro definition (ignoring all #ifdef) is as follows:
.macro kernel_ventry, el, label, regsize = 64 .align 7 sub sp, sp, #S_FRAME_SIZE b el\()\el\()_\label .endm
You can see that "kernel_venture 1, IRQ" finally jumps to el1_irq to execute
Field protection and interrupt stack switching
el1_irq: kernel_entry 1 /*Save the site*/ enable_da_f (msr daifclr, #(8 | 4 | 1)) / * shield D (Debug mask bit) A (SError mask bit) I (IRQ mask bit) f (FIQ mask bit.) DAF in*/ irq_handler /*Enter interrupt processing*/ kernel_exit 1 /*Restore the site*/ ENDPROC(el1_irq)
The preservation of the scene is rather lengthy and has been put at the end of the text.
Then the DAF in the DAIF is shielded. It seems that you don't want to be interrupted by DAF when dealing with irq. Why not explore it for the time being...
Then it starts to enter the interrupt processing irq_handler. After kernel processing_ Exit restore the scene.
irq_ The handler code is as follows:,
/* * Interrupt handling. */ .macro irq_handler ldr_l x1, handle_arch_irq mov x0, sp irq_stack_entry blr x1 irq_stack_exit .endm
Note that you are jumping to handle_ arch_ Use IRQ before and after irq_stack_entry and irq_stack_exit performs stack pointer switching. irq_ stack_ The entry code is as follows
.macro irq_stack_entry mov x19, sp // Save the current stack pointer sp to x19 in IRQ_ stack_ When exit recovers the stack pointer, take it out of x19. Question: how to ensure that the value of x19 is not overwritten in the interrupt code? /* * Compare sp to the cardinality of the task stack. If the top ~ (THREAD_SIZE - 1) bits match, we are on the task stack, * And you should switch to the irq stack. */ ldr x25, [tsk, TSK_STACK] eor x25, x25, x19 and x25, x25, #~(THREAD_SIZE - 1) cbnz x25, 9998f ldr_this_cpu x25, irq_stack_ptr, x26 /*Read the current cpu irq stack pointer*/ mov x26, #IRQ_STACK_SIZE add x26, x25, x26 /*Reserve irq stack space, that is, the stack usage of irq cannot exceed IRQ_STACK_SIZE*/ /* switch to the irq stack */ mov sp, x26 9998: .endm
handle_arch_irq detailed explanation
From here, you can fully enter the c code. Look at handle first_ arch_ How did IRQ come from.
/*kernel/irq/handle.c*/ void (*handle_arch_irq)(struct pt_regs *) __ro_after_init; /*A function pointer is defined here*/ IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init); gic_of_init gic_init_bases set_handle_irq(handle_irq=gic_handle_irq) handle_arch_irq = handle_irq
gic_handle_irq
Attachment: kernel_ Detailed explanation of entry protection site
.macro kernel_entry, el, regsize = 64 .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 clear_gp_regs mrs x21, sp_el0 ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. apply_ssbd 1, x22, x23 .else add x21, sp, #S_FRAME_SIZE get_thread_info tsk /* Save the task's original addr_limit and set USER_DS */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #USER_DS str x20, [tsk, #TSK_TI_ADDR_LIMIT] /* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */ .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp lr, x21, [sp, #S_LR] /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME #ifdef CONFIG_ARM64_SW_TTBR0_PAN /* * Set the TTBR0 PAN bit in SPSR. When the exception is taken from * EL0, there is no need to check the state of TTBR0_EL1 since * accesses are always enabled. * Note that the meaning of this bit differs from the ARMv8.1 PAN * feature as all TTBR0_EL1 accesses are disabled, not just those to * user mappings. */ alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #TTBR_ASID_MASK // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif stp x22, x23, [sp, #S_PC] /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif /* * Registers that may be useful after this macro is invoked: * * x21 - aborted SP * x22 - aborted PC * x23 - aborted PSTATE */ .endm
reference material
1.DDI0487D_b_armv8_arm.pdf
2.DEN0024A_v8_architecture_PG.pdf
3. https://www.jianshu.com/p/a9b5bc27ec83