Exception -1- (ARM32 Vector)

<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를 처리하지 않는다.

 

모드별 Exception 벡터 테이블

다음 그림은 각 모드별 ARM Exception Vector 테이블을 보여준다.

  • 붉은 박스가 하이퍼 모드를 사용하지 않을 때의 리눅스 커널이 동작하는 벡터 테이블을 보여준다.

 

Exception 벡터 테이블 주소

벡터 테이블의 주소는 arm 모드 및 Security Extension 사용 유무에 따라 각각 다르다.

  • Security Extension 미사용
    • low 벡터 주소
      • SCTLR.V=0
      • 0x0000_0000
    • high 벡터 주소
      • SCTLR.V=1
      • 0xffff_0000
  • Security Extension 사용
    • non-secure 및 secure 공통
      • VBAR 지정한 주소
        • SCTLR.V=0
      • high 벡터 주소
        • SCTLR.V=1
    • Monitor
      • MVBAR 지정한 주소
  • 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로 복사한다.

 

.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

 

참고

 

댓글 남기기