1. Exception

entry.S head.S 같은 경로에 존재하며 arch/arm64/kernel/entry.S이다.

entry.S 에서는 Exception 처리에 관한 부분을 정의한다. arm에는 User, Knerel 여러 모드가 존재하고 각각의 모드는 Level 나누어 ELn으로 나타낸다. 이러한 EL간의 이동은 자유롭지 않으며 특정 이벤트를 발생시켜 넘어갈 있다. 이러한 부분에 대한 정의를 진행하는 곳이 entry.S이다.

 

커널에 진입하기 위해서는 Exception, system call등을 이용하며 EL0 -> EL1 등으로 EL 이동할 있다. 참고로 EL2, EL3 마찬가지이며 EL1, EL2, EL3 공통포맷을가지며 유저모드인 EL0 다른 포맷을 유지한다.

정리하면 특정 EL에서 각각의 Level 이동하기 위해서는 Exception, system call등의 이벤트가 발생해야 한다. 그리고 이러한 것들을 진행하면 각각의 레벨에 따른 Exception Table 주소로 이동하게 된다. 주소로 이동하면 핸들러가 구현되어있는 주소를 가리키고 있으며 해당 핸들러 주소로 이동하여 동작을 수행한다.

 

EL 따른 base address 존재하며 이러한 레벨에 따른 base address 저장하고 있는 레지스터가 VBAR_ELn이다. 예를들어 head.S에서 정의한 VBAR_EL1 레지스터에는 EL1 대한 Exception Table 접근하기위한 EL1레벨의 base address 저장되어있다.

 

1) Exception vector table

Address Exception type Description
VBAR_ELn + 0x000 Synchronous Current EL with SP0
+ 0x080 IRQ/vIRQ  
+ 0x100 FIQ/vFIQ  
+ 0x180 SError/vSError  
+ 0x200 Synchronous Current EL with SPx
+ 0x280 IRQ/vIRQ  
+ 0x300 FIQ/vFIQ  
+ 0x380 SError/vSError  
+ 0x400 Synchronous Lower EL using AArch64
+ 0x480 IRQ/vIRQ  
+ 0x500 FIQ/vFIQ  
+ 0x580 SError/vSError  
+ 0x600 Synchronous Lower EL using AArch32
+ 0x680 IRQ/vIRQ  
+ 0x700 FIQ/vFIQ  
+ 0x780 SError/vSError  

 

그리고 위와 같이 Exception vector table 주소가 base address기준으로 정의되어 있다.

정의 내용을보면 가장 4개는 현재 Exception Level에서 Exception 발생했고 SP_EL0 사용하는 경우 IRQ, FIQ, SError 따른 분기할 주소를 나타낸다.

다음 4개는 현재 Exception Level에서 발생했고 SP_El1 사용하는 경우

다음 4개는 지금 Level보다 낮은 Exception Level에서 발생했고 64bit 경우

마지막 4개는 지금 Level 보다 낮은 Exception Level에서 발생했고 32bit 경우이다.

 

여기서 SP_ELn이란 Stack pointer 이며 Arm8v에서는 EL0~EL3까지 Exceptio Level에서 각각의 Stack pointer 따로존재하고 각각의 SP값을 SP_EL0~SP_EL3 저장한다. 마찬가지로 위에서 언급한바와 같이 base Level VBAR_ELn으로 Level 따라 따로 관리하며 마지막으로 Exception처리 복귀할 주소를 저장하는 ELR Register Exception Level별로 각각 두고 있다. (Arm7v에서는 X30하나로 ARMv8 ELR 대체한다)

 

/* * Exception vectors. */
.pushsection".entry.text","ax"
.align11
SYM_CODE_START(vectors)
kernel_ventry1,t,64,sync// Synchronous EL1t
kernel_ventry1,t,64,irq// IRQ EL1t
kernel_ventry1,t,64,fiq// FIQ EL1t
kernel_ventry1,t,64,error// Error EL1t


