linux arm64 interrupt processing flow (from Pangu) (to be continued)

Posted by brynjar on Thu, 03 Mar 2022 02:18:31 +0100

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:

  1. Where is the interrupt vector table configured in the code
  2. When responding to an interrupt, there must be some operations to read and write the gic register. Where are these operations
  3. /How is the proc file proc/interrupts implemented
  4. Before entering the interrupt, linux will shield all other interrupts. How is this realized
  5. 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,

	.head.text : {
		_text = .;

HEAD_TEXT is defined in the following position

/* 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

#define __HEAD      .section    ".head.text","ax"

__ The HEAD macro is only used in the following locations

	b	stext
	b	__primary_switch

	adr_l	x8, vectors			// load VBAR_EL1 with virtual
	msr	vbar_el1, x8			// vector table address
		b	start_kernel

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

	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

You can see that "kernel_venture 1, IRQ" finally jumps to el1_irq to execute

Field protection and interrupt stack switching

	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*/

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
	blr	x1

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

handle_arch_irq detailed explanation

From here, you can fully enter the c code. Look at handle first_ arch_ How did IRQ come from.

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);
				handle_arch_irq = 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
	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
	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

	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]
	stp	x29, x22, [sp, #S_STACKFRAME]
	add	x29, sp, #S_STACKFRAME

	 * 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

	.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

	__uaccess_ttbr0_disable x21

	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]

	 * Set sp_el0 to current thread_info.
	.if	\el == 0
	msr	sp_el0, tsk

	 * Registers that may be useful after this macro is invoked:
	 * x21 - aborted SP
	 * x22 - aborted PC
	 * x23 - aborted PSTATE

reference material


Topics: Linux arm64