저번 포스트에서는 el1_sync 이라는 익셉션 벡터 코드를 분석했습니다.
생각보다 코드의 내용이 복잡하다라는 사실을 알 수 있었는데요. 주요 동작은 다음과 같습니다.
● 신드롬 레지스터를 읽음
● 신드롬 레지스터의 값을 26만큼 왼쪽으로 비트 시프트한 값에 따라 다음 레이블을 호출
** el1_da
** el1_sp_pc
** el1_undef
이번에는 리눅스 커널 드라이버가 실행되는 도중 인터럽트가 발생하면 ARM 프로세서 입장에서 어떤 게 처리되는지 알아보고, 이어서 el1_irq 레이블을 분석하겠습니다.
EL1에서 IRQ 익셉션 벡터 코드
익셉션 벡터 테이블의 시작 주소 기준으로(VBAR_ELn + 0x280) 바이트에 Synchronous 익셉션의 코드가 위치합니다.
이번 포스트에서 분석할 익셉션 벡터 테이블의 시작 주소는 ffffff8008082000이므로,
EL1에서 IRQ 익셉션이 발생하면 다음 코드 기준으로 ffffff8008082280 주소로 프로그램 카운터가 브랜치합니다.
ffffff8008082280: d10503ff sub sp, sp, #0x140
ffffff8008082284: 8b2063ff add sp, sp, x0
ffffff8008082288: cb2063e0 sub x0, sp, x0
ffffff800808228c: 37700080 tbnz w0, #14, ffffff800808229c <vectors+0x29c>
ffffff8008082290: cb2063e0 sub x0, sp, x0
ffffff8008082294: cb2063ff sub sp, sp, x0
ffffff8008082298: 1400042a b ffffff8008083340 <el1_irq>
ffffff800808229c: d51bd040 msr tpidr_el0, x0
ffffff80080822a0: cb2063e0 sub x0, sp, x0
ffffff80080822a4: d51bd060 msr tpidrro_el0, x0
ffffff80080822a8: d0006060 adrp x0, ffffff8008c90000 <overflow_stack+0xd50>
ffffff80080822ac: 910ac01f add sp, x0, #0x2b0
ffffff80080822b0: d538d080 mrs x0, tpidr_el1
ffffff80080822b4: 8b2063ff add sp, sp, x0
ffffff80080822b8: d53bd040 mrs x0, tpidr_el0
ffffff80080822bc: cb2063e0 sub x0, sp, x0
ffffff80080822c0: f274cc1f tst x0, #0xfffffffffffff000
ffffff80080822c4: 540028a1 b.ne ffffff80080827d8 <__bad_stack>
ffffff80080822c8: cb2063ff sub sp, sp, x0
ffffff80080822cc: d53bd060 mrs x0, tpidrro_el0
ffffff80080822d0: 1400041c b ffffff8008083340 <el1_irq>
ffffff80080822d4: d503201f nop
ffffff80080822d8: d503201f nop
ffffff80080822dc: d503201f nop
위 코드의 핵심은 "예외 처리 루틴을 체크한 다음에 el1_irq 레이블로 브랜치하는 동작"입니다.
el1_irq 레이블의 코드 분석
다음은 el1_irq 레이블 코드의 구현부입니다.
https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/entry.S
01 el1_irq:
02 kernel_entry 1
03 gic_prio_irq_setup pmr=x20, tmp=x1
04 enable_da_f
05
06 #ifdef CONFIG_ARM64_PSEUDO_NMI
07 test_irqs_unmasked res=x0, pmr=x20
08 cbz x0, 1f
09 bl asm_nmi_enter
10 1:
11 #endif
12
13 #ifdef CONFIG_TRACE_IRQFLAGS
14 bl trace_hardirqs_off
15 #endif
16
17 irq_handler
18
19 #ifdef CONFIG_PREEMPT
20 ldr x24, [tsk, #TSK_TI_PREEMPT] // get preempt count
21 alternative_if ARM64_HAS_IRQ_PRIO_MASKING
22 /*
23 * DA_F were cleared at start of handling. If anything is set in DAIF,
24 * we come back from an NMI, so skip preemption
25 */
26 mrs x0, daif
27 orr x24, x24, x0
28 alternative_else_nop_endif
29 cbnz x24, 1f // preempt count != 0 || NMI return path
30 bl arm64_preempt_schedule_irq // irq en/disable is done inside
311:
32 #endif
33
코드를 보면 리눅스 커널 관점의 루틴이지, ARM 프로세서 관점으로 분석할만한 내용은 없습니다.
간단히 코드를 리뷰하겠습니다.
17번째 줄입니다.
17 irq_handler
은 인터럽트에 대한 처리를 위해 IRQ 서브 시스템을 호출합니다. 이 코드를 시작으로 호출되는 서브 루틴에서 인터럽트 핸들러가 호출됩니다.
19~30번째 줄을 보겠습니다.
19 #ifdef CONFIG_PREEMPT
20 ldr x24, [tsk, #TSK_TI_PREEMPT] // get preempt count
21 alternative_if ARM64_HAS_IRQ_PRIO_MASKING
22 /*
23 * DA_F were cleared at start of handling. If anything is set in DAIF,
24 * we come back from an NMI, so skip preemption
25 */
26 mrs x0, daif
27 orr x24, x24, x0
28 alternative_else_nop_endif
29 cbnz x24, 1f // preempt count != 0 || NMI return path
30 bl arm64_preempt_schedule_irq // irq en/disable is done inside
리눅스 커널의 Preemption과 관련된 루틴으로 태스크 디스크립터의 preempt_count 필드가 0이면,
arm64_preempt_schedule_irq() 함수를 호출하는 목적의 코드입니다.
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자

최근 덧글