kernel_ventry1,h,64,sync// Synchronous EL1h
kernel_ventry1,h,64,irq// IRQ EL1h
kernel_ventry1,h,64,fiq// FIQ EL1h
kernel_ventry1,h,64,error// Error EL1h


kernel_ventry0,t,64,sync// Synchronous 64-bit EL0
kernel_ventry0,t,64,irq// IRQ 64-bit EL0
kernel_ventry0,t,64,fiq// FIQ 64-bit EL0
kernel_ventry0,t,64,error// Error 64-bit EL0


kernel_ventry0,t,32,sync// Synchronous 32-bit EL0
kernel_ventry0,t,32,irq// IRQ 32-bit EL0
kernel_ventry0,t,32,fiq// FIQ 32-bit EL0
kernel_ventry0,t,32,error// Error 32-bit EL0
SYM_CODE_END(vectors)

그리고 위의 vector table format 직접적으로 메모리에 코딩한 부분이 entry.S 코드로 보면 된다. 포맷의 EL1 EL2, EL3 같은 포맷으로 적용된다.

가장 먼저 kernel_ventry macro el, ht, regsize, label 정의되어있다.

예를들어 kernel_ventry 1,h,64,sync EL1, h, 64 size, sync label 정의하는것과 같은 의미를 지닌다. h 아마 낮지않은 Exception Level에서 Exception 발생하였음을 말하고 t 현재보다 낮은 Exception Level에서 Exception 발생하였음을 의미하는 같다.(?)

 

2) Exception 발생 이전 data 처리

Exception 발생하게 되면 복귀 동작을 위해 공용으로 사용하는 레지스터의 값들을 미리 저장해둔다. 사용하는 매크로는 kernel_entry이다.

kernel_entry exception발생 가장 처음 무조건 들어가는 코드이며 이전에 사용하던 SP, PC, PSTATE값을 다음 레지스터에 저장한다.

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

마찬가지로 Exceptio처리가 종료되면 x21, x22, x23 저장된 값들을 다시 write해주어야 하는데 해당작업을 위한 매크로가 kernel_exit이다.

 

3) Exception handler

다시 위에서 정의한 Exception vector table 보면 vector table에는 kernel_ventry 매크로가 정의되어있다 낮은 Exception혹은 Current Exception에서 Exception 발생하고 어떤 SP_EL 레지스터를 사용하는지 그리고 SError인지 IRQ인지 Sync인지 등에 따라서 구분되어 현재 상황에 맞는 kernel_ventry 실행된다. 이렇게 실행된 kernel_ventry 매크로 정의부를 살펴보면

b  el\el\ht\()_\regsize\()_\label

위와 같이 jump하게 되고

jump 주소(Exception handler매크로) 위와 같은 명령을 실행한다. 가장 먼저 kernel_entry Exception level값과 regsize값을 전달해주어 SP, PSTATE 값들을 x21, x22, x23, x20 레지스터에 저장하는 작업을 진행하고 실제적으로 handler함수를 호출한다. 호출 문구를 kernel_ventry1,h,64,irq// IRQ EL1h

해석하면 bl el1h_64_irq_handler 명령어가 되고 이는 entry-common.c 있는 아래 handler function 호출한다.

 

  • el1h_64_sync_handler()
asmlinkage voidnoinstr el1h_64_sync_handler(structpt_regs *regs)
{
        unsignedlongesr = read_sysreg(esr_el1);
        switch(ESR_ELx_EC(esr)) {
        caseESR_ELx_EC_DABT_CUR:
        caseESR_ELx_EC_IABT_CUR:
                el1_abort(regs, esr);
                break;
        /*
         * We don't handle ESR_ELx_EC_SP_ALIGN, since we will have hit a
         * recursive exception when trying to push the initial pt_regs.
         */
        caseESR_ELx_EC_PC_ALIGN:
                el1_pc(regs, esr);
                break;
        caseESR_ELx_EC_SYS64:
        caseESR_ELx_EC_UNKNOWN:
                el1_undef(regs);
                break;
        caseESR_ELx_EC_BREAKPT_CUR:
        caseESR_ELx_EC_SOFTSTP_CUR:
        caseESR_ELx_EC_WATCHPT_CUR:
        caseESR_ELx_EC_BRK64:
                el1_dbg(regs, esr);
                break;
        caseESR_ELx_EC_FPAC:
                el1_fpac(regs, esr);
                break;
        default:
                __panic_unhandled(regs, "64-bit el1h sync", esr);
        }
}

