<kernel v5.4>
ARM32 Exception Vector
non-secure PL1에서 동작하는 리눅스 커널 위주로 벡터테이블을 설명한다.
특징
- irq 처리
- irq 처리 중 fiq 처리는 가능하지만 irq 재진입(irq preemption)은 허용하지 않는다.
- ARM32 아키텍처는 irq preemption을 허용하지만 ARM 리눅스 커널이 이를 허용하지 않는 설계로 구현되어 있다.
- ARM64 아키텍처는 특수한 Pesudo-NMI를 사용하는 시스템에서만 nmi 관련 디바이스에 한해 irq preemption을 허용한다.
- fiq 처리
- 리눅스 커널의 fiq 핸들러 함수인 handle_fiq_as_nmi()에 처리 코드를 추가하여 사용하여야 한다.
- 디폴트 처리로 아무것도 하지 않는다. (nop)
- 예) rpi2의 경우 usb 호스트 드라이버(dwc_otg)에서 사용한다.
- 일반적으로 secure 펌웨어에서 fiq를 처리하고, 리눅스 커널에서는 fiq를 처리하지 않는다.
- 리눅스 커널의 fiq 핸들러 함수인 handle_fiq_as_nmi()에 처리 코드를 추가하여 사용하여야 한다.
모드별 Exception 벡터 테이블
다음 그림은 각 모드별 ARM Exception Vector 테이블을 보여준다.
- 붉은 박스가 하이퍼 모드를 사용하지 않을 때의 리눅스 커널이 동작하는 벡터 테이블을 보여준다.
Exception 벡터 테이블 주소
벡터 테이블의 주소는 arm 모드 및 Security Extension 사용 유무에 따라 각각 다르다.
- Security Extension 미사용
- low 벡터 주소
- SCTLR.V=0
- 0x0000_0000
- high 벡터 주소
- SCTLR.V=1
- 0xffff_0000
- low 벡터 주소
- Security Extension 사용
- non-secure 및 secure 공통
- VBAR 지정한 주소
- SCTLR.V=0
- high 벡터 주소
- SCTLR.V=1
- VBAR 지정한 주소
- Monitor
- MVBAR 지정한 주소
- non-secure 및 secure 공통
- Hyper Extensino 사용
- HVBAR 지정한 주소
Exception 시 CPU 모드별 스택
- irq, fiq, abt(pabt & babt), und
- ARM32 리눅스 커널은 위의 exception이 발생하는 경우 각각 3 워드 초소형 스택을 사용하여 돌아갈 주소등을 저장하고, SVC 모드로 전환한 후 커널 스택을 사용한다.
- svc
- 커널 스택
- 커널 스레드 동작 중에 exception 발생한 경우 부트업 시 생성한 커널용 스택을 사용한다.
- 커널 스택
- usr
- 유저용 커널 스택
- 유저 process가 동작 중이었으면 유저 process마다 유저 스택과 커널 스택이 생성되며, exception이 발생하는 경우 동작 중인 유저 process용 커널 스택을 사용한다.
- 유저용 커널 스택
Exception Stub 흐름
ARM에서 Exception이 발생하면 CPU는 지정된 벡터테이블(low/high)로 강제 jump되고 해당 엔트리에는 각 exception을 처리할 수 있는 코드로의 branch 코드가 수행된다.
아래 그림은 8개의 exception vector entry 중 5개 엔트리의 경우 각각 16개의 모드 테이블을 갖고 있고 exception 되기 전의 모드를 인덱스로 하여 해당 루틴으로 이동하는 것을 보여준다.
- stub 코드들은 벡터로 부터 1 페이지 아래 위치한 주소에 복사되어 사용된다.
Vector 선언
__vectors_start
arch/arm/kernel/entry-armv.S
.section .vectors, "ax", %progbits .L__vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, __vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq
- 8개의 exception vector 엔트리에는 각각을 처리할 수 있는 stub 코드로 이동할 수 있는 branch 코드로 되어 있다.
- 3번 째 엔트리의 경우 SVC 엔트리로 syscall을 담당한다. 벡터 위치+0x1000에 담겨있는 주소로 이동하는 코드가 있는데 해당 위치는 vector_swi 변수 주소를 담고 있다.
Vector가 저장되는 섹션 위치
arch/arm/kernel/vmlinux.lds.S
#ifdef CONFIG_STRICT_KERNEL_RWX . = ALIGN(1<<SECTION_SHIFT); #else . = ALIGN(PAGE_SIZE); #endif __init_begin = .; ARM_VECTORS INIT_TEXT_SECTION(8) .exit.text : { ARM_EXIT_KEEP(EXIT_TEXT) } .init.proc.info : { ARM_CPU_DISCARD(PROC_INFO) } .init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; } .init.tagtable : { __tagtable_begin = .; *(.taglist.init) __tagtable_end = .; } #ifdef CONFIG_SMP_ON_UP .init.smpalt : { __smpalt_begin = .; *(.alt.smp.init) __smpalt_end = .; } #endif .init.pv_table : { __pv_table_begin = .; *(.pv_table) __pv_table_end = .; } INIT_DATA_SECTION(16)
- __init_begin 바로 다음에 ARM_VECTORS 매크로가 위치한 것을 확인할 수 있다.
arch/arm/kernel/vmlinux.lds.h
/* * The vectors and stubs are relocatable code, and the * only thing that matters is their relative offsets */
#define ARM_VECTORS \ __vectors_start = .; \ .vectors 0xffff0000 : AT(__vectors_start) { \ *(.vectors) \ } \ . = __vectors_start + SIZEOF(.vectors); \ __vectors_end = .; \ \ __stubs_start = .; \ .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { \ *(.stubs) \ } \ . = __stubs_start + SIZEOF(.stubs); \ __stubs_end = .; \ \ PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));
- 0xffff_0000 주소로 시작하는 __vectors_start ~ __vectors_end 사이의 .vectors 섹션에 벡터가 포함됨을 알 수 있다.
- 벡터 + 0x1000_0000 주소로 시작하는 __stubs_start ~ __stubs_end 사이의 .stubs 섹션에 syscall 관련 코드가 포함된다.
Vector 설치
exception 벡터 및 stub 코드들은 아래의 루틴에서 설치된다.
- paging_init()->devicemaps_init()->early_trap_init()
Vector 핸들러
vector_stub 매크로
arch/arm/kernel/entry-armv.S
/* * Vector stubs. * * This code is copied to 0xffff1000 so we can use branches in the * vectors, rather than ldr's. Note that this code must not exceed * a page size. * * 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) .align 2 @ handler addresses follow this label 1: .endm
Exception이 발생되면 8개의 exception vector 엔트리 중 해당하는 exception 위치로 jump 되는데 이 중 5개의 exception 엔트리에서 사용되는 공통 매크로 루틴에서는 exception 벡터로 점프되기 직전의 최대 16개 모드별 테이블에 해당하는 루틴을 찾아 수행한다. 수행 전에 svc 모드로 변환한다.
- correction은 루틴이 수행된 후 다시 리턴될 주소를 조정한다. exception이 발생될 때의 파이프라인 위치에 따라 보정 주소가 다르다.
- IRQ, FIQ, Data Abort의 경우 리턴 주소-4로 보정한다.
- Prefetch Abort의 경우 리턴 주소-8로 보정한다.
- Undefined 및 SWI의 경우 보정 없이 리턴 주소를 사용한다.
- Reset은 리턴하지 않으므로 해당사항 없다.
- irq, fiq, abt, und 모드에서 사용하는 스택
- cpu_init() 함수에서 3 word 초소형 static 배열을 4개의 스택으로 설정하였다.
- 이 스택에는 다음 3가지 항목을 보관한다.
- scratch되어 파괴될 r0 레지스터
- 되돌아갈 주소가 담긴 lr 레지스터(exception 시 pc -> lr_<exception>에 복사되는데 이 값에 correction 값을 보정하여 저장해둔다.)
- 복원되어야 할 psr 레지스터 (exception 시 cpsr -> spsr_<exception>에 복사되는데 이 값을 저장해둔다.)
- ldm/stm에서 사용하는 ^ 접미사 용도
- stm
- user 모드의 sp, lr 레지스터가 뱅크되어 일반적으로는 다른 모드에서 접근할 수 없어서 user 모드의 sp, lr 레지스터 값을 access하고자할 때 사용
- ldm
- pc가 포함된 경우 spsr->cpsr로 복사된다
- 기존 모드로 복귀할 때 spsr(백업받아 두었던) -> cpsr로 복사한다.
- stm
.stubs 섹션의 시작
arch/arm/kernel/entry-armv.S
.section .stubs, "ax", %progbits __stubs_start: @ This must be the first word .word vector_swi
stub 영역은 시스템 콜이 호출되는 vector_swi 부터 시작하여 다음과 같은 순으로 위치한다.
- vector_swi
- vector_rst
- vector_irq
- vector_dabt
- vector_pabt
- vector_und
- vector_addrexcptn
- vector_fiq
vector_rst (Reset)
arch/arm/kernel/entry-armv.S
vector_rst: ARM( swi SYS_ERROR0 ) THUMB( svc #0 ) THUMB( nop ) b vector_und
SYS_ERROR0 소프트웨어 인터럽트를 발생시켜 시스템 콜을 호출하게 한다.
vector_irq (IRQ)
arch/arm/kernel/entry-armv.S
/* * 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
- USR 모드에서 인터럽트가 발생한 경우 __irq_usr 루틴을 호출한다.
- FIQ 및 IRQ 모드에서 인터럽트가 발생한 경우 __irq_invalid 루틴을 호출한다.
- SVC 모드에서 인터럽트가 발생한 경우 __irq_svc 루틴을 호출한다.
vector_dabt (Data Abort)
arch/arm/kernel/entry-armv.S
/* * 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
vector_pabt (Prefetch Abort)
arch/arm/kernel/entry-armv.S
/* * 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
vector_und (Undefined Instruction)
arch/arm/kernel/entry-armv.S
/* * 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
vector_addrexcptn (Address Exception Handler)
arch/arm/kernel/entry-armv.S
/*============================================================================= * Address exception handler *----------------------------------------------------------------------------- * These aren't too critical. * (they're not supposed to happen, and won't happen in 32-bit data mode). */
vector_addrexcptn: b vector_addrexcptn
vector_fiq (FIQ)
arch/arm/kernel/entry-armv.S
/*============================================================================= * FIQ "NMI" handler *----------------------------------------------------------------------------- * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 * systems. */
vector_stub fiq, FIQ_MODE, 4 .long __fiq_usr @ 0 (USR_26 / USR_32) .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) .long __fiq_svc @ 3 (SVC_26 / SVC_32) .long __fiq_svc @ 4 .long __fiq_svc @ 5 .long __fiq_svc @ 6 .long __fiq_abt @ 7 .long __fiq_svc @ 8 .long __fiq_svc @ 9 .long __fiq_svc @ a .long __fiq_svc @ b .long __fiq_svc @ c .long __fiq_svc @ d .long __fiq_svc @ e .long __fiq_svc @ f .globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq
참고
- Exception -1- (ARM32 Vector) | 문c – 현재 글
- Exception -2- (ARM32 Handler 1) | 문c
- Exception -3- (ARM32 Handler 2) | 문c
- Exception -4- (ARM32 VFP & FPE) | 문c
- Exception -5- (Extable) | 문c
- Exception -6- (MM Fault Handler) | 문c