가장먼저 ESR 레지스터 값을 가져온다 ESR 레지스터는 Exception 발생 이유를 나타내며 Sync Exception 때만 정보가 제공된다.

때문에 Sync에서는 ESR 레지스터의 값을 얻어온 Switch문을 통해서 Exception 원인에 따른 처리과정을 보여준다.

el1_abort, el1_pc, el1_undef, el_dbg, el1_fpac 각각의 원인에따른 signal sending or Error 로그와함께 system die등의 결과를 나타낸다.

코드들에 대한 정의는

arch/arm64/kernel/traps.c

arch/arm64/kernel/fault.c

에서 확인이 가능하다.

 

  • el1h_64_irq_handler()
asmlinkage voidnoinstr el1h_64_irq_handler(structpt_regs *regs)
{
        el1_interrupt(regs, handle_arch_irq);
}

위와 같이 handler 함수를 실행하는데 handler함수의 주소값은 미리 handle_arch_irq 저장되어있고 값을 불러와서 호출한다.

해당 값은 device driver 초기화를 진행할 set_handle_irq() 호출해서 주소 값을 handle_arch_irq 저장한다. 보통의 경우 인터럽트 핸들러는 generic_handle_irq() 호출하여 등록된 ISR 수행한다. 해당 등록과정은 device tree 연관이 되어있을 같다(?)

참고로 FIQ 역시 IRQ 동일하게 동작한다. 다만 핸들러가 handle_arch_fiq 차이가 있다.

 

  • el1h_64_error_handler()
asmlinkage voidnoinstr el1h_64_error_handler(structpt_regs *regs)
{
        unsignedlongesr = read_sysreg(esr_el1);


        local_daif_restore(DAIF_ERRCTX);
        arm64_enter_nmi(regs);
        do_serror(regs, esr);
        arm64_exit_nmi(regs);
}

error 경우 do_serror function내부로 들어가면 arch/arm64/kernel/traps.c내부에 존재하는 function 통해 panic 발생한다.

 

  • el0t_64_sync_handler()

해당 핸들러는 EL0에서 실행되었으며 SVC모드를 통한 커널 모드로 진입하는 system call 과정을 나타낸다. 위에서 언급한 내용과 마찬가지로 EL0에서 Exception 발생하여 function 호출되면 ESR 레지스터를 읽고 ESR_Elx_EC_SVC64 값을 가지고 있는지 체크 한다. 해당 값이 가지고 있으면 User mode에서 Exception Level 상승을위한 동작이라고 생각하면 된다.

asmlinkage voidnoinstr el0t_64_sync_handler(structpt_regs *regs)
{
        unsignedlongesr = read_sysreg(esr_el1);


        switch(ESR_ELx_EC(esr)) {
        caseESR_ELx_EC_SVC64:
                el0_svc(regs);
                break;
...

}

위와 같이 switch문을통해서 el0_svc() 호출하게되고

staticvoidnoinstr el0_svc(structpt_regs *regs)
{
        enter_from_user_mode(regs);
        cortex_a76_erratum_1463225_svc_handler();
        do_el0_svc(regs);
        exit_to_user_mode(regs);
}

do_el0_svc() function 통해 arch/arm64/kernel/syscall.c 존재하는 el0_svc_common() 호출 결과적으로 invoke_syscall() function 통해서 kernel모드로 진입을 시도한다.

 

 

 

  • ref

https://elixir.bootlin.com/linux/v5.14.16/source/arch/arm64/kernel/entry.S

https://elixir.bootlin.com/linux/v5.14.16/source/

https://developer.arm.com/documentation/100933/0100/AArch64-exception-vector-table

http://egloos.zum.com/rousalome/v/10033656

https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/entry-common.c

+ Recent posts