역할과 입력
- 예외 입력: 하드웨어가 ELR_EL1·SPSR_EL1에 복귀 상태를 보존
- 소프트웨어 저장: pt_regs에 x0–x30, 원래 SP·PC·PSTATE 기록
원본 .S · 빌드 조건: Makefile · 명령·주소 표기 읽는 법
1100줄 · SHA-256: 1db96dc7904e4a1688492b395aea4eb01e6388f516f342e415ce03fd39c1554d
구조와 흐름
kernel_entry의 pt_regs 저장 위치
헤더와 범용 레지스터 초기화
clear_gp_regs는 .irp로 x0–x29에 0을 쓰는 명령을 생성합니다. 원래 값은 kernel_entry에서 먼저 pt_regs에 저장합니다. x30까지 자동으로 지우는 반복은 아닙니다.
1/* SPDX-License-Identifier: GPL-2.0-only */
2/*
3 * Low-level exception handling code
4 *
5 * Copyright (C) 2012 ARM Ltd.
6 * Authors: Catalin Marinas <catalin.marinas@arm.com>
7 * Will Deacon <will.deacon@arm.com>
8 */
9
10#include <linux/arm-smccc.h>
11#include <linux/init.h>
12#include <linux/linkage.h>
13
14#include <asm/alternative.h>
15#include <asm/assembler.h>
16#include <asm/asm-offsets.h>
17#include <asm/asm_pointer_auth.h>
18#include <asm/bug.h>
19#include <asm/cpufeature.h>
20#include <asm/errno.h>
21#include <asm/esr.h>
22#include <asm/irq.h>
23#include <asm/memory.h>
24#include <asm/mmu.h>
25#include <asm/processor.h>
26#include <asm/ptrace.h>
27#include <asm/scs.h>
28#include <asm/stacktrace/frame.h>
29#include <asm/thread_info.h>
30#include <asm/asm-uaccess.h>
31#include <asm/unistd.h>
32
33 .macro clear_gp_regs
34 .irp n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
35 mov x\n, xzr
36 .endr
37 .endm
38
- L10
- SMC/HVC로 호출하는 펌웨어 서비스의 함수 번호와 규약을 가져옵니다. 이 파일의 Spectre 완화 요청은 이 번호를 w0에 넣어 펌웨어가 서비스를 구분하도록 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L11
- 부팅 때만 사용할 코드·데이터의 섹션 표기를 가져옵니다. 초기화가 끝난 뒤 해제 가능한 영역과 계속 남겨야 하는 영역을 링커가 구분하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L12
- SYM_FUNC_START·SYM_CODE_START·SYM_INNER_LABEL 등의 심볼 선언 규칙을 가져옵니다. 함수인지 코드 내부 라벨인지, 다른 오브젝트에 보일 이름인지와 정렬·크기 정보를 빌드 도구에 전달하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L14
- CPU 기능에 따라 같은 위치의 명령을 부팅 시 교체하는 alternatives 문법을 가져옵니다. CONFIG의 빌드 선택과 달리 실제 CPU에 맞는 코드가 남도록 하는 장치입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L15
- ARM64의 큰 주소 구성, CPU별 값 조회, 시스템 레지스터 처리, 동기화 등에 쓰는 어셈블리 매크로를 가져옵니다. 각 사용 위치에서 일반 명령 여러 개로 펼쳐지는 작업입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L16
- C 구조체에서 계산한 필드 오프셋을 가져옵니다. 어셈블리가 task_struct·pt_regs 등에서 값을 읽고 쓸 때 C 빌드의 실제 배치와 일치하도록 생성하는 헤더입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L17
- PAC 키 설치 매크로를 가져옵니다. 사용자·커널 또는 task 사이를 전환할 때 instruction-address 인증 키가 해당 실행 문맥과 맞아야 하므로 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L18
- 복구를 기대하지 않는 커널 오류를 BRK와 BUG 메타데이터로 표현하는 규칙을 가져옵니다. 돌아오지 않아야 할 초기화 함수가 반환한 경우 등의 오류 경로에서 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L19
- CPU 기능과 erratum capability 이름을 가져옵니다. alternatives가 MTE·PAC·PAN·CPU 우회 코드 중 어느 명령열을 사용할지 선택하는 기준입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L20
- 아키텍처의 오류 번호 정의를 가져옵니다. 음수 반환값을 오류 종류와 연결하는 정수 상수이며 CPU 예외 벡터 번호와는 다릅니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L21
- ESR 예외 원인 레지스터의 종류와 필드를 해석할 정의를 가져옵니다. 예외 번호와 시스템 호출 번호는 서로 다른 정보입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L22
- ARM64 IRQ의 스택 크기와 관련 인터럽트 정의를 가져옵니다. 별도 IRQ 스택으로 실행을 옮길 때 task 스택과 크기·역할을 구분하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L23
- 커널 가상 주소 배치, 스택 크기·정렬, 주소 변환 관련 상수를 가져옵니다. 같은 숫자가 주소·크기·오프셋 중 무엇인지 각 사용 문맥에서 구분해야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L24
- 주소 공간 식별과 MMU 상태 관련 정의를 가져옵니다. 소프트웨어 PAN과 KPTI의 TTBR·ASID 전환에서 주소 변환 문맥을 구분하는 데 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L25
- ARM64 task의 CPU 실행 상태에 관한 정의를 가져옵니다. 저장 문맥과 task별 제어 정보를 저수준 코드에서 일관되게 해석하도록 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L26
- pt_regs와 예외 전 PSTATE의 레벨·폭·마스크 비트 정의를 가져옵니다. 저장된 실행 문맥을 구성하고 32/64비트 복귀 상태를 해석하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L27
- 별도 보호 스택에 복귀 주소를 보관하는 Shadow Call Stack의 저장·복원 규칙을 가져옵니다. task나 IRQ 문맥을 바꿀 때 일반 SP와 SCS 포인터를 함께 관리해야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L28
- 스택 추적기의 프레임 메타데이터 형식을 가져옵니다. 예외가 끊은 커널 프레임인지 추적의 마지막 프레임인지 표시하여 FP 연결을 안전하게 해석하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L29
- task의 저수준 실행 플래그와 스택 관련 정의를 가져옵니다. 예외 진입·복귀에서 single-step·SSBD·MTE 등 task별 처리를 고를 때 쓰는 정보입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L30
- 커널의 사용자 메모리 접근을 열고 닫는 어셈블리 매크로를 가져옵니다. 소프트웨어 PAN에서 TTBR0를 예약 표와 사용자 표 사이로 바꾸는 구현을 제공합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L31
- ARM64 시스템 호출 관련 번호와 정의를 가져옵니다. 예외로 들어왔다는 사실만으로 syscall이라고 단정하지 않도록 NO_SYSCALL 같은 구분도 함께 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L33
- x0~x29를 0으로 정리할 매크로입니다. 사용자 예외 진입에서는 원래 값을 먼저 pt_regs에 저장한 뒤 사용하여 복귀 상태를 보존하면서 커널이 불필요한 사용자 레지스터 값을 계속 들고 있지 않게 합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L34
- n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29 — 각 인자에 대해 매크로 본문을 반복 생성합니다.
- L35
- x\n ← xzr. 값을 복사합니다. NZCV는 유지합니다.
- L36
- — 반복 생성 구간을 끝냅니다.
- L37
- — 매크로 정의를 끝냅니다.
벡터 슬롯과 스택 초과 검사
kernel_ventry의 .align 7은 128바이트 정렬입니다. EL0 일반 진입은 첫 B로 trampoline 정리를 건너뛰고, trampoline에서 +4로 들어온 경우에는 x30 복구를 수행합니다. SP에서 PT_REGS_SIZE를 뺀 뒤 ADD/SUB로 x0와 SP를 잠시 교환해 THREAD_SHIFT 비트를 검사합니다. 정상 경로에서는 두 값을 복원합니다. 초과 경로는 TPIDR_EL0/TPIDRRO_EL0에 임시 저장하고 CPU별 overflow stack으로 전환합니다. .org는 슬롯 크기 초과를 어셈블 단계에서 드러냅니다.
39 .macro kernel_ventry, el:req, ht:req, regsize:req, label:req
40 .align 7
41.Lventry_start\@:
42 .if \el == 0
43 /*
44 * This must be the first instruction of the EL0 vector entries. It is
45 * skipped by the trampoline vectors, to trigger the cleanup.
46 */
47 b .Lskip_tramp_vectors_cleanup\@
48 .if \regsize == 64
49 mrs x30, tpidrro_el0
50 msr tpidrro_el0, xzr
51 .else
52 mov x30, xzr
53 .endif
54.Lskip_tramp_vectors_cleanup\@:
55 .endif
56
57 sub sp, sp, #PT_REGS_SIZE
58 /*
59 * Test whether the SP has overflowed, without corrupting a GPR.
60 * Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
61 * should always be zero.
62 */
63 add sp, sp, x0 // sp' = sp + x0
64 sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sp
65 tbnz x0, #THREAD_SHIFT, 0f
66 sub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0
67 sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = sp
68 b el\el\ht\()_\regsize\()_\label
69
700:
71 /*
72 * Either we've just detected an overflow, or we've taken an exception
73 * while on the overflow stack. Either way, we won't return to
74 * userspace, and can clobber EL0 registers to free up GPRs.
75 */
76
77 /* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */
78 msr tpidr_el0, x0
79
80 /* Recover the original x0 value and stash it in tpidrro_el0 */
81 sub x0, sp, x0
82 msr tpidrro_el0, x0
83
84 /* Switch to the overflow stack */
85 adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0
86
87 /*
88 * Check whether we were already on the overflow stack. This may happen
89 * after panic() re-enables interrupts.
90 */
91 mrs x0, tpidr_el0 // sp of interrupted context
92 sub x0, sp, x0 // delta with top of overflow stack
93 tst x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?
94 b.ne __bad_stack // no? -> bad stack pointer
95
96 /* We were already on the overflow stack. Restore sp/x0 and carry on. */
97 sub sp, sp, x0
98 mrs x0, tpidrro_el0
99 b el\el\ht\()_\regsize\()_\label
100.org .Lventry_start\@ + 128 // Did we overflow the ventry slot?
101 .endm
- L39
- 128바이트 예외 벡터 입구 하나를 만드는 매크로입니다. el은 중단된 레벨, ht는 SP 선택, regsize는 AArch64/AArch32, label은 sync·irq·fiq·error 종류입니다. 저장 공간과 스택 넘침 검사를 마련한 뒤 그 조합의 실제 handler로 연결합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L40
- 벡터 입구를 2^7=128바이트 경계에 맞춥니다. AArch64 하드웨어가 예외 종류별 고정 offset으로 입구를 고르기 때문에 단순한 성능 정렬이 아니라 벡터 배치 규칙입니다.
- L41
- .Lventry_start\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L42
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L47
- .Lskip_tramp_vectors_cleanup\@로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L48
- \regsize == 64 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L49
- trampoline이 임시로 TPIDRRO_EL0에 보관한 사용자 x30을 복구합니다. 일반 TLS 값을 읽는 문맥이 아니라 제한된 매핑으로 들어오는 동안 잃지 않도록 맡겨 둔 복귀 주소를 되찾는 단계입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L50
- 임시 저장에 사용한 TPIDRRO_EL0를 0으로 정리합니다. 사용자 x30은 앞줄에서 복구했으며 이후 커널 진입이 이 임시값을 다시 사용자 데이터로 오인하지 않게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L51
- — 앞 어셈블 조건의 반대 부분입니다.
- L52
- x30 ← xzr. 값을 복사합니다. NZCV는 유지합니다.
- L53
- — 어셈블 조건 구간을 끝냅니다.
- L54
- .Lskip_tramp_vectors_cleanup\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L55
- — 어셈블 조건 구간을 끝냅니다.
- L57
- pt_regs를 놓을 만큼 커널 SP를 낮춥니다. 뒤의 레지스터 저장이 호출자의 스택 자료를 덮지 않게 확보하는 공간입니다. 이 명령은 SP의 주소값을 조정하며, 확보하거나 반환하는 스택 공간의 데이터를 직접 저장하거나 지우지는 않습니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L63
- sp ← sp + x0. NZCV는 유지합니다. x0를 잃지 않고 원래 SP를 검사하기 위한 임시 산술입니다. 일반적인 스택 할당 ADD가 아닙니다.
- L64
- x0 ← sp − x0. NZCV는 유지합니다. 새 x0=(원래 SP−PT_REGS_SIZE)입니다.
- L65
- THREAD_SHIFT 위치의 비트로 정렬된 task 스택 경계를 넘어갔는지 검사합니다. 이 구간은 임시 레지스터를 잃지 않도록 SP와 x0의 가역 계산을 사용해 원래 SP를 x0에 복원한 상태입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L66
- x0 ← sp − x0. NZCV는 유지합니다. 원래 x0 값을 재구성합니다.
- L67
- sp ← sp − x0. NZCV는 유지합니다. pt_regs 공간을 확보한 SP로 되돌립니다.
- L68
- el\el\ht\()_\regsize\()_\label로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L70
- 0 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L78
- 스택 넘침 처리에서 원래 SP를 TPIDR_EL0에 임시 보관합니다. 보통 사용자 TLS 용도로 알려진 레지스터지만 이 비정상 진입 구간에서는 안전한 스택으로 옮기기 위한 scratch 저장소입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L81
- x0 ← sp − x0. NZCV는 유지합니다.
- L82
- 스택 검사 중 보존해야 할 원래 x0를 TPIDRRO_EL0에 임시 저장합니다. SP를 overflow stack으로 바꾼 뒤 원래 예외 문맥을 다시 만들기 위해서입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L85
- 현재 CPU의 overflow stack 높은 주소 끝을 SP로 선택합니다. 정상 task 스택이 넘쳤을 수 있으므로 같은 스택에 오류 처리 프레임을 더 쌓지 않게 합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L91
- TPIDR_EL0에 맡겨 둔 중단 시 SP를 다시 읽습니다. 새 overflow stack 범위 안에서 이미 처리 중이었는지 다음 차이 계산으로 검사합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L92
- x0 ← sp − x0. NZCV는 유지합니다.
- L93
- 원래 SP와 overflow stack 상단의 차이가 OVERFLOW_STACK_SIZE 범위에 들어가는지 검사합니다. 이미 overflow stack에서 발생한 중첩 예외와 최초 스택 손상을 구분하려는 조건입니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.
- L94
- 기존 NZCV가 Z=0이면 __bad_stack로 분기합니다. 그렇지 않으면 다음 명령을 실행합니다.
- L97
- sp ← sp − x0. NZCV는 유지합니다.
- L98
- 임시 TPIDRRO_EL0에서 원래 x0를 복구하여 정상 handler가 보는 저장 문맥을 유지합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L99
- el\el\ht\()_\regsize\()_\label로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L100
- 이 벡터 입구가 시작부터 128바이트를 넘지 않도록 assembler 위치를 강제합니다. 코드가 슬롯보다 커지면 빌드에서 드러나게 하여 다음 벡터를 덮어쓰지 않게 합니다.
- L101
- — 매크로 정의를 끝냅니다.
trampoline 주소와 SSBD
tramp_alias는 TRAMP_VALIAS 기준의 별칭 주소를 MOVZ/MOVK로 만듭니다. apply_ssbd는 CPU별 callback 필요 여부와 태스크 TIF_SSBD를 확인한 뒤 펌웨어 완화 호출을 수행합니다. alternative_cb가 분기를 NOP로, 호출 자리를 SMC/HVC로 바꿀 수 있습니다. x0–x3은 이 과정에서 바뀔 수 있습니다.
102
103 .macro tramp_alias, dst, sym
104 .set .Lalias\@, TRAMP_VALIAS + \sym - .entry.tramp.text
105 movz \dst, :abs_g2_s:.Lalias\@
106 movk \dst, :abs_g1_nc:.Lalias\@
107 movk \dst, :abs_g0_nc:.Lalias\@
108 .endm
109
110 /*
111 * This macro corrupts x0-x3. It is the caller's duty to save/restore
112 * them if required.
113 */
114 .macro apply_ssbd, state, tmp1, tmp2
115alternative_cb ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable
116 b .L__asm_ssbd_skip\@ // Patched to NOP
117alternative_cb_end
118 ldr_this_cpu \tmp2, arm64_ssbd_callback_required, \tmp1
119 cbz \tmp2, .L__asm_ssbd_skip\@
120 ldr \tmp2, [tsk, #TSK_TI_FLAGS]
121 tbnz \tmp2, #TIF_SSBD, .L__asm_ssbd_skip\@
122 mov w0, #ARM_SMCCC_ARCH_WORKAROUND_2
123 mov w1, #\state
124alternative_cb ARM64_ALWAYS_SYSTEM, smccc_patch_fw_mitigation_conduit
125 nop // Patched to SMC/HVC #0
126alternative_cb_end
127.L__asm_ssbd_skip\@:
128 .endm
- L103
- trampoline의 최소 매핑에서 접근할 별칭 주소를 dst에 만드는 매크로입니다. sym의 일반 커널 가상 주소 대신 TRAMP_VALIAS와 섹션 내부 offset을 사용하여 커널 전체 매핑을 없앤 뒤에도 해당 코드를 실행할 수 있게 합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L104
- sym이 .entry.tramp.text 시작에서 떨어진 거리에 고정 trampoline 가상 기준을 더합니다. 같은 코드 바이트의 다른 매핑 주소를 계산하는 식이며 코드를 복사하지 않습니다. 이름에 식의 값을 부여하는 어셈블러 지시문입니다. 실행 중 일반 레지스터나 메모리에 값을 쓰는 명령은 아닙니다.
- L105
- \dst의 상위 조각을 signed MOVW 재배치 \dst, :abs_g2_s:.Lalias\@로 구성합니다. 음수 심볼 값을 나타내기 위해 링크 시 MOVN 형태가 사용될 수 있으며 뒤의 MOVK가 하위 조각을 채웁니다.
- L106
- \dst의 지정된 16비트 조각에 :abs_g1_nc:.Lalias\@를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L107
- \dst의 지정된 16비트 조각에 :abs_g0_nc:.Lalias\@를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L108
- — 매크로 정의를 끝냅니다.
- L114
- Spectre v4의 speculative store bypass 완화를 펌웨어에 요청하는 매크로입니다. state가 1이면 커널 진입 쪽의 활성 요청, 0이면 사용자 복귀 쪽 요청에 쓰이며 CPU·task 정책상 필요할 때만 SMCCC 호출이 남습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L115
- 시스템 전체의 Spectre v4 정책을 본 callback이 다음 skip 분기를 NOP로 바꿀지 결정하도록 패치 지점을 등록합니다. ARM64_ALWAYS_SYSTEM은 여기서 정책 callback을 실행시키는 공통 capability이며 모든 CPU가 무조건 펌웨어 완화를 호출한다는 뜻은 아닙니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L116
- .L__asm_ssbd_skip\@로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L117
- 115행에서 시작한 ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.
- L118
- 이 CPU가 SSBD 변경을 펌웨어 callback으로 처리해야 하는지 per-CPU 플래그를 읽습니다. 하드웨어나 정책에 따라 callback이 필요 없는 CPU는 뒤에서 바로 건너뜁니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L119
- \tmp2가 0이면 .L__asm_ssbd_skip\@로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L120
- [tsk, #TSK_TI_FLAGS]에서 8바이트를 읽어 \tmp2에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L121
- task의 TIF_SSBD 정책이 이미 완화를 유지하도록 요구하는 경우 진입·복귀 때 상태를 바꾸는 호출을 건너뜁니다. 비트가 세워졌다는 사실만 보고 여기서 취약성 완화를 끈다고 해석하면 안 됩니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L122
- SMCCC ARCH_WORKAROUND_2 함수 ID를 첫 인수 w0에 넣습니다. 펌웨어에 Spectre v4의 SSBD 상태를 요청하는 인터페이스 번호입니다. MOV는 w0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다. w0에 쓰면 대응하는 x0의 상위 32비트도 0이 됩니다.
- L123
- 매크로 호출자가 지정한 완화 상태 state를 펌웨어의 두 번째 인수 w1에 넣습니다. 이 값은 호출 서비스 번호와 구분됩니다. MOV는 w1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다. w1에 쓰면 대응하는 x1의 상위 32비트도 0이 됩니다.
- L124
- 시스템의 SMCCC 통로에 맞춰 다음 NOP를 SMC 또는 HVC 명령으로 바꾸는 callback을 등록합니다. 펌웨어가 어느 레벨에서 서비스를 제공하는지에 맞추기 위한 코드 패치입니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L125
- 초기 NOP는 펌웨어 완화 통로가 결정되면 SMC #0 또는 HVC #0으로 바뀔 수 있습니다. 실행 시에는 w0의 서비스 번호와 w1의 상태를 그 통로로 전달합니다.
- L126
- 124행에서 시작한 ARM64_ALWAYS_SYSTEM, smccc_patch_fw_mitigation_conduit 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.
- L127
- .L__asm_ssbd_skip\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L128
- — 매크로 정의를 끝냅니다.
MTE 비동기 오류와 태그 생성 설정
비동기 또는 비대칭 검사 모드에서 TFSRE0_EL1.TF0가 설정되면 STSET으로 태스크의 _TIF_MTE_ASYNC_FAULT를 기록합니다. 복귀 시 DSB 후 오류 상태를 지웁니다. GCR_EL1에는 사용자 태그 제외 마스크 또는 KASAN용 커널 값을 설정합니다. CONFIG와 CPU 기능 패치에 따라 해당 명령 자체가 생략될 수 있습니다.
129
130 /* Check for MTE asynchronous tag check faults */
131 .macro check_mte_async_tcf, tmp, ti_flags, thread_sctlr
132#ifdef CONFIG_ARM64_MTE
133 .arch_extension lse
134alternative_if_not ARM64_MTE
135 b 1f
136alternative_else_nop_endif
137 /*
138 * Asynchronous tag check faults are only possible in ASYNC (2) or
139 * ASYM (3) modes. In each of these modes bit 1 of SCTLR_EL1.TCF0 is
140 * set, so skip the check if it is unset.
141 */
142 tbz \thread_sctlr, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
143 mrs_s \tmp, SYS_TFSRE0_EL1
144 tbz \tmp, #SYS_TFSR_EL1_TF0_SHIFT, 1f
145 /* Asynchronous TCF occurred for TTBR0 access, set the TI flag */
146 mov \tmp, #_TIF_MTE_ASYNC_FAULT
147 add \ti_flags, tsk, #TSK_TI_FLAGS
148 stset \tmp, [\ti_flags]
1491:
150#endif
151 .endm
152
153 /* Clear the MTE asynchronous tag check faults */
154 .macro clear_mte_async_tcf thread_sctlr
155#ifdef CONFIG_ARM64_MTE
156alternative_if ARM64_MTE
157 /* See comment in check_mte_async_tcf above. */
158 tbz \thread_sctlr, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
159 dsb ish
160 msr_s SYS_TFSRE0_EL1, xzr
1611:
162alternative_else_nop_endif
163#endif
164 .endm
165
166 .macro mte_set_gcr, mte_ctrl, tmp
167#ifdef CONFIG_ARM64_MTE
168 ubfx \tmp, \mte_ctrl, #MTE_CTRL_GCR_USER_EXCL_SHIFT, #16
169 orr \tmp, \tmp, #SYS_GCR_EL1_RRND
170 msr_s SYS_GCR_EL1, \tmp
171#endif
172 .endm
173
174 .macro mte_set_kernel_gcr, tmp, tmp2
175#ifdef CONFIG_KASAN_HW_TAGS
176alternative_cb ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
177 b 1f
178alternative_cb_end
179 mov \tmp, KERNEL_GCR_EL1
180 msr_s SYS_GCR_EL1, \tmp
1811:
182#endif
183 .endm
184
185 .macro mte_set_user_gcr, tsk, tmp, tmp2
186#ifdef CONFIG_KASAN_HW_TAGS
187alternative_cb ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
188 b 1f
189alternative_cb_end
190 ldr \tmp, [\tsk, #THREAD_MTE_CTRL]
191
192 mte_set_gcr \tmp, \tmp2
1931:
194#endif
195 .endm
196
- L131
- 사용자 실행 중 누적된 비동기 MTE 태그 오류를 task 플래그에 반영하는 매크로입니다. tmp는 상태 조회용, ti_flags는 task 플래그 주소용, thread_sctlr는 이 task의 검사 mode를 담습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L132
- MTE는 포인터의 논리 태그와 메모리의 할당 태그를 검사해 일부 잘못된 접근을 찾는 기능입니다. 지원 빌드에만 비동기 오류 조회를 포함하며, 꺼지면 이 매크로의 본문이 사라집니다. 실제 CPU 지원은 아래 alternative에서 다시 확인합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L133
- 어셈블러가 뒤의 LSE 원자 명령 STSET를 받아들이도록 명령 확장을 허용합니다. 여기서 CPU 기능을 켜는 것이 아니라 MTE 오류 플래그를 원자적으로 OR할 명령의 빌드 문법을 준비합니다.
- L134
- 실제 시스템에 ARM64_MTE가 없으면 다음 건너뛰기 분기가 남도록 합니다. MTE가 있으면 그 분기를 NOP로 바꿔 태그 오류 레지스터를 검사합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L135
- 1f (L149)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L136
- 134행에서 시작한 ARM64_MTE 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L142
- task의 TCF0 mode가 비동기 또는 비대칭 검사에 해당하는지 관련 비트를 확인합니다. 동기 mode만 사용하면 이 누적 오류 조회는 필요 없어 끝으로 갑니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L143
- TFSRE0_EL1에서 EL0 태그 검사 실패의 누적 상태를 읽습니다. 비동기 실패는 원래 접근 즉시 일반 동기 fault로 처리되지 않을 수 있으므로 커널 진입 때 수거합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L144
- EL0 태그 실패 표식 TF0가 없으면 task에 새 오류를 표시할 필요가 없습니다. 세워져 있을 때만 다음 원자적 플래그 갱신을 합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L146
- 후속 사용자 복귀 경로가 비동기 MTE 오류를 처리하도록 _TIF_MTE_ASYNC_FAULT 마스크를 준비합니다. MOV는 \tmp의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L147
- \ti_flags ← tsk + #TSK_TI_FLAGS. NZCV는 유지합니다.
- L148
- task의 thread-info flags에 MTE 오류 비트를 원자적으로 OR합니다. 다른 비트 갱신을 잃지 않고 비동기 태그 실패를 커널의 후속 처리에 전달하기 위해 STSET를 사용합니다.
- L149
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L150
- — 전처리 조건 구간을 끝냅니다.
- L151
- — 매크로 정의를 끝냅니다.
- L154
- 사용자 복귀 전에 이전 비동기 MTE 실패의 누적 상태를 지우는 매크로입니다. 인수 thread_sctlr로 비동기 수거 대상 mode인지 확인해 필요 없는 레지스터 작업은 생략합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L155
- MTE 지원 빌드에만 오류 상태 정리 코드를 포함합니다. 이 기능이 꺼져 있으면 MTE 시스템 레지스터를 쓰는 본문은 생성되지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L156
- 실제 MTE 지원이 확인된 시스템에만 아래 TFSRE0_EL1 정리가 남습니다. 빌드 지원과 실제 CPU 기능을 나누어 없는 레지스터에 접근하지 않게 합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L158
- \thread_sctlr의 비트 #(SCTLR_EL1_TCF0_SHIFT + 1)가 0이면 1f (L161)로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L159
- 기존 태그 검사 결과가 필요한 공유 범위에서 반영된 뒤 상태를 지우도록 DSB ISH를 수행합니다. 누적 실패가 늦게 나타나 다음 사용자 실행에 섞이는 것을 피하려는 순서입니다. DSB는 지정한 범위에 대해 앞선 작업의 완료를 기다린 뒤 다음 명령으로 진행하게 합니다.
- L160
- TFSRE0_EL1의 누적 오류 표식을 0으로 지웁니다. 앞에서 task 플래그로 넘긴 오류와 이후 사용자 실행에서 발생할 새 오류를 구분하기 위해서입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L161
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L162
- 156행에서 시작한 ARM64_MTE 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L163
- — 전처리 조건 구간을 끝냅니다.
- L164
- — 매크로 정의를 끝냅니다.
- L166
- MTE 태그 생성 제어 GCR_EL1을 task 정책에 맞게 만드는 매크로입니다. mte_ctrl에서 제외 태그 마스크를 꺼내 임의 태그 생성 제어와 함께 tmp에 만든 뒤 레지스터에 씁니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L167
- MTE 지원 빌드에서만 태그 생성 제어를 갱신합니다. 꺼진 빌드에는 GCR_EL1 쓰기가 없어 일반 CPU 실행 경로에 영향을 주지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L168
- task의 mte_ctrl에서 사용자 제외 태그 마스크 16비트를 꺼냅니다. 각 비트는 임의 태그 생성에서 제외할 태그를 나타내며 주소의 16비트 일부가 아닙니다. 추출한 필드는 결과의 하위 비트에 놓이고 나머지 상위 비트는 0으로 채워집니다.
- L169
- GCR_EL1의 RRND 비트를 더해 지원되는 임의 태그 선택 동작을 사용하도록 합니다. 제외 마스크와 임의 생성 제어는 다른 필드입니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.
- L170
- 계산한 제외 태그와 생성 정책을 GCR_EL1에 설치합니다. 이후 태그 생성 명령이 현재 task에 맞는 정책을 사용하게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L171
- — 전처리 조건 구간을 끝냅니다.
- L172
- — 매크로 정의를 끝냅니다.
- L174
- 커널의 하드웨어 태그 KASAN이 사용하는 태그 생성 정책으로 GCR을 전환하는 매크로입니다. 사용자 정책을 그대로 쓰면 커널이 기대한 태그 범위와 달라질 수 있습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L175
- KASAN_HW_TAGS는 Arm MTE 하드웨어로 커널 메모리 태그를 검사하는 KASAN 방식입니다. 켜진 빌드에만 커널용 GCR 전환을 포함하고, 꺼지면 이 매크로는 비어 있습니다. 소프트웨어 shadow를 사용하는 GENERIC/SW_TAGS와 구분합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L176
- KASAN 하드웨어 태그 검사를 런타임에도 활성화했는지 보는 callback입니다. 활성인 경우 다음 skip 분기를 NOP로 바꾸어 커널 GCR을 설치하고, 비활성이면 건너뜁니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L177
- 1f (L181)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L178
- 176행에서 시작한 ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.
- L179
- 커널 태그 KASAN이 사용할 GCR_EL1의 제외 마스크와 생성 설정 KERNEL_GCR_EL1을 준비합니다. 사용자 task의 mte_ctrl 값과 의도적으로 다른 정책입니다. MOV는 \tmp의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L180
- 커널 메모리용 태그 생성 제어를 실제 GCR_EL1에 씁니다. 사용자 진입에서 커널 태그 정책으로 넘어오는 지점입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L181
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L182
- — 전처리 조건 구간을 끝냅니다.
- L183
- — 매크로 정의를 끝냅니다.
- L185
- 커널 하드웨어 태그 KASAN 때문에 바뀐 GCR을 사용자 task 정책으로 되돌리는 매크로입니다. tsk에서 mte_ctrl을 읽어 mte_set_gcr에 넘기며 tmp·tmp2를 계산용으로 씁니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L186
- KASAN_HW_TAGS 구성에서는 커널 실행 중 GCR을 바꾸므로 사용자 복귀 때 task의 태그 생성 정책을 복원해야 합니다. 이 구성이 아니면 해당 전환 코드 자체를 생략합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L187
- 런타임 하드웨어 태그 KASAN이 실제 활성일 때만 사용자 GCR 복원 경로를 남기는 callback입니다. 커널이 GCR을 바꾸지 않은 경우 불필요한 복원을 피합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L188
- 1f (L193)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L189
- 187행에서 시작한 ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.
- L190
- 현재 task의 THREAD_MTE_CTRL에서 사용자 태그 생성 정책을 읽습니다. 이전 task나 커널의 제외 마스크를 사용자 실행에 남기지 않으려는 입력입니다. 실제로 \tsk + (THREAD_MTE_CTRL) 주소의 메모리 8바이트를 \tmp에 읽습니다. 이 주소 형식은 기준 레지스터 \tsk 자체를 갱신하지 않습니다.
- L192
- 현재 task에서 읽은 MTE 제어값으로 사용자 제외 태그 마스크와 RRND를 조합해 GCR_EL1에 기록합니다. 커널 하드웨어 태그 KASAN의 생성 정책을 사용자 복귀에 남기지 않도록 되돌리는 호출입니다.
- L193
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L194
- — 전처리 조건 구간을 끝냅니다.
- L195
- — 매크로 정의를 끝냅니다.
kernel_entry: x0–x29 저장과 current 설정
AArch32 진입에서는 MOV W0,W0로 상위 32비트를 지웁니다. 이어 STP 15개가 x0–x29를 저장합니다. EL0에서 왔다면 SP_EL0에 남은 사용자 SP를 x21로 읽고 __entry_task를 SP_EL0에 설치합니다. 단일 단계 디버그, MTE, PAC, SSBD, SCS를 커널용으로 준비합니다. EL1 진입에서는 원래 SP가 현재 SP+PT_REGS_SIZE입니다.
197 .macro kernel_entry, el, regsize = 64
198 .if \el == 0
199 alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT
200 .endif
201 .if \regsize == 32
202 mov w0, w0 // zero upper 32 bits of x0
203 .endif
204 stp x0, x1, [sp, #16 * 0]
205 stp x2, x3, [sp, #16 * 1]
206 stp x4, x5, [sp, #16 * 2]
207 stp x6, x7, [sp, #16 * 3]
208 stp x8, x9, [sp, #16 * 4]
209 stp x10, x11, [sp, #16 * 5]
210 stp x12, x13, [sp, #16 * 6]
211 stp x14, x15, [sp, #16 * 7]
212 stp x16, x17, [sp, #16 * 8]
213 stp x18, x19, [sp, #16 * 9]
214 stp x20, x21, [sp, #16 * 10]
215 stp x22, x23, [sp, #16 * 11]
216 stp x24, x25, [sp, #16 * 12]
217 stp x26, x27, [sp, #16 * 13]
218 stp x28, x29, [sp, #16 * 14]
219
220 .if \el == 0
221 clear_gp_regs
222 mrs x21, sp_el0
223 ldr_this_cpu tsk, __entry_task, x20
224 msr sp_el0, tsk
225
226 /*
227 * Ensure MDSCR_EL1.SS is clear, since we can unmask debug exceptions
228 * when scheduling.
229 */
230 ldr x19, [tsk, #TSK_TI_FLAGS]
231 disable_step_tsk x19, x20
232
233 /* Check for asynchronous tag check faults in user space */
234 ldr x0, [tsk, THREAD_SCTLR_USER]
235 check_mte_async_tcf x22, x23, x0
236
237#ifdef CONFIG_ARM64_PTR_AUTH
238alternative_if ARM64_HAS_ADDRESS_AUTH
239 /*
240 * Enable IA for in-kernel PAC if the task had it disabled. Although
241 * this could be implemented with an unconditional MRS which would avoid
242 * a load, this was measured to be slower on Cortex-A75 and Cortex-A76.
243 *
244 * Install the kernel IA key only if IA was enabled in the task. If IA
245 * was disabled on kernel exit then we would have left the kernel IA
246 * installed so there is no need to install it again.
247 */
248 tbz x0, SCTLR_ELx_ENIA_SHIFT, 1f
249 __ptrauth_keys_install_kernel_nosync tsk, x20, x22, x23
250 b 2f
2511:
252 mrs x0, sctlr_el1
253 orr x0, x0, SCTLR_ELx_ENIA
254 msr sctlr_el1, x0
2552:
256alternative_else_nop_endif
257#endif
258
259 apply_ssbd 1, x22, x23
260
261 mte_set_kernel_gcr x22, x23
262
263 /*
264 * Any non-self-synchronizing system register updates required for
265 * kernel entry should be placed before this point.
266 */
267alternative_if ARM64_MTE
268 isb
269 b 1f
270alternative_else_nop_endif
271alternative_if ARM64_HAS_ADDRESS_AUTH
272 isb
273alternative_else_nop_endif
2741:
275
276 scs_load_current
277 .else
278 add x21, sp, #PT_REGS_SIZE
279 get_current_task tsk
280 .endif /* \el == 0 */
- L197
- 예외 프레임에 범용 레지스터·SP·PC·PSTATE를 저장하고 current, 보안 제어, unwind 메타데이터를 준비하는 진입 매크로입니다. el=0이면 사용자 진입용 전환을 추가하고 regsize=32이면 AArch32 인수 폭을 정리합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L198
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L199
- DIT 지원 CPU에서는 EL0에서 들어올 때 PSTATE.DIT를 1로 만드는 명령으로 NOP를 교체합니다. DIT는 지원 명령의 실행 시간이 데이터 값에 의존하는 것을 제한하는 기능이며 모든 코드의 절대 상수 시간을 보장한다는 뜻은 아닙니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L200
- — 어셈블 조건 구간을 끝냅니다.
- L201
- \regsize == 32 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L202
- w0 ← w0. 값을 복사합니다. NZCV는 유지합니다. 대응 X 레지스터의 상위 32비트는 0이 됩니다. 32비트 사용자 x0의 상위 32비트를 확실히 0으로 만듭니다.
- L203
- — 어셈블 조건 구간을 끝냅니다.
- L204
- x0, x1의 값을 [sp, #16 * 0]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L205
- x2, x3의 값을 [sp, #16 * 1]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L206
- x4, x5의 값을 [sp, #16 * 2]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L207
- x6, x7의 값을 [sp, #16 * 3]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L208
- x8, x9의 값을 [sp, #16 * 4]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L209
- x10, x11의 값을 [sp, #16 * 5]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L210
- x12, x13의 값을 [sp, #16 * 6]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L211
- x14, x15의 값을 [sp, #16 * 7]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L212
- x16, x17의 값을 [sp, #16 * 8]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L213
- x18, x19의 값을 [sp, #16 * 9]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L214
- x20, x21의 값을 [sp, #16 * 10]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L215
- x22, x23의 값을 [sp, #16 * 11]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L216
- x24, x25의 값을 [sp, #16 * 12]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L217
- x26, x27의 값을 [sp, #16 * 13]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L218
- x28, x29의 값을 [sp, #16 * 14]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L220
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L221
- x0–x29에 XZR을 복사합니다. 원래 값은 그 전에 pt_regs에 저장되어 있습니다. 인자: 없음.
- L222
- 예외 전 사용자 SP를 SP_EL0에서 x21로 읽어 pt_regs에 보존할 준비를 합니다. 이후 SP_EL0를 커널 current 포인터로 바꾸기 전에 원래 사용자 스택 위치를 확보해야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L223
- CPU별 __entry_task에서 실제 현재 task 주소를 얻습니다. EL0에서 들어온 순간 SP_EL0에는 사용자 SP가 있으므로 그것을 current로 사용할 수 없습니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L224
- SP_EL0를 current task 주소로 바꾸어 커널 내부의 current 조회를 가능하게 합니다. 사용자 SP는 x21에 따로 저장했고 뒤에서 프레임에 기록합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L230
- [tsk, #TSK_TI_FLAGS]에서 8바이트를 읽어 x19에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L231
- task가 사용자 single-step 상태였더라도 커널 진입 코드를 한 명령씩 사용자 디버거에 노출하지 않도록 스텝 상태를 끕니다. 원래 task 플래그는 복귀 시 다시 확인합니다. TIF_SINGLESTEP이 설정된 경우 MDSCR_EL1.SS를 지우고 ISB로 동기화합니다.
- L234
- task의 사용자 SCTLR 정책 사본을 읽습니다. 뒤에서 MTE 검사 mode와 사용자 포인터 인증 활성 여부를 판단할 공통 입력입니다. 실제로 tsk + (THREAD_SCTLR_USER) 주소의 메모리 8바이트를 x0에 읽습니다. 이 주소 형식은 기준 레지스터 tsk 자체를 갱신하지 않습니다.
- L235
- 이 task가 비동기 MTE를 사용했다면 누적 태그 실패를 확인해 TIF_MTE_ASYNC_FAULT에 반영합니다. x22·x23은 조회와 thread flags 주소 계산용 임시 레지스터입니다.
- L237
- 포인터 인증 지원 빌드에만 사용자→커널 전환의 PAC 키/제어 처리를 포함합니다. PAC는 포인터를 키로 서명·인증하며, 커널에서 사용할 키와 사용자 키를 구분해야 하므로 이 위치에서 전환합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L238
- 실제 주소 인증 기능이 있는 CPU에서만 아래 APIA 키와 ENIA 처리가 남도록 합니다. 커널 설정만 켰다고 미지원 CPU에서 인증 레지스터를 쓰지 않습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L248
- task의 사용자 SCTLR.ENIA가 켜져 있으면 현재 APIA 키가 사용자 키일 수 있으므로 커널 키 설치 경로를 탑니다. 꺼져 있으면 커널 키를 유지한 상태로 ENIA만 켜는 경로를 선택합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L249
- current task의 커널 APIA 키를 설치합니다. x20·x22·x23은 키를 읽고 시스템 레지스터에 쓸 임시 레지스터이며 nosync는 공통 ISB를 뒤에서 모아 수행한다는 뜻입니다.
- L250
- 2f (L255)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L251
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L252
- 사용자에서 ENIA를 꺼 둔 경우 현재 SCTLR을 읽고 다른 설정을 보존하면서 커널 주소 인증만 다시 활성화할 준비를 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L253
- SCTLR.ENIA 비트를 세워 커널 코드의 instruction-address A-key 인증을 사용할 수 있게 합니다. 사용자 task가 요청한 비활성 상태를 커널에 그대로 적용하지 않습니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.
- L254
- 커널에서 쓸 ENIA 활성 SCTLR을 설치합니다. 아래 공통 ISB가 PAC/MTE 변경 뒤의 실행을 동기화합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L255
- 2 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L256
- 238행에서 시작한 ARM64_HAS_ADDRESS_AUTH 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L257
- — 전처리 조건 구간을 끝냅니다.
- L259
- 커널 진입 쪽 SSBD 상태 1을 요청합니다. 펌웨어 완화가 필요한 CPU와 task 정책에서만 실제 호출이 남으며 Spectre v4 완화 상태를 사용자 실행과 구분합니다.
- L261
- 하드웨어 태그 KASAN이 활성일 때 GCR을 커널 태그 생성 정책으로 바꿉니다. x22·x23은 그 계산에 사용하는 임시 레지스터입니다.
- L267
- MTE 제어를 바꾼 CPU에는 동기화 ISB가 필요하므로 아래 한 번의 ISB를 남깁니다. 뒤의 PAC 경로와 중복 수행하지 않도록 공통 끝으로 분기합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L268
- 변경된 실행 제어 상태를 반영하여 뒤 명령을 실행하도록 명령 흐름을 동기화합니다.
- L269
- 1f (L274)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L270
- 267행에서 시작한 ARM64_MTE 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L271
- MTE 경로에서 이미 ISB를 하지 않았더라도 주소 인증 상태를 바꾼 CPU에는 ISB를 남깁니다. 두 기능이 모두 없어 제어를 바꾸지 않으면 이 추가 ISB는 NOP 대안으로 빠집니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L272
- 변경된 실행 제어 상태를 반영하여 뒤 명령을 실행하도록 명령 흐름을 동기화합니다.
- L273
- 271행에서 시작한 ARM64_HAS_ADDRESS_AUTH 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L274
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L276
- 현재 태스크의 shadow stack 포인터를 x18에 읽습니다. 일반 SP와 별도인 반환 주소 보호용 스택입니다. 인자: 없음.
- L277
- — 앞 어셈블 조건의 반대 부분입니다.
- L278
- x21 ← sp + #PT_REGS_SIZE. NZCV는 유지합니다. EL1에서 중단된 원래 SP는 프레임을 잡기 전의 SP입니다.
- L279
- SP_EL0에서 현재 task_struct 포인터를 읽습니다. 인자: tsk.
- L280
- — 어셈블 조건 구간을 끝냅니다.
kernel_entry: PC·PSTATE·프레임 메타데이터
ELR_EL1과 SPSR_EL1을 x22/x23으로 읽고 pt_regs에 기록합니다. S_LR에는 LR과 원래 SP를 연속 저장합니다. EL0에서 온 프레임은 FINAL, EL1 예외 프레임은 PT_REGS 메타데이터를 씁니다. EL0의 syscall 번호는 기본 NO_SYSCALL로 시작하며 실제 시스템 호출 경로가 나중에 바꿉니다. pseudo-NMI 구성에서는 ICC_PMR_EL1도 저장합니다.
281 mrs x22, elr_el1
282 mrs x23, spsr_el1
283 stp lr, x21, [sp, #S_LR]
284
285 /*
286 * Create a metadata frame record. The unwinder will use this to
287 * identify and unwind exception boundaries.
288 */
289 stp xzr, xzr, [sp, #S_STACKFRAME]
290 .if \el == 0
291 mov x0, #FRAME_META_TYPE_FINAL
292 .else
293 mov x0, #FRAME_META_TYPE_PT_REGS
294 .endif
295 str x0, [sp, #S_STACKFRAME_TYPE]
296 add x29, sp, #S_STACKFRAME
297
298#ifdef CONFIG_ARM64_SW_TTBR0_PAN
299alternative_if_not ARM64_HAS_PAN
300 bl __swpan_entry_el\el
301alternative_else_nop_endif
302#endif
303
304 stp x22, x23, [sp, #S_PC]
305
306 /* Not in a syscall by default (el0_svc overwrites for real syscall) */
307 .if \el == 0
308 mov w21, #NO_SYSCALL
309 str w21, [sp, #S_SYSCALLNO]
310 .endif
311
312#ifdef CONFIG_ARM64_PSEUDO_NMI
313alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
314 b .Lskip_pmr_save\@
315alternative_else_nop_endif
316
317 mrs_s x20, SYS_ICC_PMR_EL1
318 str w20, [sp, #S_PMR]
319 mov x20, #GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET
320 msr_s SYS_ICC_PMR_EL1, x20
321
322.Lskip_pmr_save\@:
323#endif
324
325 /*
326 * Registers that may be useful after this macro is invoked:
327 *
328 * x20 - ICC_PMR_EL1
329 * x21 - aborted SP
330 * x22 - aborted PC
331 * x23 - aborted PSTATE
332 */
333 .endm
- L281
- ELR_EL1은 예외 후 돌아갈 PC를 담습니다. C handler·시그널·디버거가 저장 프레임을 통해 복귀 위치를 다룰 수 있도록 x22에 읽어 둡니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L282
- SPSR_EL1의 예외 전 PSTATE를 x23에 읽습니다. 원래 실행 레벨·마스크·condition flags를 pt_regs에 보존해 나중에 ERET로 복원할 자료입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L283
- lr, x21의 값을 [sp, #S_LR]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다. LR과 중단된 SP를 S_LR에서 시작하는 연속 두 슬롯에 저장합니다.
- L289
- xzr, xzr의 값을 [sp, #S_STACKFRAME]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L290
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L291
- 사용자에서 왔으면 커널 스택 추적이 여기서 끝나도록 FRAME_META_TYPE_FINAL을 선택합니다. 사용자 FP를 커널 스택 연결로 계속 따라가지 않게 합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L292
- — 앞 어셈블 조건의 반대 부분입니다.
- L293
- 커널 실행 중 예외이면 FRAME_META_TYPE_PT_REGS로 표시하여 unwinder가 저장된 pt_regs를 통해 중단된 커널 프레임으로 이어갈 수 있게 합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L294
- — 어셈블 조건 구간을 끝냅니다.
- L295
- x0의 값을 [sp, #S_STACKFRAME_TYPE]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L296
- x29 ← sp + #S_STACKFRAME. NZCV는 유지합니다.
- L298
- 소프트웨어 PAN은 TTBR0를 예약된 표로 바꿔 커널의 무분별한 사용자 메모리 접근을 막는 구성입니다. 켜진 빌드에서는 진입 시 그 상태를 보존·차단하고, 꺼지면 아래 emulation 호출은 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L299
- 하드웨어 PAN이 없는 CPU에서만 소프트웨어 TTBR0 전환 호출을 유지합니다. PAN이 있으면 하드웨어 접근 차단을 쓰므로 이 호출은 NOP로 교체됩니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L300
- 중단된 레벨에 맞는 __swpan_entry_el0 또는 el1을 호출합니다. EL1 진입이면 원래 사용자 접근 허용 여부를 SPSR의 모의 PAN 표식에 남기고, 필요하면 TTBR0를 예약 상태로 바꿉니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L301
- 299행에서 시작한 ARM64_HAS_PAN 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L302
- — 전처리 조건 구간을 끝냅니다.
- L304
- x22, x23의 값을 [sp, #S_PC]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다. x22=ELR, x23=SPSR이므로 S_PC에는 복귀 PC와 PSTATE가 들어갑니다.
- L307
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L308
- NO_SYSCALL은 아직 이 예외가 시스템 호출로 판정되지 않았음을 나타내는 값입니다. EL0 예외에는 IRQ·fault도 있으므로 진입부터 유효한 syscall 번호라고 가정하지 않습니다. MOV는 w21의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다. w21에 쓰면 대응하는 x21의 상위 32비트도 0이 됩니다.
- L309
- w21의 값을 [sp, #S_SYSCALLNO]에 4바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L310
- — 어셈블 조건 구간을 끝냅니다.
- L312
- pseudo-NMI는 GIC 우선순위 마스크로 일반 IRQ를 막아도 더 높은 우선순위의 요청을 받도록 하는 기능입니다. 켜진 빌드에서 PMR 저장·복원을 포함하며, 꺼지면 일반 DAIF 중심의 경로만 남습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L313
- 실제 시스템이 GIC 우선순위 마스킹을 사용하지 않으면 PMR 저장 코드를 건너뛰도록 분기를 유지합니다. 사용하는 시스템에서는 분기를 NOP로 바꿔 PMR 상태를 프레임에 담습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L314
- .Lskip_pmr_save\@로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L315
- 313행에서 시작한 ARM64_HAS_GIC_PRIO_MASKING 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L317
- ICC_PMR_EL1에서 현재 CPU가 받아들일 인터럽트 우선순위 문턱을 읽습니다. 단순 IRQ 허용 비트보다 세분화된 마스크 상태이므로 예외 프레임에 별도로 저장합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L318
- w20의 값을 [sp, #S_PMR]에 4바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L319
- 커널 진입 후 사용할 GIC 우선순위 기준과 PSTATE.I 관련 표시를 결합합니다. 일반 IRQ와 pseudo-NMI의 차단 수준을 맞추기 위한 값입니다. MOV는 x20의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L320
- 준비한 우선순위 마스크를 ICC_PMR_EL1에 설치하여 이 예외 처리 구간의 인터럽트 수신 조건을 정합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L322
- .Lskip_pmr_save\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L323
- — 전처리 조건 구간을 끝냅니다.
- L333
- — 매크로 정의를 끝냅니다.
kernel_exit: 복귀 전 제어 상태
저장한 인터럽트 우선순위와 ELR/SPSR을 읽습니다. 소프트웨어 PAN이 필요한 경우 TTBR0 접근을 복구합니다. EL0 복귀에서는 SP_EL0, SCS, MTE, 사용자 PAC 키와 SSBD 상태를 맞춥니다. 사용자 PAC 키를 설치한 뒤에는 커널 C 함수를 호출하지 않는다는 제약이 원본에 있습니다.
334
335 .macro kernel_exit, el
336 .if \el != 0
337 disable_daif
338 .endif
339
340#ifdef CONFIG_ARM64_PSEUDO_NMI
341alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
342 b .Lskip_pmr_restore\@
343alternative_else_nop_endif
344
345 ldr w20, [sp, #S_PMR]
346 msr_s SYS_ICC_PMR_EL1, x20
347
348 /* Ensure priority change is seen by redistributor */
349alternative_if_not ARM64_HAS_GIC_PRIO_RELAXED_SYNC
350 dsb sy
351alternative_else_nop_endif
352
353.Lskip_pmr_restore\@:
354#endif
355
356 ldp x21, x22, [sp, #S_PC] // load ELR, SPSR
357
358#ifdef CONFIG_ARM64_SW_TTBR0_PAN
359alternative_if_not ARM64_HAS_PAN
360 bl __swpan_exit_el\el
361alternative_else_nop_endif
362#endif
363
364 .if \el == 0
365 ldr x23, [sp, #S_SP] // load return stack pointer
366 msr sp_el0, x23
367 tst x22, #PSR_MODE32_BIT // native task?
368 b.eq 3f
369
370#ifdef CONFIG_ARM64_ERRATUM_845719
371alternative_if ARM64_WORKAROUND_845719
372#ifdef CONFIG_PID_IN_CONTEXTIDR
373 mrs x29, contextidr_el1
374 msr contextidr_el1, x29
375#else
376 msr contextidr_el1, xzr
377#endif
378alternative_else_nop_endif
379#endif
3803:
381 scs_save tsk
382
383 /* Ignore asynchronous tag check faults in the uaccess routines */
384 ldr x0, [tsk, THREAD_SCTLR_USER]
385 clear_mte_async_tcf x0
386
387#ifdef CONFIG_ARM64_PTR_AUTH
388alternative_if ARM64_HAS_ADDRESS_AUTH
389 /*
390 * IA was enabled for in-kernel PAC. Disable it now if needed, or
391 * alternatively install the user's IA. All other per-task keys and
392 * SCTLR bits were updated on task switch.
393 *
394 * No kernel C function calls after this.
395 */
396 tbz x0, SCTLR_ELx_ENIA_SHIFT, 1f
397 __ptrauth_keys_install_user tsk, x0, x1, x2
398 b 2f
3991:
400 mrs x0, sctlr_el1
401 bic x0, x0, SCTLR_ELx_ENIA
402 msr sctlr_el1, x0
4032:
404alternative_else_nop_endif
405#endif
406
407 mte_set_user_gcr tsk, x0, x1
408
409 apply_ssbd 0, x0, x1
410 .endif
- L335
- pt_regs에 저장한 예외 전 문맥을 복원하는 매크로입니다. el=0은 사용자 복귀라 PAC·MTE·KPTI 등의 전환을 포함하고, el=1은 중단된 커널 실행으로 돌아갈 상태를 복구합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L336
- \el != 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L337
- EL1으로 돌아갈 상태를 복구하는 동안 새 디버그·SError·IRQ·FIQ 예외가 끼지 않도록 DAIF 마스크를 모두 세웁니다. 최종 ERET가 저장된 SPSR의 원래 마스크 상태를 복원합니다. 매크로가 펼치는 MSR DAIFSet, #0xf는 네 예외 마스크 비트를 모두 1로 만듭니다.
- L338
- — 어셈블 조건 구간을 끝냅니다.
- L340
- pseudo-NMI 지원 빌드에서는 진입 때 저장했던 PMR도 복원해야 원래 코드의 인터럽트 우선순위 차단 상태가 돌아옵니다. 미지원 빌드는 이 추가 복원이 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L341
- GIC 우선순위 마스킹을 실제로 사용하지 않는 CPU는 아래 PMR 복원을 건너뜁니다. 사용 여부에 따른 런타임 코드 패치이며 매번 capability를 C if로 검사하는 문장은 아닙니다.
- L342
- .Lskip_pmr_restore\@로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L343
- 341행에서 시작한 ARM64_HAS_GIC_PRIO_MASKING 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L345
- [sp, #S_PMR]에서 4바이트를 읽어 w20에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다. W 레지스터에 쓰므로 대응하는 X 레지스터의 상위 32비트는 0이 됩니다.
- L346
- pt_regs에 저장한 우선순위 마스크를 ICC_PMR_EL1에 되돌립니다. 예외 전 코드가 막아 두었던 IRQ가 복귀 과정에서 잘못 허용되지 않게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L349
- GIC가 PMR 갱신의 완화된 동기화 조건을 지원하지 않으면 DSB SY를 남겨 변경 완료를 기다립니다. 지원하는 시스템에서는 이 DSB를 NOP 대안으로 제거할 수 있습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L350
- DSB sy: 지정 범위·종류의 앞선 메모리 및 관리 작업이 완료되기를 기다립니다.
- L351
- 349행에서 시작한 ARM64_HAS_GIC_PRIO_RELAXED_SYNC 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L353
- .Lskip_pmr_restore\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L354
- — 전처리 조건 구간을 끝냅니다.
- L356
- [sp, #S_PC]에서 16바이트를 읽어 x21, x22에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L358
- 소프트웨어 PAN을 사용하는 빌드에서는 복귀 대상이 원래 사용자 접근을 허용했는지에 따라 TTBR0를 복구합니다. 설정이 없으면 이 emulation 경로를 포함하지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L359
- 하드웨어 PAN이 없는 CPU에서만 __swpan_exit 호출을 남깁니다. 하드웨어 PAN이 있으면 저장된 PSTATE의 실제 PAN 상태가 의미를 갖고 이 소프트웨어 TTBR0 복원은 필요하지 않습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L360
- 복귀 대상 EL에 맞는 소프트웨어 PAN 해제를 호출합니다. EL0 복귀는 사용자 표를 다시 연결하고, EL1 복귀는 저장한 모의 PAN 표식에 따라 기존 사용자 접근 허용 여부를 복원합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.
- L361
- 359행에서 시작한 ARM64_HAS_PAN 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L362
- — 전처리 조건 구간을 끝냅니다.
- L364
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L365
- [sp, #S_SP]에서 8바이트를 읽어 x23에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L366
- pt_regs에서 읽은 사용자 SP를 SP_EL0에 되돌립니다. 커널 실행 중에는 이 레지스터를 current 포인터로 썼지만 ERET 후에는 사용자 스택 위치여야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L367
- 저장한 PSTATE의 MODE32 비트로 AArch32 사용자 복귀인지 확인합니다. 아래 Cortex-A53 erratum 처리는 이 32비트 복귀 문맥에만 필요합니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.
- L368
- 기존 NZCV가 Z=1이면 3f (L380)로 분기합니다. 그렇지 않으면 다음 명령을 실행합니다.
- L370
- Cortex-A53 erratum 845719 우회 코드를 빌드하는 설정입니다. 켜진 빌드에서는 영향을 받는 CPU의 AArch32 복귀 전에 CONTEXTIDR을 다시 쓰는 경로를 포함하고, 꺼지면 해당 보완이 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L371
- 실제로 erratum 845719의 영향을 받는 CPU에서만 CONTEXTIDR 재기록을 남깁니다. 설정을 켰어도 영향을 받지 않으면 같은 크기의 NOP로 바뀝니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L372
- PID를 CONTEXTIDR에 기록해 추적하는 빌드라면 그 값을 보존한 채 다시 써야 합니다. 이 기능이 꺼진 빌드는 아래 #else에서 0을 써도 됩니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L373
- 추적용 PID 등의 기존 CONTEXTIDR 값을 읽습니다. erratum 보완을 위해 재기록하되 디버깅·추적 식별자를 잃지 않으려는 목적입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L374
- 방금 읽은 CONTEXTIDR을 같은 값으로 다시 기록합니다. 값 변경이 아니라 이 시스템 레지스터 쓰기 자체가 필요한 CPU 보완입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L375
- CONTEXTIDR에 PID를 기록하지 않는 빌드는 유지해야 할 PID 값이 없으므로 0을 재기록하는 보완 경로를 사용합니다.
- L376
- CONTEXTIDR에 0을 써 AArch32 복귀에 필요한 erratum 보완 동작을 수행합니다. 현재 task의 PID를 0으로 바꾸는 코드가 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L377
- — 전처리 조건 구간을 끝냅니다.
- L378
- 371행에서 시작한 ARM64_WORKAROUND_845719 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L379
- — 전처리 조건 구간을 끝냅니다.
- L380
- 3 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L381
- SHADOW_CALL_STACK 설정에서 x18의 shadow stack 포인터를 태스크에 저장합니다. 비활성 구성에서는 비어 있습니다. 인자: tsk.
- L384
- 사용자 task의 SCTLR 정책을 다시 읽어 MTE 오류 정리와 PAC 사용자 상태 복원에 사용합니다. 커널이 쓰던 설정을 그대로 사용자에게 넘기지 않기 위한 기준입니다. 실제로 tsk + (THREAD_SCTLR_USER) 주소의 메모리 8바이트를 x0에 읽습니다. 이 주소 형식은 기준 레지스터 tsk 자체를 갱신하지 않습니다.
- L385
- 비동기 MTE mode의 이전 실패 누적 상태를 동기화한 뒤 지웁니다. 새 사용자 실행에서 발생할 태그 오류와 이미 처리 중인 오류를 분리합니다. 매크로는 DSB로 앞선 태그 검사 처리를 동기화한 뒤 TFSRE0_EL1에 0을 씁니다.
- L387
- 포인터 인증 지원 빌드에서는 사용자로 돌아가기 전에 사용자 APIA 키 또는 ENIA 비활성 정책을 복원합니다. 꺼진 빌드는 이 키·제어 전환 코드가 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L388
- 실제 주소 인증 기능이 있을 때만 사용자 키/ENIA 복구를 수행합니다. 없는 CPU는 시스템 레지스터 접근 대신 NOP 대안을 사용합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L396
- 사용자 task가 ENIA를 활성화했으면 사용자 키를 설치하고, 꺼 두었으면 ENIA를 지우는 경로를 선택합니다. task별 인증 정책을 존중하는 분기입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L397
- task_struct에 보존된 사용자 포인터 인증 키를 CPU에 설치합니다. 커널 키로 사용자 포인터를 검사하면 정상 복귀 주소도 인증되지 않으므로 구분해야 합니다. 이 매크로에서 복원하는 키는 사용자 명령어 주소 인증용 APIA 키이며, 모든 종류의 PAC 키를 한꺼번에 설치하는 것은 아닙니다.
- L398
- 2f (L403)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L399
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L400
- 기존 SCTLR의 다른 비트는 유지하면서 사용자 요청에 따라 ENIA만 끌 값을 준비합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L401
- 사용자가 instruction-address A-key 인증을 비활성화한 정책에 맞춰 ENIA 비트를 지웁니다. 모든 PAC 기능과 모든 키를 0으로 만드는 동작은 아닙니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.
- L402
- ENIA를 지운 사용자 실행용 SCTLR 값을 설치합니다. 커널 진입 때 다시 켰던 상태를 사용자 요청으로 되돌리는 단계입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L403
- 2 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L404
- 388행에서 시작한 ARM64_HAS_ADDRESS_AUTH 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L405
- — 전처리 조건 구간을 끝냅니다.
- L407
- 커널 하드웨어 태그 KASAN이 바꾸었던 GCR을 현재 사용자 task의 제외 태그 정책으로 복원합니다. tsk가 정책의 출처이며 x0·x1은 임시 레지스터입니다. 매크로는 task의 THREAD_MTE_CTRL 필드에서 사용자 정책을 읽습니다.
- L409
- 사용자 복귀 쪽 SSBD 상태 0을 요청합니다. task가 완화를 유지하도록 요구하는 경우 등에는 apply_ssbd 내부에서 이 전환을 건너뛰므로 무조건 완화를 끄는 명령이 아닙니다.
- L410
- — 어셈블 조건 구간을 끝냅니다.
kernel_exit: 레지스터 복원과 ERET
ELR_EL1·SPSR_EL1을 설정하고 x0–x29를 복원합니다. KPTI 경로는 FAR_EL1에 x29를 임시 보관하고 trampoline 벡터로 VBAR를 바꾼 뒤 BR합니다. 일반 경로는 LR과 SP를 복원하고 ERET합니다. ERET 뒤의 SB는 정상 경로에서 다음 명령처럼 실행되는 코드가 아니라 추측 실행을 제한하기 위한 배치입니다.
411
412 msr elr_el1, x21 // set up the return data
413 msr spsr_el1, x22
414 ldp x0, x1, [sp, #16 * 0]
415 ldp x2, x3, [sp, #16 * 1]
416 ldp x4, x5, [sp, #16 * 2]
417 ldp x6, x7, [sp, #16 * 3]
418 ldp x8, x9, [sp, #16 * 4]
419 ldp x10, x11, [sp, #16 * 5]
420 ldp x12, x13, [sp, #16 * 6]
421 ldp x14, x15, [sp, #16 * 7]
422 ldp x16, x17, [sp, #16 * 8]
423 ldp x18, x19, [sp, #16 * 9]
424 ldp x20, x21, [sp, #16 * 10]
425 ldp x22, x23, [sp, #16 * 11]
426 ldp x24, x25, [sp, #16 * 12]
427 ldp x26, x27, [sp, #16 * 13]
428 ldp x28, x29, [sp, #16 * 14]
429
430 .if \el == 0
431#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
432 alternative_insn "b .L_skip_tramp_exit_\@", nop, ARM64_UNMAP_KERNEL_AT_EL0
433
434 msr far_el1, x29
435
436 ldr_this_cpu x30, this_cpu_vector, x29
437 tramp_alias x29, tramp_exit
438 msr vbar_el1, x30 // install vector table
439 ldr lr, [sp, #S_LR] // restore x30
440 add sp, sp, #PT_REGS_SIZE // restore sp
441 br x29
442
443.L_skip_tramp_exit_\@:
444#endif
445 .endif
446
447 ldr lr, [sp, #S_LR]
448 add sp, sp, #PT_REGS_SIZE // restore sp
449
450 .if \el == 0
451 /* This must be after the last explicit memory access */
452alternative_if ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD
453 tlbi vale1, xzr
454 dsb nsh
455alternative_else_nop_endif
456 .else
457 /* Ensure any device/NC reads complete */
458 alternative_insn nop, "dmb sy", ARM64_WORKAROUND_1508412
459 .endif
460
461 eret
462 sb
463 .endm
- L412
- 저장 프레임의 복귀 PC를 ELR_EL1에 설치합니다. 시그널·디버거 등이 pt_regs.pc를 바꾸었다면 변경된 위치로 ERET하게 됩니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L413
- 저장된 복귀 PSTATE를 SPSR_EL1에 설치합니다. ERET가 실행 레벨·마스크·flags를 이 값에서 복원합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L414
- [sp, #16 * 0]에서 16바이트를 읽어 x0, x1에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L415
- [sp, #16 * 1]에서 16바이트를 읽어 x2, x3에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L416
- [sp, #16 * 2]에서 16바이트를 읽어 x4, x5에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L417
- [sp, #16 * 3]에서 16바이트를 읽어 x6, x7에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L418
- [sp, #16 * 4]에서 16바이트를 읽어 x8, x9에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L419
- [sp, #16 * 5]에서 16바이트를 읽어 x10, x11에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L420
- [sp, #16 * 6]에서 16바이트를 읽어 x12, x13에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L421
- [sp, #16 * 7]에서 16바이트를 읽어 x14, x15에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L422
- [sp, #16 * 8]에서 16바이트를 읽어 x16, x17에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L423
- [sp, #16 * 9]에서 16바이트를 읽어 x18, x19에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L424
- [sp, #16 * 10]에서 16바이트를 읽어 x20, x21에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L425
- [sp, #16 * 11]에서 16바이트를 읽어 x22, x23에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L426
- [sp, #16 * 12]에서 16바이트를 읽어 x24, x25에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L427
- [sp, #16 * 13]에서 16바이트를 읽어 x26, x27에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L428
- [sp, #16 * 14]에서 16바이트를 읽어 x28, x29에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L430
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L431
- KPTI는 사용자 실행 중 커널의 넓은 매핑을 감추어 일부 추측 실행 정보 노출을 줄이는 기능입니다. 지원 빌드에서만 최소 trampoline으로 이동해 매핑을 제거하는 복귀 경로를 포함합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L432
- 실제 KPTI가 활성인 시스템에서는 skip 분기를 NOP로 바꿔 trampoline 복귀를 실행합니다. 비활성이면 일반 ERET 경로로 건너뜁니다. CONFIG 지원과 런타임 적용 여부는 다릅니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L434
- 사용자 x29를 FAR_EL1에 임시 보관합니다. 여기서는 fault 주소를 보고하는 것이 아니라 커널 매핑을 제거할 때 x29를 작업용으로 쓰기 위한 scratch 사용입니다. tramp_exit에서 되찾습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L436
- 이 CPU의 KPTI/BHB 정책에 맞는 진입 벡터 주소를 읽습니다. 다음 사용자 예외가 올바른 trampoline·완화 경로로 들어오게 할 값입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L437
- TRAMP_VALIAS와 섹션 내 심볼 오프셋을 더한 별칭 주소를 구성합니다. 인자: x29, tramp_exit.
- L438
- 다음 EL0 예외를 받을 trampoline 벡터 주소를 VBAR_EL1에 설치합니다. 커널 전체 매핑을 없앤 뒤에도 이 입구는 접근 가능해야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L439
- [sp, #S_LR]에서 8바이트를 읽어 lr에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L440
- sp ← sp + #PT_REGS_SIZE. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다.
- L441
- x29에 든 주소로 이동합니다. LR을 새로 만들지 않습니다. 현재 x29는 복구된 사용자 FP가 아니라 trampoline 별칭 주소입니다.
- L443
- .L_skip_tramp_exit_\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L444
- — 전처리 조건 구간을 끝냅니다.
- L445
- — 어셈블 조건 구간을 끝냅니다.
- L447
- [sp, #S_LR]에서 8바이트를 읽어 lr에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L448
- sp ← sp + #PT_REGS_SIZE. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다. pt_regs 프레임 공간을 해제합니다. 저장된 LR은 바로 앞에서 읽었습니다.
- L450
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L452
- speculative unprivileged load 관련 CPU 보완이 필요한 경우에만 다음 TLBI와 DSB를 남깁니다. 사용자 복귀 경계에서 잘못된 추측 접근 상태를 정리하는 CPU별 우회이며 일반적인 페이지 테이블 변경 flush로 설명하면 안 됩니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L453
- TLBI vale1, xzr: 지정 번역 영역의 TLB 엔트리를 무효화합니다. 주변 DSB/ISB가 완료와 이후 실행을 동기화합니다.
- L454
- DSB nsh: 지정 범위·종류의 앞선 메모리 및 관리 작업이 완료되기를 기다립니다.
- L455
- 452행에서 시작한 ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L456
- — 앞 어셈블 조건의 반대 부분입니다.
- L458
- erratum 1508412가 적용되는 CPU의 커널 복귀 경로에는 NOP 대신 DMB SY를 넣습니다. 정상 프로그램의 함수 인수가 아니라 영향을 받는 CPU에서 필요한 메모리 순서를 보완합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L459
- — 어셈블 조건 구간을 끝냅니다.
- L461
- 현재 예외 레벨의 ELR와 SPSR에 따라 PC와 실행 상태를 복원합니다. 복귀 EL과 SP 선택도 SPSR의 모드 비트에 따릅니다.
- L462
- ERET 뒤의 명령이 잘못된 직선 경로로 추측 실행되는 것을 막는 배리어입니다. SB 지원 CPU는 SB를, 미지원 CPU는 DSB NSH와 ISB를 사용하며 정상 복귀 경로는 이미 ERET로 떠납니다.
- L463
- — 매크로 정의를 끝냅니다.
소프트웨어 PAN과 tsk 별명
하드웨어 PAN이 없는 구성에서 TTBR0와 예약 ASID로 접근 차단 상태를 관리합니다. 저장된 PSTATE의 PAN 비트는 소프트웨어 표식이며 복귀 전 제거합니다. tsk는 x28의 별명입니다. .req는 새 저장 공간을 만들지 않습니다.
464
465#ifdef CONFIG_ARM64_SW_TTBR0_PAN
466 /*
467 * Set the TTBR0 PAN bit in SPSR. When the exception is taken from
468 * EL0, there is no need to check the state of TTBR0_EL1 since
469 * accesses are always enabled.
470 * Note that the meaning of this bit differs from the ARMv8.1 PAN
471 * feature as all TTBR0_EL1 accesses are disabled, not just those to
472 * user mappings.
473 */
474SYM_CODE_START_LOCAL(__swpan_entry_el1)
475 mrs x21, ttbr0_el1
476 tst x21, #TTBR_ASID_MASK // Check for the reserved ASID
477 orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR
478 b.eq 1f // TTBR0 access already disabled
479 and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR
480SYM_INNER_LABEL(__swpan_entry_el0, SYM_L_LOCAL)
481 __uaccess_ttbr0_disable x21
4821: ret
483SYM_CODE_END(__swpan_entry_el1)
484
485 /*
486 * Restore access to TTBR0_EL1. If returning to EL0, no need for SPSR
487 * PAN bit checking.
488 */
489SYM_CODE_START_LOCAL(__swpan_exit_el1)
490 tbnz x22, #22, 1f // Skip re-enabling TTBR0 access if the PSR_PAN_BIT is set
491 __uaccess_ttbr0_enable x0, x1
4921: and x22, x22, #~PSR_PAN_BIT // ARMv8.0 CPUs do not understand this bit
493 ret
494SYM_CODE_END(__swpan_exit_el1)
495
496SYM_CODE_START_LOCAL(__swpan_exit_el0)
497 __uaccess_ttbr0_enable x0, x1
498 /*
499 * Enable errata workarounds only if returning to user. The only
500 * workaround currently required for TTBR0_EL1 changes are for the
501 * Cavium erratum 27456 (broadcast TLBI instructions may cause I-cache
502 * corruption).
503 */
504 b post_ttbr_update_workaround
505SYM_CODE_END(__swpan_exit_el0)
506#endif
507
508/* GPRs used by entry code */
509tsk .req x28 // current thread_info
- L465
- 하드웨어 PAN이 없는 CPU에서도 사용자 주소에 대한 커널의 직접 접근을 막을 수 있도록 TTBR0 기반의 소프트웨어 PAN 함수들을 빌드합니다. 사용자 복사 helper가 필요할 때만 유효 TTBR0를 임시로 복원합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L474
- (__swpan_entry_el1)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L475
- 현재 TTBR0의 ASID 필드를 읽어 사용자 접근용 표가 이미 예약 상태로 비활성화되어 있었는지 알아냅니다. 단순 페이지 표 주소만 검사하는 것이 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L476
- 예약 ASID 여부를 검사합니다. 예약값이면 예외 전에도 소프트웨어 PAN이 사용자 접근을 막고 있었으므로 복귀 때도 그 상태를 유지해야 합니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.
- L477
- 저장 PSTATE에 모의 PAN 비트를 우선 세웁니다. 하드웨어 PAN이 없는 CPU에서 이 비트는 소프트웨어가 예외 전 사용자 접근 상태를 기록하는 용도입니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.
- L478
- 기존 NZCV가 Z=1이면 1f (L482)로 분기합니다. 그렇지 않으면 다음 명령을 실행합니다.
- L479
- TTBR0가 유효한 사용자 접근 상태였다면 모의 PAN 표식을 지웁니다. EL1으로 복귀할 때 원래 허용 상태를 되살릴 근거가 됩니다. AND는 두 피연산자의 같은 위치 비트가 모두 1인 곳만 결과에 남깁니다.
- L480
- (__swpan_entry_el0, SYM_L_LOCAL): 함수 안의 심볼을 정의합니다. 두 번째 인자가 외부 공개 여부를 결정하며 별도 함수 프레임·RET·BTI를 만들지 않습니다.
- L481
- 유효한 사용자 TTBR0를 예약된 표와 ASID로 바꾸어 일반 커널 코드의 사용자 메모리 접근을 막습니다. tmp x21을 쓰며 명시적인 uaccess에서만 다시 허용합니다.
- L482
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다. x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
- L483
- (__swpan_entry_el1)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L489
- (__swpan_exit_el1)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L490
- 저장한 모의 PAN bit 22가 켜져 있으면 예외 전에도 사용자 접근이 막혀 있었으므로 TTBR0를 다시 열지 않습니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L491
- 예외 전 커널이 uaccess 중이었다면 유효한 TTBR0를 복원해 그 접근을 계속할 수 있게 합니다. x0·x1은 그 전환에 쓰는 임시 레지스터입니다. 매크로는 현재 task에 저장된 TTBR0와 그 주소 공간의 ASID를 함께 복원합니다.
- L492
- 하드웨어 PAN을 모르는 ARMv8.0 CPU에 실제 PSTATE 예약 비트를 넘기지 않도록 모의 PAN 표식을 지웁니다. 소프트웨어 기록은 이미 TTBR0 복원 판단에 사용했습니다. AND는 두 피연산자의 같은 위치 비트가 모두 1인 곳만 결과에 남깁니다.
- L493
- x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
- L494
- (__swpan_exit_el1)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L496
- (__swpan_exit_el0)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L497
- 사용자 모드로 돌아가므로 task의 유효 TTBR0를 복원합니다. 사용자 자신의 메모리 접근을 소프트웨어 PAN의 예약 표로 막아 두어서는 안 됩니다. 매크로는 현재 task에 저장된 TTBR0와 그 주소 공간의 ASID를 함께 복원합니다.
- L504
- TTBR 변경 뒤 필요한 CPU별 보완으로 tail-call합니다. 이 함수에서 새 LR을 만들지 않아 보완 함수가 RET하면 원래 kernel_exit 호출 위치로 돌아갑니다.
- L505
- (__swpan_exit_el0)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L506
- — 전처리 조건 구간을 끝냅니다.
- L509
- tsk .req x28: 레지스터 별명을 정의합니다. 예를 들어 tsk는 x28을 가리킵니다.
16개 벡터와 잘못된 스택
4종류 예외×4개 진입 상태로 16개 슬롯을 생성합니다. 전체 테이블은 2 KiB 정렬입니다. __bad_stack은 overflow stack에서 pt_regs를 만들고 원래 SP를 보정해 handle_bad_stack에 전달합니다. 뒤의 ASM_BUG는 함수가 예상과 달리 반환할 때 예외를 발생시킵니다.
510
511 .text
512
513/*
514 * Exception vectors.
515 */
516 .pushsection ".entry.text", "ax"
517
518 .align 11
519SYM_CODE_START(vectors)
520 kernel_ventry 1, t, 64, sync // Synchronous EL1t
521 kernel_ventry 1, t, 64, irq // IRQ EL1t
522 kernel_ventry 1, t, 64, fiq // FIQ EL1t
523 kernel_ventry 1, t, 64, error // Error EL1t
524
525 kernel_ventry 1, h, 64, sync // Synchronous EL1h
526 kernel_ventry 1, h, 64, irq // IRQ EL1h
527 kernel_ventry 1, h, 64, fiq // FIQ EL1h
528 kernel_ventry 1, h, 64, error // Error EL1h
529
530 kernel_ventry 0, t, 64, sync // Synchronous 64-bit EL0
531 kernel_ventry 0, t, 64, irq // IRQ 64-bit EL0
532 kernel_ventry 0, t, 64, fiq // FIQ 64-bit EL0
533 kernel_ventry 0, t, 64, error // Error 64-bit EL0
534
535 kernel_ventry 0, t, 32, sync // Synchronous 32-bit EL0
536 kernel_ventry 0, t, 32, irq // IRQ 32-bit EL0
537 kernel_ventry 0, t, 32, fiq // FIQ 32-bit EL0
538 kernel_ventry 0, t, 32, error // Error 32-bit EL0
539SYM_CODE_END(vectors)
540
541SYM_CODE_START_LOCAL(__bad_stack)
542 /*
543 * We detected an overflow in kernel_ventry, which switched to the
544 * overflow stack. Stash the exception regs, and head to our overflow
545 * handler.
546 */
547
548 /* Restore the original x0 value */
549 mrs x0, tpidrro_el0
550
551 /*
552 * Store the original GPRs to the new stack. The orginal SP (minus
553 * PT_REGS_SIZE) was stashed in tpidr_el0 by kernel_ventry.
554 */
555 sub sp, sp, #PT_REGS_SIZE
556 kernel_entry 1
557 mrs x0, tpidr_el0
558 add x0, x0, #PT_REGS_SIZE
559 str x0, [sp, #S_SP]
560
561 /* Stash the regs for handle_bad_stack */
562 mov x0, sp
563
564 /* Time to die */
565 bl handle_bad_stack
566 ASM_BUG()
567SYM_CODE_END(__bad_stack)
- L511
- — 일반 코드 출력 섹션 .text로 전환합니다.
- L516
- ".entry.text", "ax" — 현재 섹션을 보관하고 지정 섹션으로 이동합니다.
- L518
- 전체 벡터 표를 2KiB 경계에 맞춥니다. 16개 입구 각각 128바이트라는 하드웨어 벡터 배치에 필요한 정렬입니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.
- L519
- (vectors)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다. 전체 16개 슬롯의 시작이며 VBAR_EL1이 가리키는 기준 주소입니다.
- L520
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.
- L521
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.
- L522
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.
- L523
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.
- L525
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.
- L526
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.
- L527
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.
- L528
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.
- L530
- EL0의 AArch64 사용자 실행 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.
- L531
- EL0의 AArch64 사용자 실행 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.
- L532
- EL0의 AArch64 사용자 실행 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.
- L533
- EL0의 AArch64 사용자 실행 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.
- L535
- EL0의 AArch32 사용자 실행 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.
- L536
- EL0의 AArch32 사용자 실행 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.
- L537
- EL0의 AArch32 사용자 실행 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.
- L538
- EL0의 AArch32 사용자 실행 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.
- L539
- (vectors)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L541
- (__bad_stack)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L549
- 정상 스택을 사용할 수 없던 시점에 TPIDRRO_EL0에 맡긴 원래 x0를 읽어 비정상 예외 문맥을 재구성합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L555
- sp ← sp − #PT_REGS_SIZE. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다.
- L556
- overflow stack에서 EL1 예외 프레임을 완성하여 오류 진단 코드가 레지스터를 안전하게 볼 수 있게 합니다. 손상된 원래 SP는 뒤에서 별도로 덮어써 프레임에 기록합니다.
- L557
- TPIDR_EL0에 맡긴 중단 당시 SP를 읽습니다. 현재 SP는 안전한 overflow stack이므로 둘을 구분해 오류 보고에 원래 값을 남깁니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L558
- x0 ← x0 + #PT_REGS_SIZE. NZCV는 유지합니다.
- L559
- x0의 값을 [sp, #S_SP]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L562
- x0 ← sp. 값을 복사합니다. NZCV는 유지합니다.
- L565
- x0의 pt_regs를 handle_bad_stack에 전달해 task 스택 넘침/손상 상태를 보고합니다. 이미 안전한 overflow stack에 옮겼으며 정상적인 사용자 복귀를 기대하는 경로가 아닙니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L566
- handle_bad_stack이 뜻하지 않게 반환해도 손상된 스택의 실행을 이어가지 않도록 BUG 예외를 발생시킵니다. 원래 예외로 정상 복귀하는 지점이 아닙니다. 어셈블 단계에는 진단용 BUG 메타데이터와 BRK 인코딩을 배치하고, 실행이 이 BRK에 도달하면 예외가 발생합니다.
- L567
- (__bad_stack)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
C 핸들러와 사용자·커널 복귀
entry_handler가 kernel_entry, x0=SP, C handler 호출, 복귀 경로 선택을 생성합니다. 세부 ESR 해석과 시스템 호출 분기는 entry-common.c에서 진행합니다. ret_to_user는 단일 단계 상태를 재확인하고 선택적으로 stackleak 정리를 한 뒤 kernel_exit 0을 실행합니다.
568
569
570 .macro entry_handler el:req, ht:req, regsize:req, label:req
571SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
572 kernel_entry \el, \regsize
573 mov x0, sp
574 bl el\el\ht\()_\regsize\()_\label\()_handler
575 .if \el == 0
576 b ret_to_user
577 .else
578 b ret_to_kernel
579 .endif
580SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
581 .endm
582
583/*
584 * Early exception handlers
585 */
586 entry_handler 1, t, 64, sync
587 entry_handler 1, t, 64, irq
588 entry_handler 1, t, 64, fiq
589 entry_handler 1, t, 64, error
590
591 entry_handler 1, h, 64, sync
592 entry_handler 1, h, 64, irq
593 entry_handler 1, h, 64, fiq
594 entry_handler 1, h, 64, error
595
596 entry_handler 0, t, 64, sync
597 entry_handler 0, t, 64, irq
598 entry_handler 0, t, 64, fiq
599 entry_handler 0, t, 64, error
600
601 entry_handler 0, t, 32, sync
602 entry_handler 0, t, 32, irq
603 entry_handler 0, t, 32, fiq
604 entry_handler 0, t, 32, error
605
606SYM_CODE_START_LOCAL(ret_to_kernel)
607 kernel_exit 1
608SYM_CODE_END(ret_to_kernel)
609
610SYM_CODE_START_LOCAL(ret_to_user)
611 ldr x19, [tsk, #TSK_TI_FLAGS] // re-check for single-step
612 enable_step_tsk x19, x2
613#ifdef CONFIG_KSTACK_ERASE
614 bl stackleak_erase_on_task_stack
615#endif
616 kernel_exit 0
617SYM_CODE_END(ret_to_user)
618
619 .popsection // .entry.text
- L570
- 벡터별 처리 본문을 만드는 매크로입니다. 예외 문맥을 저장하고 x0=pt_regs로 해당 C handler를 호출한 뒤, 중단된 EL에 맞춰 ret_to_user 또는 ret_to_kernel로 연결합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L571
- (el\el\ht\()_\regsize\()_\label)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L572
- 예외 이전 레지스터를 pt_regs에 저장하고 EL0/EL1에 맞는 current·제어 상태·메타데이터를 준비합니다. 인자: \el, \regsize.
- L573
- x0 ← sp. 값을 복사합니다. NZCV는 유지합니다. C 예외 핸들러의 첫 인자로 pt_regs 포인터를 전달합니다.
- L574
- el·ht·regsize·label로 조립한 예외 종류별 C handler를 호출합니다. x0는 완성된 pt_regs이며, 예를 들어 EL0 AArch64 sync는 ESR을 해석해 SVC·fault 등의 실제 처리로 분기합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L575
- \el == 0 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L576
- ret_to_user로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L577
- — 앞 어셈블 조건의 반대 부분입니다.
- L578
- ret_to_kernel로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L579
- — 어셈블 조건 구간을 끝냅니다.
- L580
- (el\el\ht\()_\regsize\()_\label)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L581
- — 매크로 정의를 끝냅니다.
- L586
- EL1t 커널 실행(SP_EL0 선택)에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L587
- EL1t 커널 실행(SP_EL0 선택)에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L588
- EL1t 커널 실행(SP_EL0 선택)에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L589
- EL1t 커널 실행(SP_EL0 선택)에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L591
- EL1h 커널 실행(SP_EL1 선택)에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L592
- EL1h 커널 실행(SP_EL1 선택)에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L593
- EL1h 커널 실행(SP_EL1 선택)에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L594
- EL1h 커널 실행(SP_EL1 선택)에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.
- L596
- EL0의 AArch64 사용자 실행에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L597
- EL0의 AArch64 사용자 실행에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L598
- EL0의 AArch64 사용자 실행에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L599
- EL0의 AArch64 사용자 실행에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L601
- EL0의 AArch32 사용자 실행에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L602
- EL0의 AArch32 사용자 실행에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L603
- EL0의 AArch32 사용자 실행에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L604
- EL0의 AArch32 사용자 실행에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.
- L606
- (ret_to_kernel)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L607
- pt_regs에서 복귀 상태를 복구하고 ERET합니다. EL0의 KPTI 구성은 trampoline 경로로 이어집니다. 인자: 1.
- L608
- (ret_to_kernel)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L610
- (ret_to_user)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L611
- [tsk, #TSK_TI_FLAGS]에서 8바이트를 읽어 x19에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L612
- 사용자 복귀 직전에 task의 TIF_SINGLESTEP를 다시 확인해 디버거가 요청한 단일 스텝 상태를 복구합니다. 커널 진입 때 꺼 두었던 스텝 상태와 대응합니다. 실제 설정 대상은 MDSCR_EL1.SS이며 TIF_SINGLESTEP이 있는 경우에만 이 비트를 세웁니다.
- L613
- KSTACK_ERASE는 syscall/예외 처리에서 사용한 커널 스택의 잔여값을 지워 정보 노출 위험을 줄이는 기능입니다. 켜진 빌드에서는 사용자 복귀 전에 정리 함수를 호출하고, 꺼지면 추가 스택 지우기 비용이 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L614
- 현재 task 스택에서 지울 수 있는 사용 흔적을 정리합니다. 예외 복귀에 필요한 활성 프레임을 무작정 0으로 만드는 것이 아니라 stackleak가 추적한 범위와 현재 SP를 고려하는 호출입니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L615
- — 전처리 조건 구간을 끝냅니다.
- L616
- pt_regs에서 복귀 상태를 복구하고 ERET합니다. EL0의 KPTI 구성은 trampoline 경로로 이어집니다. 인자: 0.
- L617
- (ret_to_user)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L619
- — 보관했던 이전 섹션으로 돌아갑니다.
KPTI 매핑 교체와 데이터 접근
tramp_map_kernel은 TTBR1에 TRAMP_SWAPPER_OFFSET을 더하고 USER_ASID_FLAG를 지웁니다. unmap은 반대 연산입니다. 이는 링커가 고정한 두 루트 사이의 간격에 의존합니다. 재배치 커널의 trampoline 상수는 별도 .entry.tramp.rodata 리터럴을 사용하고 비재배치 커널은 MOVZ/MOVK로 구성합니다.
620
621 // Move from tramp_pg_dir to swapper_pg_dir
622 .macro tramp_map_kernel, tmp
623 mrs \tmp, ttbr1_el1
624 add \tmp, \tmp, #TRAMP_SWAPPER_OFFSET
625 bic \tmp, \tmp, #USER_ASID_FLAG
626 msr ttbr1_el1, \tmp
627#ifdef CONFIG_QCOM_FALKOR_ERRATUM_1003
628alternative_if ARM64_WORKAROUND_QCOM_FALKOR_E1003
629 /* ASID already in \tmp[63:48] */
630 movk \tmp, #:abs_g2_nc:(TRAMP_VALIAS >> 12)
631 movk \tmp, #:abs_g1_nc:(TRAMP_VALIAS >> 12)
632 /* 2MB boundary containing the vectors, so we nobble the walk cache */
633 movk \tmp, #:abs_g0_nc:((TRAMP_VALIAS & ~(SZ_2M - 1)) >> 12)
634 isb
635 tlbi vae1, \tmp
636 dsb nsh
637alternative_else_nop_endif
638#endif /* CONFIG_QCOM_FALKOR_ERRATUM_1003 */
639 .endm
640
641 // Move from swapper_pg_dir to tramp_pg_dir
642 .macro tramp_unmap_kernel, tmp
643 mrs \tmp, ttbr1_el1
644 sub \tmp, \tmp, #TRAMP_SWAPPER_OFFSET
645 orr \tmp, \tmp, #USER_ASID_FLAG
646 msr ttbr1_el1, \tmp
647 /*
648 * We avoid running the post_ttbr_update_workaround here because
649 * it's only needed by Cavium ThunderX, which requires KPTI to be
650 * disabled.
651 */
652 .endm
653
654 .macro tramp_data_read_var dst, var
655#ifdef CONFIG_RELOCATABLE
656 ldr \dst, .L__tramp_data_\var
657 .ifndef .L__tramp_data_\var
658 .pushsection ".entry.tramp.rodata", "a", %progbits
659 .align 3
660.L__tramp_data_\var:
661 .quad \var
662 .popsection
663 .endif
664#else
665 /*
666 * As !RELOCATABLE implies !RANDOMIZE_BASE the address is always a
667 * compile time constant (and hence not secret and not worth hiding).
668 *
669 * As statically allocated kernel code and data always live in the top
670 * 47 bits of the address space we can sign-extend bit 47 and avoid an
671 * instruction to load the upper 16 bits (which must be 0xFFFF).
672 */
673 movz \dst, :abs_g2_s:\var
674 movk \dst, :abs_g1_nc:\var
675 movk \dst, :abs_g0_nc:\var
676#endif
677 .endm
- L622
- KPTI의 최소 trampoline 매핑에서 정상 커널 매핑으로 돌아가는 매크로입니다. TTBR1의 두 표 사이 고정 offset과 사용자 ASID 표식을 조정하며 필요한 CPU 보완을 수행합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L623
- 현재 trampoline 표를 가리키는 TTBR1을 읽습니다. 이 값을 기준으로 커널 swapper 표 주소를 계산하므로 별도 메모리 포인터를 먼저 읽지 않아도 됩니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L624
- 링커가 검증한 TRAMP_SWAPPER_OFFSET을 더해 trampoline 표에서 정상 swapper 페이지 표 루트로 이동합니다. 임의 메모리 offset이 아니라 두 표의 배치 차이입니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L625
- TTBR1에서 사용자 쪽 ASID 선택 표식을 지워 커널 쪽 번역 식별자를 사용하게 합니다. KPTI의 두 매핑 번역을 섞지 않도록 하는 전환입니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.
- L626
- 계산한 정상 커널 TTBR1을 설치합니다. 뒤에서 원래 커널 vectors와 데이터에 접근할 수 있게 만드는 핵심 매핑 복구입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L627
- Qualcomm Falkor erratum 1003 우회 지원을 빌드하면 KPTI 매핑 복구 뒤의 추가 TLBI 경로를 포함합니다. 꺼지면 이 CPU별 보완 코드는 생성되지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L628
- 실제로 Falkor E1003 영향을 받는 시스템에만 trampoline 영역 번역 무효화와 동기화를 남깁니다. 같은 CPU별 작업을 일반 KPTI 필수 작업과 구분합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L630
- \tmp의 지정된 16비트 조각에 #:abs_g2_nc:(TRAMP_VALIAS >> 12)를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L631
- \tmp의 지정된 16비트 조각에 #:abs_g1_nc:(TRAMP_VALIAS >> 12)를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L633
- \tmp의 지정된 16비트 조각에 #:abs_g0_nc:((TRAMP_VALIAS & ~(SZ_2M - 1)) >> 12)를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L634
- 변경된 실행 제어 상태를 반영하여 뒤 명령을 실행하도록 명령 흐름을 동기화합니다.
- L635
- 앞에서 만든 trampoline 영역 주소를 TLBI 피연산자로 사용해 관련 번역을 무효화합니다. Falkor E1003 보완의 일부이며 데이터 캐시 내용을 삭제하는 명령이 아닙니다. 페이지 테이블 메모리를 수정해도 이미 저장된 TLB 변환이 자동으로 없어지는 것은 아니므로 별도로 무효화합니다.
- L636
- DSB nsh: 지정 범위·종류의 앞선 메모리 및 관리 작업이 완료되기를 기다립니다.
- L637
- 628행에서 시작한 ARM64_WORKAROUND_QCOM_FALKOR_E1003 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L638
- — 전처리 조건 구간을 끝냅니다.
- L639
- — 매크로 정의를 끝냅니다.
- L642
- 사용자 복귀를 위해 정상 커널 TTBR1에서 최소 trampoline 표로 바꾸는 매크로입니다. 커널의 넓은 주소를 감추되 이 매크로 다음의 복귀 코드는 계속 실행할 수 있어야 합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L643
- 현재 정상 커널 루트와 ASID를 담은 TTBR1을 읽어 제한된 사용자 실행용 매핑으로 바꿀 기준으로 사용합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L644
- swapper와 trampoline 루트 사이의 고정 배치 차이를 빼서 최소 표 주소로 이동합니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L645
- 사용자 쪽 ASID 표식을 세워 최소 매핑의 번역을 커널 쪽 번역과 구분합니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.
- L646
- 사용자 실행용 최소 TTBR1을 설치합니다. 이후에는 일반 커널 데이터에 기대지 않고 trampoline 안에서 ERET까지 마쳐야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L652
- — 매크로 정의를 끝냅니다.
- L654
- 최소 trampoline 매핑에서 사용할 외부 기호 주소를 읽는 매크로입니다. 재배치 커널은 전용 rodata 슬롯을 통해 런타임 주소를 얻고, 고정 주소 커널은 MOVZ/MOVK로 주소 상수를 조립합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L655
- 재배치 가능한 커널은 적재 위치·KASLR에 따라 기호 주소가 달라질 수 있으므로 trampoline 전용 데이터 슬롯에 든 주소를 읽습니다. 꺼진 빌드는 고정 가상 주소를 명령 즉시값으로 만들 수 있습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L656
- trampoline rodata의 해당 기호 슬롯에서 재배치된 실제 주소를 읽습니다. 제한된 매핑 안에서도 사용할 수 있게 따로 둔 리터럴입니다. 이 LDR 리터럴 형식은 PC 기준으로 레이블 위치를 찾아 그곳의 메모리를 읽습니다. 레이블 주소 자체를 구하는 ADR과 구분합니다.
- L657
- .L__tramp_data_\var — 어셈블러 심볼이 정의되지 않았을 때 아래 부분을 생성합니다.
- L658
- ".entry.tramp.rodata", "a", %progbits — 현재 섹션을 보관하고 지정 섹션으로 이동합니다.
- L659
- 현재 위치를 2^(3)바이트 경계에 맞춥니다. 예: .align 7은 128바이트입니다.
- L660
- .L__tramp_data_\var 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L661
- 각 식 \var을 8바이트 데이터로 출력합니다. 심볼이나 식은 어셈블·링크 단계에서 계산됩니다.
- L662
- — 보관했던 이전 섹션으로 돌아갑니다.
- L663
- — 어셈블 조건 구간을 끝냅니다.
- L664
- 고정 주소 빌드에서는 기호의 가상 주소를 MOVZ/MOVK 즉시값으로 조립합니다. 재배치 포인터를 담은 별도 trampoline rodata 슬롯이 필요 없는 대안입니다.
- L673
- \dst의 상위 조각을 signed MOVW 재배치 \dst, :abs_g2_s:\var로 구성합니다. 음수 심볼 값을 나타내기 위해 링크 시 MOVN 형태가 사용될 수 있으며 뒤의 MOVK가 하위 조각을 채웁니다.
- L674
- \dst의 지정된 16비트 조각에 :abs_g1_nc:\var를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L675
- \dst의 지정된 16비트 조각에 :abs_g0_nc:\var를 넣습니다. 나머지 비트는 유지합니다. 재배치 modifier가 있으면 링크 시 심볼 값을 사용합니다.
- L676
- — 전처리 조건 구간을 끝냅니다.
- L677
- — 매크로 정의를 끝냅니다.
분기 이력 완화와 trampoline 슬롯
BHB 완화는 루프·펌웨어·명령 방식 중 선택한 형태를 생성합니다. KPTI 경로는 커널 매핑을 복구하고 VBAR를 원래 vectors로 바꿉니다. x30에 대응 벡터 주소+4를 만들어 RET하므로 kernel_ventry의 첫 B를 건너뛰고 정리 코드를 실행합니다. 이 RET는 일반 함수 호출자로 복귀하는 동작이 아닙니다.
678
679#define BHB_MITIGATION_NONE 0
680#define BHB_MITIGATION_LOOP 1
681#define BHB_MITIGATION_FW 2
682#define BHB_MITIGATION_INSN 3
683
684 .macro tramp_ventry, vector_start, regsize, kpti, bhb
685 .align 7
6861:
687 .if \regsize == 64
688 msr tpidrro_el0, x30 // Restored in kernel_ventry
689 .endif
690
691 .if \bhb == BHB_MITIGATION_LOOP
692 /*
693 * This sequence must appear before the first indirect branch. i.e. the
694 * ret out of tramp_ventry. It appears here because x30 is free.
695 */
696 __mitigate_spectre_bhb_loop x30
697 .endif // \bhb == BHB_MITIGATION_LOOP
698
699 .if \bhb == BHB_MITIGATION_INSN
700 clearbhb
701 isb
702 .endif // \bhb == BHB_MITIGATION_INSN
703
704 .if \kpti == 1
705 /*
706 * Defend against branch aliasing attacks by pushing a dummy
707 * entry onto the return stack and using a RET instruction to
708 * enter the full-fat kernel vectors.
709 */
710 bl 2f
711 b .
7122:
713 tramp_map_kernel x30
714alternative_insn isb, nop, ARM64_WORKAROUND_QCOM_FALKOR_E1003
715 tramp_data_read_var x30, vectors
716alternative_if_not ARM64_WORKAROUND_CAVIUM_TX2_219_PRFM
717 prfm plil1strm, [x30, #(1b - \vector_start)]
718alternative_else_nop_endif
719
720 msr vbar_el1, x30
721 isb
722 .else
723 adr_l x30, vectors
724 .endif // \kpti == 1
725
726 .if \bhb == BHB_MITIGATION_FW
727 /*
728 * The firmware sequence must appear before the first indirect branch.
729 * i.e. the ret out of tramp_ventry. But it also needs the stack to be
730 * mapped to save/restore the registers the SMC clobbers.
731 */
732 __mitigate_spectre_bhb_fw
733 .endif // \bhb == BHB_MITIGATION_FW
734
735 add x30, x30, #(1b - \vector_start + 4)
736 ret
737.org 1b + 128 // Did we overflow the ventry slot?
738 .endm
739
740 .macro generate_tramp_vector, kpti, bhb
741.Lvector_start\@:
742 .space 0x400
743
744 .rept 4
745 tramp_ventry .Lvector_start\@, 64, \kpti, \bhb
746 .endr
747 .rept 4
748 tramp_ventry .Lvector_start\@, 32, \kpti, \bhb
749 .endr
750 .endm
- L679
- BHB_MITIGATION_NONE=0은 분기 이력 완화를 추가하지 않은 벡터 변형을 고르는 생성 인수입니다. 런타임 오류 코드가 아닙니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.
- L680
- BHB_MITIGATION_LOOP=1은 CPU에 맞는 분기 반복으로 이력 영향을 줄이는 벡터 변형입니다. 반복 자체를 보안 목적의 진입 코드로 생성합니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.
- L681
- BHB_MITIGATION_FW=2는 펌웨어 SMCCC 완화를 호출하는 벡터 변형입니다. LOOP 방식과 동시에 모두 실행하라는 뜻이 아니라 별도 후보를 구분합니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.
- L682
- BHB_MITIGATION_INSN=3은 CLEARBHB 명령으로 분기 이력을 정리하는 벡터 변형입니다. 지원 방식에 맞는 벡터가 이후 CPU별로 선택됩니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.
- L684
- 사용자 예외용 trampoline 입구를 생성합니다. regsize는 원래 사용자 상태의 폭, kpti는 매핑 교체 여부, bhb는 Spectre 분기 이력 완화 방식을 선택하며 vector_start로 같은 예외 슬롯의 정상 벡터 offset을 계산합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L685
- 현재 위치를 2^(7)바이트 경계에 맞춥니다. 예: .align 7은 128바이트입니다.
- L686
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L687
- \regsize == 64 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L688
- 최소 매핑에서 x30을 임시로 사용하기 전에 TPIDRRO_EL0에 사용자 값을 보관합니다. 정상 kernel_ventry에 도착하면 앞의 복구 코드가 다시 x30으로 읽습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L689
- — 어셈블 조건 구간을 끝냅니다.
- L691
- \bhb == BHB_MITIGATION_LOOP — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L696
- CPU별로 준비된 분기 반복을 실행해 사용자 분기 이력이 커널의 추측 실행에 미치는 영향을 줄입니다. x30은 아직 원래 값을 TPIDRRO_EL0에 보관해 둔 임시 레지스터입니다. 반복 횟수는 CPU에 필요한 값으로 패치됩니다.
- L697
- — 어셈블 조건 구간을 끝냅니다.
- L699
- \bhb == BHB_MITIGATION_INSN — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L700
- 지원 CPU에서 분기 이력 상태를 정리하는 CLEARBHB를 실행합니다. 일반 메모리 캐시를 비우는 명령이 아니며 다음 ISB와 함께 진입 완화를 구성합니다. 매크로는 HINT #22 인코딩을 출력합니다.
- L701
- 변경된 실행 제어 상태를 반영하여 뒤 명령을 실행하도록 명령 흐름을 동기화합니다.
- L702
- — 어셈블 조건 구간을 끝냅니다.
- L704
- \kpti == 1 — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L710
- 바로 아래 지역 라벨을 BL로 호출해 반환 예측용 이력을 준비합니다. 일반 서비스 함수 호출이 아니라 trampoline의 추측 실행 방어 순서를 만드는 제어 흐름입니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L711
- .로 무조건 분기합니다. LR은 바꾸지 않습니다. 현재 위치를 계속 반복합니다.
- L712
- 2 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L713
- trampoline 표에서 정상 커널 표로 TTBR1을 전환합니다. 원래 vectors 주소와 커널 데이터에 접근하기 전에 매핑을 복구해야 합니다.
- L714
- Falkor E1003 경로는 tramp_map_kernel 안에서 필요한 ISB를 이미 수행하므로 이 추가 ISB를 NOP로 바꿉니다. 다른 CPU에는 TTBR 갱신 뒤 실행 동기화를 위해 ISB가 남습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L715
- 재배치 여부에 맞는 방법으로 정상 커널 vectors 주소를 x30에 가져옵니다. 최소 trampoline에서 커널 진입점으로 연결할 목적지입니다. 재배치 구성에서는 별도 rodata의 리터럴을 읽고, 그렇지 않은 구성에서는 MOVZ/MOVK로 주소를 구성합니다.
- L716
- ThunderX2의 특정 prefetch 문제 보완이 필요한 CPU에서는 다음 코드 prefetch를 NOP로 대체합니다. 영향을 받지 않는 경우만 정상 벡터의 명령 가져오기를 미리 요청합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L717
- 정상 벡터 표에서 현재 예외와 같은 슬롯의 명령을 instruction cache 쪽으로 prefetch합니다. 실제 handler 호출을 수행하는 것은 아니며 CPU 보완 조건에 따라 빠질 수 있습니다. 이는 미리 가져오라는 힌트이며 LDR처럼 결과를 일반 레지스터에 넣지 않습니다.
- L718
- 716행에서 시작한 ARM64_WORKAROUND_CAVIUM_TX2_219_PRFM 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L720
- VBAR_EL1을 정상 커널 벡터 표로 바꿔 이후 커널 실행 중의 예외도 정상 입구로 들어오게 합니다. 이어지는 ISB가 새 벡터 설정을 반영합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L721
- 변경된 실행 제어 상태를 반영하여 뒤 명령을 실행하도록 명령 흐름을 동기화합니다.
- L722
- — 앞 어셈블 조건의 반대 부분입니다.
- L723
- ADRP와 ADD로 심볼 주소를 구성합니다. 임시 페이지 기준 주소에 하위 12비트를 더합니다. 인자: x30, vectors.
- L724
- — 어셈블 조건 구간을 끝냅니다.
- L726
- \bhb == BHB_MITIGATION_FW — 어셈블 단계에서 식을 검사해 포함할 코드를 고릅니다.
- L732
- 이 CPU가 사용하는 펌웨어 BHB 완화를 호출합니다. LOOP 또는 CLEARBHB를 선택한 별도 벡터에는 이 호출이 생성되지 않습니다. 요청에 사용하는 x0~x3는 스택에 보존했다가 펌웨어에서 돌아온 뒤 복원합니다.
- L733
- — 어셈블 조건 구간을 끝냅니다.
- L735
- 현재 trampoline 슬롯과 같은 종류의 정상 벡터 위치를 계산하되 첫 분기 4바이트는 건너뜁니다. 그래야 정상 kernel_ventry의 trampoline 복구 부분으로 들어갑니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L736
- x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다. 여기서 RET는 예외 복귀 ERET가 아니라 x30에 계산한 kernel vector 내부 주소로 이동합니다.
- L737
- 1b + 128 — 출력 위치를 지정값까지 이동합니다. 이미 지나쳤다면 어셈블 오류가 발생할 수 있습니다.
- L738
- — 매크로 정의를 끝냅니다.
- L740
- KPTI 사용 여부와 BHB 완화 방식 한 조합의 벡터 표를 생성합니다. 사용하지 않는 EL1 슬롯 0x400바이트를 건너뛰고 AArch64·AArch32 EL0 예외 입구를 네 개씩 만듭니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L741
- .Lvector_start\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L742
- 0x400 — 지정 크기만큼 공간을 채웁니다.
- L744
- 4 — 지정 횟수만큼 아래 코드를 반복 생성합니다.
- L745
- AArch64 사용자 예외의 128바이트 진입 슬롯을 만듭니다. kpti 인수에 따라 최소 매핑에서 정상 매핑으로 전환하고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.
- L746
- — 반복 생성 구간을 끝냅니다.
- L747
- 4 — 지정 횟수만큼 아래 코드를 반복 생성합니다.
- L748
- AArch32 사용자 예외의 128바이트 진입 슬롯을 만듭니다. kpti 인수에 따라 최소 매핑에서 정상 매핑으로 전환하고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.
- L749
- — 반복 생성 구간을 끝냅니다.
- L750
- — 매크로 정의를 끝냅니다.
trampoline 테이블과 EL1 완화 벡터
generate_tramp_vector는 첫 0x400바이트를 비워 둔 뒤 하위 EL의 64비트·32비트 슬롯을 생성합니다. KPTI 출구 tramp_exit는 커널 매핑을 제거하고 FAR_EL1에 보존한 x29를 읽은 뒤 ERET합니다. 별도의 __bp_harden_el1_vectors는 KPTI를 사용하지 않는 경우의 BHB 완화 벡터입니다.
751
752#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
753/*
754 * Exception vectors trampoline.
755 * The order must match __bp_harden_el1_vectors and the
756 * arm64_bp_harden_el1_vectors enum.
757 */
758 .pushsection ".entry.tramp.text", "ax"
759 .align 11
760SYM_CODE_START_LOCAL_NOALIGN(tramp_vectors)
761#ifdef CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY
762 generate_tramp_vector kpti=1, bhb=BHB_MITIGATION_LOOP
763 generate_tramp_vector kpti=1, bhb=BHB_MITIGATION_FW
764 generate_tramp_vector kpti=1, bhb=BHB_MITIGATION_INSN
765#endif /* CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY */
766 generate_tramp_vector kpti=1, bhb=BHB_MITIGATION_NONE
767SYM_CODE_END(tramp_vectors)
768
769SYM_CODE_START_LOCAL(tramp_exit)
770 tramp_unmap_kernel x29
771 mrs x29, far_el1 // restore x29
772 eret
773 sb
774SYM_CODE_END(tramp_exit)
775 .popsection // .entry.tramp.text
776#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
777
778/*
779 * Exception vectors for spectre mitigations on entry from EL1 when
780 * kpti is not in use.
781 */
782 .macro generate_el1_vector, bhb
783.Lvector_start\@:
784 kernel_ventry 1, t, 64, sync // Synchronous EL1t
785 kernel_ventry 1, t, 64, irq // IRQ EL1t
786 kernel_ventry 1, t, 64, fiq // FIQ EL1h
787 kernel_ventry 1, t, 64, error // Error EL1t
788
789 kernel_ventry 1, h, 64, sync // Synchronous EL1h
790 kernel_ventry 1, h, 64, irq // IRQ EL1h
791 kernel_ventry 1, h, 64, fiq // FIQ EL1h
792 kernel_ventry 1, h, 64, error // Error EL1h
793
794 .rept 4
795 tramp_ventry .Lvector_start\@, 64, 0, \bhb
796 .endr
797 .rept 4
798 tramp_ventry .Lvector_start\@, 32, 0, \bhb
799 .endr
800 .endm
801
802/* The order must match tramp_vecs and the arm64_bp_harden_el1_vectors enum. */
803 .pushsection ".entry.text", "ax"
804 .align 11
805SYM_CODE_START(__bp_harden_el1_vectors)
806#ifdef CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY
807 generate_el1_vector bhb=BHB_MITIGATION_LOOP
808 generate_el1_vector bhb=BHB_MITIGATION_FW
809 generate_el1_vector bhb=BHB_MITIGATION_INSN
810#endif /* CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY */
811SYM_CODE_END(__bp_harden_el1_vectors)
812 .popsection
813
- L752
- KPTI를 지원하는 빌드에서만 커널 전체 매핑이 없어도 접근할 trampoline 벡터와 복귀 코드를 전용 섹션에 둡니다. 설정이 꺼지면 이 최소 매핑 경로는 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L758
- ".entry.tramp.text", "ax" — 현재 섹션을 보관하고 지정 섹션으로 이동합니다.
- L759
- 현재 위치를 2^(11)바이트 경계에 맞춥니다. 예: .align 7은 128바이트입니다.
- L760
- (tramp_vectors)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 추가 정렬 없이 배치합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L761
- Spectre 분기 이력 완화 지원 빌드에는 LOOP·펌웨어·명령 방식의 KPTI 벡터 후보를 모두 생성합니다. 실제로 매번 모두 실행하는 것이 아니라 CPU·정책에 맞는 후보를 선택할 수 있게 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L762
- 분기 반복 LOOP 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L763
- 펌웨어 호출 FW 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L764
- CLEARBHB 명령 INSN 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L765
- — 전처리 조건 구간을 끝냅니다.
- L766
- 추가 BHB 완화가 없는 NONE 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L767
- (tramp_vectors)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L769
- (tramp_exit)의 시작 심볼을 정의합니다. LOCAL은 파일 내부 심볼입니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L770
- TTBR1에서 고정 오프셋을 빼고 USER_ASID_FLAG를 설정해 trampoline 매핑으로 전환합니다. 인자: x29.
- L771
- kernel_exit가 FAR_EL1에 임시 보관한 사용자 x29를 되찾습니다. 여기서 FAR는 fault 주소가 아니며, 매핑 제거에 쓴 x29를 원래 사용자 값으로 돌린 뒤 ERET합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L772
- 현재 예외 레벨의 ELR와 SPSR에 따라 PC와 실행 상태를 복원합니다. 복귀 EL과 SP 선택도 SPSR의 모드 비트에 따릅니다.
- L773
- trampoline ERET 다음에 추측 실행 방지 배리어를 둡니다. 정상 실행은 ERET로 사용자 공간에 갔으며, CPU의 잘못된 직선 추측이 뒤 코드를 진행하지 못하게 합니다.
- L774
- (tramp_exit)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L775
- — 보관했던 이전 섹션으로 돌아갑니다.
- L776
- — 전처리 조건 구간을 끝냅니다.
- L782
- KPTI 없이도 사용자 예외에 BHB 완화를 적용할 벡터 표를 만드는 매크로입니다. EL1 예외 입구는 정상 kernel_ventry로 만들고 EL0 입구는 지정된 bhb 완화를 포함합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L783
- .Lvector_start\@ 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L784
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.
- L785
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.
- L786
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.
- L787
- EL1t 커널 실행(SP_EL0 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.
- L789
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.
- L790
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.
- L791
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.
- L792
- EL1h 커널 실행(SP_EL1 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.
- L794
- 4 — 지정 횟수만큼 아래 코드를 반복 생성합니다.
- L795
- AArch64 사용자 예외의 128바이트 진입 슬롯을 만듭니다. KPTI 매핑 전환은 하지 않고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.
- L796
- — 반복 생성 구간을 끝냅니다.
- L797
- 4 — 지정 횟수만큼 아래 코드를 반복 생성합니다.
- L798
- AArch32 사용자 예외의 128바이트 진입 슬롯을 만듭니다. KPTI 매핑 전환은 하지 않고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.
- L799
- — 반복 생성 구간을 끝냅니다.
- L800
- — 매크로 정의를 끝냅니다.
- L803
- ".entry.text", "ax" — 현재 섹션을 보관하고 지정 섹션으로 이동합니다.
- L804
- 현재 위치를 2^(11)바이트 경계에 맞춥니다. 예: .align 7은 128바이트입니다.
- L805
- (__bp_harden_el1_vectors)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L806
- BHB 완화를 지원하는 빌드에만 KPTI 없는 경우의 LOOP·펌웨어·명령 벡터 후보를 생성합니다. 커널 매핑을 감추는 기능과 분기 이력 완화는 별개이므로 이 표가 따로 필요합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L807
- 분기 반복 LOOP 방식의 벡터 표 후보를 생성합니다. KPTI 없이 커널 매핑을 유지하는 경우의 진입입니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L808
- 펌웨어 호출 FW 방식의 벡터 표 후보를 생성합니다. KPTI 없이 커널 매핑을 유지하는 경우의 진입입니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L809
- CLEARBHB 명령 INSN 방식의 벡터 표 후보를 생성합니다. KPTI 없이 커널 매핑을 유지하는 경우의 진입입니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.
- L810
- — 전처리 조건 구간을 끝냅니다.
- L811
- (__bp_harden_el1_vectors)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L812
- — 보관했던 이전 섹션으로 돌아갑니다.
cpu_switch_to: 태스크 상태 교체
x0는 이전 task_struct, x1은 다음 task_struct입니다. x19–x29, SP, LR을 이전 태스크의 THREAD_CPU_CONTEXT에 저장하고 다음 태스크의 값을 읽습니다. SP_EL0를 다음 태스크로 바꾸고 PAC/SCS를 교체합니다. x0는 이전 태스크 포인터를 유지합니다. RET는 다음 태스크에 저장되어 있던 LR로 이어집니다.
814
815/*
816 * Register switch for AArch64. The callee-saved registers need to be saved
817 * and restored. On entry:
818 * x0 = previous task_struct (must be preserved across the switch)
819 * x1 = next task_struct
820 * Previous and next are guaranteed not to be the same.
821 *
822 */
823SYM_FUNC_START(cpu_switch_to)
824 save_and_disable_daif x11
825 mov x10, #THREAD_CPU_CONTEXT
826 add x8, x0, x10
827 mov x9, sp
828 stp x19, x20, [x8], #16 // store callee-saved registers
829 stp x21, x22, [x8], #16
830 stp x23, x24, [x8], #16
831 stp x25, x26, [x8], #16
832 stp x27, x28, [x8], #16
833 stp x29, x9, [x8], #16
834 str lr, [x8]
835 add x8, x1, x10
836 ldp x19, x20, [x8], #16 // restore callee-saved registers
837 ldp x21, x22, [x8], #16
838 ldp x23, x24, [x8], #16
839 ldp x25, x26, [x8], #16
840 ldp x27, x28, [x8], #16
841 ldp x29, x9, [x8], #16
842 ldr lr, [x8]
843 mov sp, x9
844 msr sp_el0, x1
845 ptrauth_keys_install_kernel x1, x8, x9, x10
846 scs_save x0
847 scs_load_current
848 restore_irq x11
849 ret
850SYM_FUNC_END(cpu_switch_to)
851NOKPROBE(cpu_switch_to)
- L823
- (cpu_switch_to)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다.
- L824
- DAIF를 인자 레지스터에 저장하고 DAIFSet #0xf로 D/A/I/F를 마스크합니다. 인자: x11. SP/current가 교체되는 동안 예외 관찰 상태가 섞이지 않도록 DAIF를 보존하고 마스크합니다.
- L825
- x10 ← #THREAD_CPU_CONTEXT. 값을 복사합니다. NZCV는 유지합니다.
- L826
- x8 ← x0 + x10. NZCV는 유지합니다.
- L827
- x9 ← sp. 값을 복사합니다. NZCV는 유지합니다.
- L828
- x19, x20의 값을 [x8], #16에 16바이트 저장합니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L829
- x21, x22의 값을 [x8], #16에 16바이트 저장합니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L830
- x23, x24의 값을 [x8], #16에 16바이트 저장합니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L831
- x25, x26의 값을 [x8], #16에 16바이트 저장합니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L832
- x27, x28의 값을 [x8], #16에 16바이트 저장합니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L833
- x29, x9의 값을 [x8], #16에 16바이트 저장합니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다. 이전 태스크의 FP와 SP를 연속해서 저장합니다.
- L834
- lr의 값을 [x8]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L835
- x8 ← x1 + x10. NZCV는 유지합니다.
- L836
- [x8], #16에서 16바이트를 읽어 x19, x20에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L837
- [x8], #16에서 16바이트를 읽어 x21, x22에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L838
- [x8], #16에서 16바이트를 읽어 x23, x24에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L839
- [x8], #16에서 16바이트를 읽어 x25, x26에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L840
- [x8], #16에서 16바이트를 읽어 x27, x28에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L841
- [x8], #16에서 16바이트를 읽어 x29, x9에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L842
- [x8]에서 8바이트를 읽어 lr에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L843
- sp ← x9. 값을 복사합니다. NZCV는 유지합니다. 이 시점부터 다음 태스크의 커널 스택을 사용합니다.
- L844
- SP_EL0에 다음 task_struct를 기록하여 커널의 current 조회를 새 task로 바꿉니다. 실제 스택은 앞의 MOV SP로 이미 바뀌었으며 SP_EL0를 그 스택 주소로 쓰는 문맥이 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L845
- x1이 가리키는 다음 task의 커널 instruction-address 인증 키를 설치합니다. x8~x10은 임시 레지스터입니다. 태스크마다 다른 PAC 키를 쓰므로 SP/current를 바꾼 전환에서 이전 task의 키를 그대로 유지하면 안 됩니다.
- L846
- SHADOW_CALL_STACK 설정에서 x18의 shadow stack 포인터를 태스크에 저장합니다. 비활성 구성에서는 비어 있습니다. 인자: x0.
- L847
- 현재 태스크의 shadow stack 포인터를 x18에 읽습니다. 일반 SP와 별도인 반환 주소 보호용 스택입니다. 인자: 없음.
- L848
- 저장했던 인자 값을 DAIF에 다시 씁니다. 이름은 IRQ이지만 이 정의는 DAIF 전체를 복원합니다. 인자: x11.
- L849
- x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다. 다음 태스크에 저장된 LR로 실행을 이어갑니다. x0는 이전 태스크 포인터를 유지합니다.
- L850
- (cpu_switch_to)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L851
- cpu_switch_to을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
fork 뒤의 첫 실행
schedule_tail 이후 x19가 0이 아니면 커널 스레드 함수 주소로 사용하고 x20을 첫 인자로 전달합니다. 그 후 현재 태스크와 pt_regs를 기준으로 asm_exit_to_user_mode, ret_to_user를 거칩니다.
852
853/*
854 * This is how we return from a fork.
855 */
856SYM_CODE_START(ret_from_fork)
857 bl schedule_tail
858 cbz x19, 1f // not a kernel thread
859 mov x0, x20
860 blr x19
8611: get_current_task tsk
862 mov x0, sp
863 bl asm_exit_to_user_mode
864 b ret_to_user
865SYM_CODE_END(ret_from_fork)
866NOKPROBE(ret_from_fork)
- L856
- (ret_from_fork)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L857
- 새 task가 처음 실행되는 경로에서 schedule_tail을 호출하여 이전 task와의 스케줄 전환 마무리와 첫 실행 bookkeeping을 완료합니다. 일반 task가 이미 한 번 실행한 뒤의 cpu_switch_to 복귀와 구분합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L858
- x19가 0이면 1f (L861)로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L859
- x0 ← x20. 값을 복사합니다. NZCV는 유지합니다.
- L860
- 커널 스레드라면 x19의 시작 함수를 x0=x20 인수로 호출합니다. x19가 0인 사용자 task는 앞의 CBZ로 이 호출을 건너뛰고 사용자 복귀 준비를 합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L861
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다. SP_EL0에서 현재 task_struct 포인터를 읽습니다. 인자: tsk.
- L862
- x0 ← sp. 값을 복사합니다. NZCV는 유지합니다.
- L863
- x0=현재 pt_regs를 전달해 처음 사용자 실행으로 나가기 전 필요한 exit-to-user 작업을 처리합니다. 그 다음 ret_to_user가 실제 레지스터와 예외 상태를 복원합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.
- L864
- ret_to_user로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L865
- (ret_from_fork)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L866
- ret_from_fork을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
IRQ 스택에서 함수 호출
현재 스택에 FP/LR 프레임을 만들고 CPU별 irq_stack_ptr+IRQ_STACK_SIZE를 SP로 선택합니다. x0의 pt_regs는 유지한 채 x1의 함수를 호출합니다. 호출 전후 DAIF를 관리하고 SCS를 사용하는 경우 IRQ용 shadow stack도 바꿉니다. 복귀 시 x29로 원래 SP를 찾아 프레임을 해제합니다.
867
868/*
869 * void call_on_irq_stack(struct pt_regs *regs,
870 * void (*func)(struct pt_regs *));
871 *
872 * Calls func(regs) using this CPU's irq stack and shadow irq stack.
873 */
874SYM_FUNC_START(call_on_irq_stack)
875 save_and_disable_daif x9
876#ifdef CONFIG_SHADOW_CALL_STACK
877 get_current_task x16
878 scs_save x16
879 ldr_this_cpu scs_sp, irq_shadow_call_stack_ptr, x17
880#endif
881
882 /* Create a frame record to save our LR and SP (implicit in FP) */
883 stp x29, x30, [sp, #-16]!
884 mov x29, sp
885
886 ldr_this_cpu x16, irq_stack_ptr, x17
887
888 /* Move to the new stack and call the function there */
889 add sp, x16, #IRQ_STACK_SIZE
890 restore_irq x9
891 blr x1
892
893 save_and_disable_daif x9
894 /*
895 * Restore the SP from the FP, and restore the FP and LR from the frame
896 * record.
897 */
898 mov sp, x29
899 ldp x29, x30, [sp], #16
900 scs_load_current
901 restore_irq x9
902 ret
903SYM_FUNC_END(call_on_irq_stack)
904NOKPROBE(call_on_irq_stack)
905
- L874
- (call_on_irq_stack)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다.
- L875
- DAIF를 인자 레지스터에 저장하고 DAIFSet #0xf로 D/A/I/F를 마스크합니다. 인자: x9.
- L876
- Shadow Call Stack은 일반 스택과 별도 영역에 복귀 주소를 보관하는 보호 기법입니다. 켜진 빌드에서는 task의 SCS 포인터를 저장하고 IRQ 전용 SCS를 사용하며, 꺼지면 일반 IRQ 스택 전환만 수행합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L877
- SP_EL0에서 현재 task_struct 포인터를 읽습니다. 인자: x16.
- L878
- SHADOW_CALL_STACK 설정에서 x18의 shadow stack 포인터를 태스크에 저장합니다. 비활성 구성에서는 비어 있습니다. 인자: x16.
- L879
- CPU별 IRQ 전용 shadow call stack 포인터를 읽습니다. IRQ 처리의 복귀 주소가 task의 SCS 공간을 계속 소비하지 않도록 일반 IRQ 스택과 함께 보호용 스택도 바꿉니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L880
- — 전처리 조건 구간을 끝냅니다.
- L883
- x29, x30의 값을 [sp, #-16]!에 16바이트 저장합니다. 대괄호 안의 오프셋을 기준 레지스터에 먼저 반영한 뒤 접근합니다. 원래 스택에서 만든 16바이트 프레임이 IRQ 스택 사용 후 돌아갈 위치를 보존합니다.
- L884
- x29 ← sp. 값을 복사합니다. NZCV는 유지합니다.
- L886
- 현재 CPU의 IRQ 스택 시작을 읽습니다. task 스택에서 IRQ 처리 깊이가 과도하게 늘지 않도록 별도 스택으로 작업을 옮길 준비입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L889
- sp ← x16 + #IRQ_STACK_SIZE. NZCV는 유지합니다. IRQ 스택 끝을 SP로 선택합니다. 이후 저장할 때 SP는 낮은 주소로 이동합니다.
- L890
- 저장했던 인자 값을 DAIF에 다시 씁니다. 이름은 IRQ이지만 이 정의는 DAIF 전체를 복원합니다. 인자: x9.
- L891
- x0의 pt_regs를 그대로 인수로 전달해 x1의 IRQ 처리 함수를 호출합니다. SP와 필요 시 SCS는 이미 이 CPU의 IRQ 전용 공간으로 바뀌었고, 반환 뒤 원래 task 스택을 복구합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
- L893
- DAIF를 인자 레지스터에 저장하고 DAIFSet #0xf로 D/A/I/F를 마스크합니다. 인자: x9.
- L898
- sp ← x29. 값을 복사합니다. NZCV는 유지합니다.
- L899
- [sp], #16에서 16바이트를 읽어 x29, x30에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
- L900
- 현재 태스크의 shadow stack 포인터를 x18에 읽습니다. 일반 SP와 별도인 반환 주소 보호용 스택입니다. 인자: 없음.
- L901
- 저장했던 인자 값을 DAIF에 다시 씁니다. 이름은 IRQ이지만 이 정의는 DAIF 전체를 복원합니다. 인자: x9.
- L902
- x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
- L903
- (call_on_irq_stack)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L904
- call_on_irq_stack을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
SDEI 진입·복귀 trampoline
SDEI는 펌웨어가 위임하는 이벤트 경로입니다. 필요한 경우 커널 매핑을 되살린 후 이벤트 구조체에 원래 TTBR1 상태를 기록합니다. exit trampoline은 기록을 보고 매핑을 되돌립니다. sdei_handler_exit는 SMC/HVC를 선택하고, 성공 시 반환하지 않는 호출 뒤에 자기 자신으로 도는 B를 둡니다.
906#ifdef CONFIG_ARM_SDE_INTERFACE
907
908#include <asm/sdei.h>
909#include <uapi/linux/arm_sdei.h>
910
911.macro sdei_handler_exit exit_mode
912 /* On success, this call never returns... */
913 cmp \exit_mode, #SDEI_EXIT_SMC
914 b.ne 99f
915 smc #0
916 b .
91799: hvc #0
918 b .
919.endm
920
921#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
922/*
923 * The regular SDEI entry point may have been unmapped along with the rest of
924 * the kernel. This trampoline restores the kernel mapping to make the x1 memory
925 * argument accessible.
926 *
927 * This clobbers x4, __sdei_handler() will restore this from firmware's
928 * copy.
929 */
930.pushsection ".entry.tramp.text", "ax"
931SYM_CODE_START(__sdei_asm_entry_trampoline)
932 mrs x4, ttbr1_el1
933 tbz x4, #USER_ASID_BIT, 1f
934
935 tramp_map_kernel tmp=x4
936 isb
937 mov x4, xzr
938
939 /*
940 * Remember whether to unmap the kernel on exit.
941 */
9421: str x4, [x1, #(SDEI_EVENT_INTREGS + S_SDEI_TTBR1)]
943 tramp_data_read_var x4, __sdei_asm_handler
944 br x4
945SYM_CODE_END(__sdei_asm_entry_trampoline)
946NOKPROBE(__sdei_asm_entry_trampoline)
947
948/*
949 * Make the exit call and restore the original ttbr1_el1
950 *
951 * x0 & x1: setup for the exit API call
952 * x2: exit_mode
953 * x4: struct sdei_registered_event argument from registration time.
954 */
955SYM_CODE_START(__sdei_asm_exit_trampoline)
956 ldr x4, [x4, #(SDEI_EVENT_INTREGS + S_SDEI_TTBR1)]
957 cbnz x4, 1f
958
959 tramp_unmap_kernel tmp=x4
960
9611: sdei_handler_exit exit_mode=x2
962SYM_CODE_END(__sdei_asm_exit_trampoline)
963NOKPROBE(__sdei_asm_exit_trampoline)
964.popsection // .entry.tramp.text
965#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
966
- L906
- SDEI는 펌웨어가 운영체제에 소프트웨어 위임 예외 이벤트를 전달하는 인터페이스입니다. 켜진 빌드에만 별도 이벤트 스택·저장 문맥·SMC/HVC 완료 경로를 포함하며, 일반 장치 IRQ의 GIC handler와는 다른 진입 규약입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L908
- SDEI 이벤트의 커널 저장 형식·우선순위·스택 관련 정의를 가져옵니다. 일반 IRQ와 다른 펌웨어 이벤트 진입 문맥을 보존하는 데 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L909
- 펌웨어와 주고받는 SDEI 서비스 번호와 완료 상태 등 인터페이스 상수를 가져옵니다. EVENT_COMPLETE와 COMPLETE_AND_RESUME 같은 요청을 구분하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L911
- SDEI 이벤트 처리를 펌웨어에 완료 통지하는 매크로입니다. exit_mode가 SMC인지 HVC인지에 따라 통로를 선택하며 x0·x1에 준비한 완료 서비스와 복귀 주소를 전달합니다. 정상 완료하면 원래 중단 문맥으로 돌아가므로 이 호출 위치로 반환하지 않습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
- L913
- 펌웨어 SDEI 완료 통로가 SMC인지 확인합니다. 이벤트 번호나 성공 여부를 비교하는 값이 아니라 호출 방식 선택입니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.
- L914
- 기존 NZCV가 Z=0이면 99f (L917)로 분기합니다. 그렇지 않으면 다음 명령을 실행합니다.
- L915
- x0·x1에 준비한 SDEI 완료 또는 complete-and-resume 요청을 SMC로 전달합니다. 성공하면 펌웨어가 중단된 실행을 복원하므로 다음 무한 루프는 비정상 반환을 막는 경로입니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.
- L916
- .로 무조건 분기합니다. LR은 바꾸지 않습니다. 현재 위치를 계속 반복합니다.
- L917
- SMC가 아닌 구성은 HVC 통로로 SDEI 완료 요청을 보냅니다. 이벤트의 일반 C callback을 호출하는 명령이 아니라 상위 펌웨어/하이퍼바이저 인터페이스입니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.
- L918
- .로 무조건 분기합니다. LR은 바꾸지 않습니다. 현재 위치를 계속 반복합니다.
- L919
- — 매크로 정의를 끝냅니다.
- L921
- KPTI가 사용자 실행 중 정상 SDEI 코드까지 매핑에서 제외할 수 있으므로 지원 빌드에는 최소 매핑 안의 SDEI 진입·복귀 trampoline을 포함합니다. 없으면 직접 SDEI handler로 진입할 수 있습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L930
- ".entry.tramp.text", "ax" — 현재 섹션을 보관하고 지정 섹션으로 이동합니다.
- L931
- (__sdei_asm_entry_trampoline)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L932
- SDEI가 들어왔을 때 TTBR1이 사용자 쪽 최소 매핑인지 확인할 값을 읽습니다. 아래 USER_ASID_BIT가 세워져 있으면 일반 커널 데이터 접근 전 매핑 복구가 필요합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L933
- USER_ASID_BIT가 없으면 이미 커널 매핑이므로 추가 전환을 건너뜁니다. 있으면 trampoline이 매핑을 복구하고 나중에 되돌릴 필요가 있음을 기록합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L935
- TTBR1에 고정 오프셋을 더하고 USER_ASID_FLAG를 지워 커널 매핑으로 전환합니다. 인자: tmp=x4.
- L936
- 변경된 실행 제어 상태를 반영하여 뒤 명령을 실행하도록 명령 흐름을 동기화합니다.
- L937
- x4 ← xzr. 값을 복사합니다. NZCV는 유지합니다.
- L942
- 이벤트 저장 영역에 진입 전 매핑 상태 표식을 남깁니다. 0은 이 trampoline이 커널 매핑을 복구했으므로 복귀 때 다시 감춰야 한다는 뜻이고, 0이 아니면 기존 커널 매핑을 유지합니다. 실제로 x1 + ((SDEI_EVENT_INTREGS + S_SDEI_TTBR1)) 주소의 메모리 8바이트를 x4의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x1 자체를 갱신하지 않습니다.
- L943
- 최소 매핑에서 접근 가능한 방법으로 정상 __sdei_asm_handler 주소를 얻습니다. x1의 이벤트 자료를 읽을 수 있도록 앞에서 커널 매핑을 복구한 뒤 그 handler로 이어집니다. 재배치 구성에서는 별도 rodata의 리터럴을 읽고, 그렇지 않은 구성에서는 MOVZ/MOVK로 주소를 구성합니다.
- L944
- x4에 든 주소로 이동합니다. LR을 새로 만들지 않습니다.
- L945
- (__sdei_asm_entry_trampoline)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L946
- __sdei_asm_entry_trampoline을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
- L955
- (__sdei_asm_exit_trampoline)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L956
- SDEI 진입 때 남긴 TTBR1 상태 표식을 읽습니다. 현재 이벤트가 사용자 최소 매핑에서 들어왔는지에 따라 원래 매핑으로 되돌릴지 결정합니다. 실제로 x4 + ((SDEI_EVENT_INTREGS + S_SDEI_TTBR1)) 주소의 메모리 8바이트를 x4에 읽습니다.
- L957
- 0이 아니면 진입 전에도 커널 매핑이었으므로 unmap을 생략합니다. 0이면 이 이벤트 때문에 복구했던 커널 매핑을 다시 감춥니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L959
- TTBR1에서 고정 오프셋을 빼고 USER_ASID_FLAG를 설정해 trampoline 매핑으로 전환합니다. 인자: tmp=x4.
- L961
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다. exit_mode에 따라 SMC/HVC로 이벤트 완료를 요청합니다. 호출이 돌아오면 B . 루프에 머뭅니다. 인자: exit_mode=x2.
- L962
- (__sdei_asm_exit_trampoline)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L963
- __sdei_asm_exit_trampoline을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
- L964
- — 보관했던 이전 섹션으로 돌아갑니다.
- L965
- — 전처리 조건 구간을 끝냅니다.
SDEI 레지스터·스택 준비
펌웨어가 x0–x17을 보존한 상황에서 나머지 레지스터와 PC/PSTATE/SP를 이벤트 구조체에 저장합니다. normal/critical 우선순위에 맞는 CPU별 이벤트 포인터, 실행 스택, shadow stack을 선택합니다. 중단 지점의 SP_EL0를 믿을 수 없어 __entry_task로 current를 복구합니다. 커널을 중단한 경우에만 기존 FP와 PC를 프레임에 연결합니다.
967/*
968 * Software Delegated Exception entry point.
969 *
970 * x0: Event number
971 * x1: struct sdei_registered_event argument from registration time.
972 * x2: interrupted PC
973 * x3: interrupted PSTATE
974 * x4: maybe clobbered by the trampoline
975 *
976 * Firmware has preserved x0->x17 for us, we must save/restore the rest to
977 * follow SMC-CC. We save (or retrieve) all the registers as the handler may
978 * want them.
979 */
980SYM_CODE_START(__sdei_asm_handler)
981 stp x2, x3, [x1, #SDEI_EVENT_INTREGS + S_PC]
982 stp x4, x5, [x1, #SDEI_EVENT_INTREGS + 16 * 2]
983 stp x6, x7, [x1, #SDEI_EVENT_INTREGS + 16 * 3]
984 stp x8, x9, [x1, #SDEI_EVENT_INTREGS + 16 * 4]
985 stp x10, x11, [x1, #SDEI_EVENT_INTREGS + 16 * 5]
986 stp x12, x13, [x1, #SDEI_EVENT_INTREGS + 16 * 6]
987 stp x14, x15, [x1, #SDEI_EVENT_INTREGS + 16 * 7]
988 stp x16, x17, [x1, #SDEI_EVENT_INTREGS + 16 * 8]
989 stp x18, x19, [x1, #SDEI_EVENT_INTREGS + 16 * 9]
990 stp x20, x21, [x1, #SDEI_EVENT_INTREGS + 16 * 10]
991 stp x22, x23, [x1, #SDEI_EVENT_INTREGS + 16 * 11]
992 stp x24, x25, [x1, #SDEI_EVENT_INTREGS + 16 * 12]
993 stp x26, x27, [x1, #SDEI_EVENT_INTREGS + 16 * 13]
994 stp x28, x29, [x1, #SDEI_EVENT_INTREGS + 16 * 14]
995 mov x4, sp
996 stp lr, x4, [x1, #SDEI_EVENT_INTREGS + S_LR]
997
998 mov x19, x1
999
1000 /* Store the registered-event for crash_smp_send_stop() */
1001 ldrb w4, [x19, #SDEI_EVENT_PRIORITY]
1002 cbnz w4, 1f
1003 adr_this_cpu dst=x5, sym=sdei_active_normal_event, tmp=x6
1004 b 2f
10051: adr_this_cpu dst=x5, sym=sdei_active_critical_event, tmp=x6
10062: str x19, [x5]
1007
1008 /*
1009 * entry.S may have been using sp as a scratch register, find whether
1010 * this is a normal or critical event and switch to the appropriate
1011 * stack for this CPU.
1012 */
1013 cbnz w4, 1f
1014 ldr_this_cpu dst=x5, sym=sdei_stack_normal_ptr, tmp=x6
1015 b 2f
10161: ldr_this_cpu dst=x5, sym=sdei_stack_critical_ptr, tmp=x6
10172: mov x6, #SDEI_STACK_SIZE
1018 add x5, x5, x6
1019 mov sp, x5
1020
1021#ifdef CONFIG_SHADOW_CALL_STACK
1022 /* Use a separate shadow call stack for normal and critical events */
1023 cbnz w4, 3f
1024 ldr_this_cpu dst=scs_sp, sym=sdei_shadow_call_stack_normal_ptr, tmp=x6
1025 b 4f
10263: ldr_this_cpu dst=scs_sp, sym=sdei_shadow_call_stack_critical_ptr, tmp=x6
10274:
1028#endif
1029
1030 /*
1031 * We may have interrupted userspace, or a guest, or exit-from or
1032 * return-to either of these. We can't trust sp_el0, restore it.
1033 */
1034 mrs x28, sp_el0
1035 ldr_this_cpu dst=x0, sym=__entry_task, tmp=x1
1036 msr sp_el0, x0
1037
1038 /* If we interrupted the kernel point to the previous stack/frame. */
1039 and x0, x3, #0xc
1040 mrs x1, CurrentEL
1041 cmp x0, x1
1042 csel x29, x29, xzr, eq // fp, or zero
1043 csel x4, x2, xzr, eq // elr, or zero
1044
1045 stp x29, x4, [sp, #-16]!
1046 mov x29, sp
1047
1048 add x0, x19, #SDEI_EVENT_INTREGS
1049 mov x1, x19
1050 bl __sdei_handler
- L980
- (__sdei_asm_handler)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L981
- x2, x3의 값을 [x1, #SDEI_EVENT_INTREGS + S_PC]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L982
- x4, x5의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 2]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L983
- x6, x7의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 3]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L984
- x8, x9의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 4]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L985
- x10, x11의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 5]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L986
- x12, x13의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 6]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L987
- x14, x15의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 7]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L988
- x16, x17의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 8]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L989
- x18, x19의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 9]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L990
- x20, x21의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 10]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L991
- x22, x23의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 11]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L992
- x24, x25의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 12]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L993
- x26, x27의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 13]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L994
- x28, x29의 값을 [x1, #SDEI_EVENT_INTREGS + 16 * 14]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L995
- x4 ← sp. 값을 복사합니다. NZCV는 유지합니다.
- L996
- lr, x4의 값을 [x1, #SDEI_EVENT_INTREGS + S_LR]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L998
- x19 ← x1. 값을 복사합니다. NZCV는 유지합니다.
- L1001
- 등록된 SDEI 이벤트의 normal/critical 우선순위를 읽습니다. 두 이벤트가 중첩될 수 있으므로 실행 중 이벤트 포인터와 스택을 우선순위별로 분리합니다. 실제로 x19 + (SDEI_EVENT_PRIORITY) 주소의 메모리 1바이트를 w4에 읽습니다. 이 주소 형식은 기준 레지스터 x19 자체를 갱신하지 않습니다.
- L1002
- w4가 0이 아니면 1f (L1005)로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L1003
- 이 CPU의 normal SDEI 활성 이벤트 포인터 슬롯 주소를 x5에 구합니다. 이어지는 store가 현재 이벤트 객체를 여기에 기록하며 x6는 주소 계산용입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1004
- 2f (L1006)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L1005
- critical 이벤트는 별도의 CPU별 활성 포인터 슬롯을 사용합니다. normal 이벤트를 중첩 중단해도 두 종류의 활성 상태를 덮어쓰지 않도록 x5를 critical 슬롯으로 바꿉니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1006
- 현재 CPU가 처리 중인 이벤트 객체를 normal 또는 critical 슬롯에 기록합니다. crash_smp_send_stop 등의 비정상 종료 경로도 활성 이벤트를 찾을 수 있게 합니다. 실제로 x5 주소의 메모리 8바이트를 x19의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x5 자체를 갱신하지 않습니다.
- L1013
- w4가 0이 아니면 1f (L1016)로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L1014
- normal SDEI 이벤트용 CPU별 스택을 선택합니다. 예외가 커널 진입의 임시 SP 사용 중간을 끊을 수도 있으므로 기존 SP를 신뢰하지 않고 별도 스택을 씁니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1015
- 2f (L1017)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L1016
- critical 이벤트면 normal과 다른 CPU별 스택을 선택하여 critical 이벤트가 normal 처리의 스택을 덮지 않게 합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1017
- 2 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다. x6 ← #SDEI_STACK_SIZE. 값을 복사합니다. NZCV는 유지합니다.
- L1018
- x5 ← x5 + x6. NZCV는 유지합니다.
- L1019
- sp ← x5. 값을 복사합니다. NZCV는 유지합니다.
- L1021
- SCS 보호 빌드는 SDEI에서도 normal·critical 이벤트마다 별도 shadow call stack을 선택합니다. 일반 스택을 나눠도 보호용 복귀 스택을 공유하면 중첩 처리 문맥이 섞일 수 있기 때문입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L1023
- w4가 0이 아니면 3f (L1026)로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L1024
- normal SDEI 이벤트 전용 shadow call stack 포인터를 x18에 읽습니다. 일반 task·IRQ의 보호용 복귀 스택과 구분하여 비동기 이벤트 중첩에 대비합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1025
- 4f (L1027)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L1026
- critical 이벤트의 별도 shadow call stack을 선택합니다. normal SDEI handler를 중단한 경우에도 그 보호용 스택을 계속 소비하지 않도록 합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1027
- 4 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다.
- L1028
- — 전처리 조건 구간을 끝냅니다.
- L1034
- 중단 시 SP_EL0를 x28에 보존합니다. 이벤트가 사용자·게스트·전환 도중 어디서든 올 수 있어 이 값이 이미 커널 current라고 가정하지 않습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L1035
- CPU별 __entry_task에서 신뢰할 현재 task 주소를 얻습니다. SDEI C handler가 current를 사용할 수 있게 할 입력입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1036
- SP_EL0에 커널 current 포인터를 설치합니다. 원래 값은 x28에 보관했으므로 이벤트 처리가 끝난 뒤 정확히 되돌릴 수 있습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L1039
- x0 ← x3 AND #0xc. NZCV는 유지합니다.
- L1040
- 현재 실행 EL과 펌웨어가 준 중단 PSTATE의 EL을 비교할 값을 읽습니다. 같은 커널 문맥이면 기존 FP·PC를 연결하고 사용자나 다른 EL 문맥이면 커널 unwind 연결을 끊습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L1041
- x0 − x1 결과로 NZCV를 설정합니다. 계산 결과를 일반 레지스터에 쓰지는 않습니다.
- L1042
- Z=1이면 x29←x29, 아니면 x29←xzr입니다. NZCV는 바꾸지 않습니다.
- L1043
- Z=1이면 x4←x2, 아니면 x4←xzr입니다. NZCV는 바꾸지 않습니다.
- L1045
- x29, x4의 값을 [sp, #-16]!에 16바이트 저장합니다. 대괄호 안의 오프셋을 기준 레지스터에 먼저 반영한 뒤 접근합니다.
- L1046
- x29 ← sp. 값을 복사합니다. NZCV는 유지합니다.
- L1048
- x0 ← x19 + #SDEI_EVENT_INTREGS. NZCV는 유지합니다.
- L1049
- x1 ← x19. 값을 복사합니다. NZCV는 유지합니다.
- L1050
- x0=저장 레지스터, x1=등록 이벤트 객체로 __sdei_handler를 호출합니다. 펌웨어가 보존한 값 수거와 실제 이벤트 callback 등을 처리하고, 반환값은 완료 상태 또는 재개할 주소 선택에 사용됩니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.
SDEI 완료와 중단 처리
C handler 반환 후 SP_EL0와 변경한 callee-saved 상태를 되돌립니다. 반환값을 비교해 EVENT_COMPLETE 또는 EVENT_COMPLETE_AND_RESUME를 선택하고 활성 이벤트 포인터를 지웁니다. KPTI 여부에 따라 직접 또는 trampoline에서 펌웨어로 돌아갑니다. __sdei_handler_abort는 완료 후 로컬 라벨로 재개하도록 x1을 설정합니다.
1051
1052 msr sp_el0, x28
1053 /* restore regs >x17 that we clobbered */
1054 mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline
1055 ldp x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14]
1056 ldp x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9]
1057 ldp lr, x1, [x4, #SDEI_EVENT_INTREGS + S_LR]
1058 mov sp, x1
1059
1060 mov x1, x0 // address to complete_and_resume
1061 /* x0 = (x0 <= SDEI_EV_FAILED) ?
1062 * EVENT_COMPLETE:EVENT_COMPLETE_AND_RESUME
1063 */
1064 cmp x0, #SDEI_EV_FAILED
1065 mov_q x2, SDEI_1_0_FN_SDEI_EVENT_COMPLETE
1066 mov_q x3, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
1067 csel x0, x2, x3, ls
1068
1069 ldr_l x2, sdei_exit_mode
1070
1071 /* Clear the registered-event seen by crash_smp_send_stop() */
1072 ldrb w3, [x4, #SDEI_EVENT_PRIORITY]
1073 cbnz w3, 1f
1074 adr_this_cpu dst=x5, sym=sdei_active_normal_event, tmp=x6
1075 b 2f
10761: adr_this_cpu dst=x5, sym=sdei_active_critical_event, tmp=x6
10772: str xzr, [x5]
1078
1079alternative_if_not ARM64_UNMAP_KERNEL_AT_EL0
1080 sdei_handler_exit exit_mode=x2
1081alternative_else_nop_endif
1082
1083#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
1084 tramp_alias dst=x5, sym=__sdei_asm_exit_trampoline
1085 br x5
1086#endif
1087SYM_CODE_END(__sdei_asm_handler)
1088NOKPROBE(__sdei_asm_handler)
1089
1090SYM_CODE_START(__sdei_handler_abort)
1091 mov_q x0, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
1092 adr x1, 1f
1093 ldr_l x2, sdei_exit_mode
1094 sdei_handler_exit exit_mode=x2
1095 // exit the handler and jump to the next instruction.
1096 // Exit will stomp x0-x17, PSTATE, ELR_ELx, and SPSR_ELx.
10971: ret
1098SYM_CODE_END(__sdei_handler_abort)
1099NOKPROBE(__sdei_handler_abort)
1100#endif /* CONFIG_ARM_SDE_INTERFACE */
- L1052
- 이벤트 전 SP_EL0를 되돌립니다. 커널 handler를 위해 임시로 설치했던 current 값을 사용자·게스트의 원래 실행 상태에 남기지 않게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
- L1054
- x4 ← x19. 값을 복사합니다. NZCV는 유지합니다.
- L1055
- [x4, #SDEI_EVENT_INTREGS + 16 * 14]에서 16바이트를 읽어 x28, x29에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L1056
- [x4, #SDEI_EVENT_INTREGS + 16 * 9]에서 16바이트를 읽어 x18, x19에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L1057
- [x4, #SDEI_EVENT_INTREGS + S_LR]에서 16바이트를 읽어 lr, x1에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
- L1058
- sp ← x1. 값을 복사합니다. NZCV는 유지합니다.
- L1060
- x1 ← x0. 값을 복사합니다. NZCV는 유지합니다.
- L1064
- C handler 반환값이 완료 상태 코드 범위인지 아니면 별도 재개 주소인지 구분합니다. 아래 CSEL은 펌웨어의 EVENT_COMPLETE와 COMPLETE_AND_RESUME 중 어떤 API를 보낼지 고릅니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.
- L1065
- 원래 중단 문맥으로 돌아가는 SDEI EVENT_COMPLETE 함수 ID를 준비합니다. 정상 반환 상태를 펌웨어에 보고하는 서비스 번호입니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.
- L1066
- 명시한 주소에서 실행을 재개하는 EVENT_COMPLETE_AND_RESUME 함수 ID를 준비합니다. x1에 앞에서 복사한 재개 주소와 짝을 이루는 서비스입니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.
- L1067
- C=0 또는 Z=1이면 x0←x2, 아니면 x0←x3입니다. NZCV는 바꾸지 않습니다. LS는 부호 없는 이하(C=0 또는 Z=1)입니다. 실패 이하이면 COMPLETE, 그 외에는 COMPLETE_AND_RESUME를 선택합니다.
- L1069
- SDEI 등록 시 정해진 완료 통로 SMC/HVC 종류를 읽습니다. 뒤의 sdei_handler_exit가 이 값으로 실제 명령을 선택합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.
- L1072
- [x4, #SDEI_EVENT_PRIORITY]에서 1바이트를 읽어 w3에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다. W 레지스터에 쓰므로 대응하는 X 레지스터의 상위 32비트는 0이 됩니다.
- L1073
- w3가 0이 아니면 1f (L1076)로 분기하고, 아니면 다음 명령으로 갑니다. NZCV는 바꾸지 않습니다.
- L1074
- normal 이벤트 처리를 마친 뒤 비울 CPU별 활성 슬롯의 주소를 x5에 구합니다. 뒤의 store 0은 이벤트 객체 해제가 아니라 활성 표시 제거입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1075
- 2f (L1077)로 무조건 분기합니다. LR은 바꾸지 않습니다.
- L1076
- critical 이벤트였다면 normal 슬롯 대신 critical 활성 슬롯 주소를 선택합니다. 방금 종료할 이벤트의 표시만 지우기 위한 선택입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
- L1077
- CPU별 활성 SDEI 이벤트 포인터를 0으로 비워 처리가 끝났음을 표시합니다. 이벤트 객체 자체를 해제하는 store가 아니라 crash 경로에서 볼 활성 표식 정리입니다. 실제로 x5 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x5 자체를 갱신하지 않습니다.
- L1079
- 실제 KPTI가 비활성이면 정상 커널 매핑에서 곧바로 펌웨어 완료 호출을 해도 되므로 이 경로를 유지합니다. 활성인 경우는 최소 exit trampoline에서 매핑을 되돌린 뒤 호출해야 합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L1080
- exit_mode에 따라 SMC/HVC로 이벤트 완료를 요청합니다. 호출이 돌아오면 B . 루프에 머뭅니다. 인자: exit_mode=x2.
- L1081
- 1079행에서 시작한 ARM64_UNMAP_KERNEL_AT_EL0 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.
- L1083
- KPTI 지원 빌드에는 이벤트 전 매핑 상태로 되돌릴 exit trampoline 분기를 포함합니다. 실제 활성 시스템에서 위의 직접 완료 경로가 빠졌을 때 이 경로로 이어집니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
- L1084
- TRAMP_VALIAS와 섹션 내 심볼 오프셋을 더한 별칭 주소를 구성합니다. 인자: dst=x5, sym=__sdei_asm_exit_trampoline.
- L1085
- x5에 든 주소로 이동합니다. LR을 새로 만들지 않습니다.
- L1086
- — 전처리 조건 구간을 끝냅니다.
- L1087
- (__sdei_asm_handler)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L1088
- __sdei_asm_handler을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
- L1090
- (__sdei_handler_abort)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
- L1091
- 진행 중인 SDEI handler를 중단하면서 바로 아래 지역 라벨에서 다시 실행하도록 COMPLETE_AND_RESUME 서비스 번호를 준비합니다. task 시그널 반환 번호와는 다른 펌웨어 API입니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.
- L1092
- 펌웨어가 이벤트 종료 뒤 재개할 주소로 1f를 전달합니다. 원래 LR로 즉시 RET하는 대신 펌웨어의 이벤트 상태도 정상적으로 정리해야 하기 때문입니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.
- L1093
- SDEI가 사용하는 펌웨어 완료 통로의 종류를 x2에 읽습니다. 뒤의 exit 매크로가 SMC인지 HVC인지 골라 중단한 이벤트의 상태를 정리합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.
- L1094
- exit_mode에 따라 SMC/HVC로 이벤트 완료를 요청합니다. 호출이 돌아오면 B . 루프에 머뭅니다. 인자: exit_mode=x2.
- L1097
- 1 라벨: 다음 코드나 데이터의 위치에 이름을 붙입니다. x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
- L1098
- (__sdei_handler_abort)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L1099
- __sdei_handler_abort을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
- L1100
- — 전처리 조건 구간을 끝냅니다.
매크로 정의와 확장
각 정의는 v6.18.37 원문입니다. 역슬래시 인자는 사용 위치의 값으로 치환되며, 안쪽 매크로와 조건부 블록은 이후 단계에서 다시 처리됩니다.
ASM_BUG
BUG 메타데이터와 BRK 예외 명령을 배치합니다. 여기까지 실행되면 정상 복귀가 아닌 오류 예외를 일으킵니다.
SYM_CODE_END
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
include/linux/linkage.h L305–L306
#define SYM_CODE_END(name) \
SYM_END(name, SYM_T_NONE)SYM_CODE_START
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
include/linux/linkage.h L278–L279
#define SYM_CODE_START(name) \
SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)SYM_CODE_START_LOCAL
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
include/linux/linkage.h L290–L291
#define SYM_CODE_START_LOCAL(name) \
SYM_START(name, SYM_L_LOCAL, SYM_A_ALIGN)SYM_CODE_START_LOCAL_NOALIGN
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
include/linux/linkage.h L299–L300
#define SYM_CODE_START_LOCAL_NOALIGN(name) \
SYM_START(name, SYM_L_LOCAL, SYM_A_NONE)SYM_FUNC_END
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
include/linux/linkage.h L248–L249
#define SYM_FUNC_END(name) \
SYM_END(name, SYM_T_FUNC)SYM_FUNC_START
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/linkage.h L18–L20
#define SYM_FUNC_START(name) \
SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN) \
bti c ;include/linux/linkage.h L209–L210
#define SYM_FUNC_START(name) \
SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)SYM_INNER_LABEL
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
include/linux/linkage.h L202–L204
#define SYM_INNER_LABEL(name, linkage) \
.type name SYM_T_NONE ASM_NL \
SYM_ENTRY(name, linkage, SYM_A_NONE)__ptrauth_keys_install_kernel_nosync
커널용 instruction authentication 키를 설치하며 이 매크로 끝에 ISB를 추가하지 않습니다. 호출 경로가 동기화 시점을 정합니다.
arch/arm64/include/asm/asm_pointer_auth.h L12–L18
.macro __ptrauth_keys_install_kernel_nosync tsk, tmp1, tmp2, tmp3
mov \tmp1, #THREAD_KEYS_KERNEL
add \tmp1, \tsk, \tmp1
ldp \tmp2, \tmp3, [\tmp1, #PTRAUTH_KERNEL_KEY_APIA]
msr_s SYS_APIAKEYLO_EL1, \tmp2
msr_s SYS_APIAKEYHI_EL1, \tmp3
.endmarch/arm64/include/asm/asm_pointer_auth.h L35–L36
.macro __ptrauth_keys_install_kernel_nosync tsk, tmp1, tmp2, tmp3
.endm인자 치환 예: __ptrauth_keys_install_kernel_nosync tsk, x20, x22, x23
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
mov x20, #THREAD_KEYS_KERNEL
add x20, tsk, x20
ldp x22, x23, [x20, #PTRAUTH_KERNEL_KEY_APIA]
msr_s SYS_APIAKEYLO_EL1, x22
msr_s SYS_APIAKEYHI_EL1, x23__ptrauth_keys_install_user
태스크의 사용자 instruction authentication 키를 시스템 레지스터에 설치합니다.
arch/arm64/include/asm/asm_pointer_auth.h L52–L58
.macro __ptrauth_keys_install_user tsk, tmp1, tmp2, tmp3
mov \tmp1, #THREAD_KEYS_USER
add \tmp1, \tsk, \tmp1
ldp \tmp2, \tmp3, [\tmp1, #PTRAUTH_USER_KEY_APIA]
msr_s SYS_APIAKEYLO_EL1, \tmp2
msr_s SYS_APIAKEYHI_EL1, \tmp3
.endm인자 치환 예: __ptrauth_keys_install_user tsk, x0, x1, x2
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
mov x0, #THREAD_KEYS_USER
add x0, tsk, x0
ldp x1, x2, [x0, #PTRAUTH_USER_KEY_APIA]
msr_s SYS_APIAKEYLO_EL1, x1
msr_s SYS_APIAKEYHI_EL1, x2__uaccess_ttbr0_disable
TTBR0를 예약된 매핑으로 바꾸고 관련 ASID 상태를 조정하여 사용자 주소 접근을 차단합니다.
arch/arm64/include/asm/asm-uaccess.h L16–L24
.macro __uaccess_ttbr0_disable, tmp1
mrs \tmp1, ttbr1_el1 // swapper_pg_dir
bic \tmp1, \tmp1, #TTBR_ASID_MASK
sub \tmp1, \tmp1, #RESERVED_SWAPPER_OFFSET // reserved_pg_dir
msr ttbr0_el1, \tmp1 // set reserved TTBR0_EL1
add \tmp1, \tmp1, #RESERVED_SWAPPER_OFFSET
msr ttbr1_el1, \tmp1 // set reserved ASID
isb
.endm인자 치환 예: __uaccess_ttbr0_disable x21
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
mrs x21, ttbr1_el1 // swapper_pg_dir
bic x21, x21, #TTBR_ASID_MASK
sub x21, x21, #RESERVED_SWAPPER_OFFSET // reserved_pg_dir
msr ttbr0_el1, x21 // set reserved TTBR0_EL1
add x21, x21, #RESERVED_SWAPPER_OFFSET
msr ttbr1_el1, x21 // set reserved ASID
isb__uaccess_ttbr0_enable
태스크가 저장한 TTBR0와 해당 ASID를 복원해 사용자 주소 접근을 허용합니다.
arch/arm64/include/asm/asm-uaccess.h L26–L35
.macro __uaccess_ttbr0_enable, tmp1, tmp2
get_current_task \tmp1
ldr \tmp1, [\tmp1, #TSK_TI_TTBR0] // load saved TTBR0_EL1
mrs \tmp2, ttbr1_el1
extr \tmp2, \tmp2, \tmp1, #48
ror \tmp2, \tmp2, #16
msr ttbr1_el1, \tmp2 // set the active ASID
msr ttbr0_el1, \tmp1 // set the non-PAN TTBR0_EL1
isb
.endm인자 치환 예: __uaccess_ttbr0_enable x0, x1
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
get_current_task x0
ldr x0, [x0, #TSK_TI_TTBR0] // load saved TTBR0_EL1
mrs x1, ttbr1_el1
extr x1, x1, x0, #48
ror x1, x1, #16
msr ttbr1_el1, x1 // set the active ASID
msr ttbr0_el1, x0 // set the non-PAN TTBR0_EL1
isbalternative_cb
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/alternative-macros.h L156–L162
.macro alternative_cb cap, cb
.set .Lasm_alt_mode, 0
.pushsection .altinstructions, "a"
altinstruction_entry 661f, \cb, (1 << ARM64_CB_SHIFT) | \cap, 662f-661f, 0
.popsection
661:
.endm인자 치환 예: alternative_cb ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.set .Lasm_alt_mode, 0
.pushsection .altinstructions, "a"
altinstruction_entry 661f, spectre_v4_patch_fw_mitigation_enable, (1 << ARM64_CB_SHIFT) | ARM64_ALWAYS_SYSTEM, 662f-661f, 0
.popsection
661:alternative_cb_end
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/alternative-macros.h L192–L194
.macro alternative_cb_end
662:
.endm인자 치환 예: alternative_cb_end
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
662:alternative_else_nop_endif
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/alternative-macros.h L201–L205
.macro alternative_else_nop_endif
alternative_else
nops (662b-661b) / AARCH64_INSN_SIZE
alternative_endif
.endm인자 치환 예: alternative_else_nop_endif
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
alternative_else
nops (662b-661b) / AARCH64_INSN_SIZE
alternative_endifalternative_if
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/alternative-macros.h L146–L154
.macro alternative_if cap
.set .Lasm_alt_mode, 1
.pushsection .altinstructions, "a"
altinstruction_entry 663f, 661f, \cap, 664f-663f, 662f-661f
.popsection
.subsection 1
.align 2 /* So GAS knows label 661 is suitably aligned */
661:
.endm인자 치환 예: alternative_if ARM64_MTE
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.set .Lasm_alt_mode, 1
.pushsection .altinstructions, "a"
altinstruction_entry 663f, 661f, ARM64_MTE, 664f-663f, 662f-661f
.popsection
.subsection 1
.align 2 /* So GAS knows label 661 is suitably aligned */
661:alternative_if_not
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/alternative-macros.h L138–L144
.macro alternative_if_not cap
.set .Lasm_alt_mode, 0
.pushsection .altinstructions, "a"
altinstruction_entry 661f, 663f, \cap, 662f-661f, 664f-663f
.popsection
661:
.endm인자 치환 예: alternative_if_not ARM64_MTE
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.set .Lasm_alt_mode, 0
.pushsection .altinstructions, "a"
altinstruction_entry 661f, 663f, ARM64_MTE, 662f-661f, 664f-663f
.popsection
661:alternative_insn
아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.
arch/arm64/include/asm/alternative-macros.h L102–L114
.macro alternative_insn insn1, insn2, cap, enable = 1
.if \enable
661: \insn1
662: .pushsection .altinstructions, "a"
altinstruction_entry 661b, 663f, \cap, 662b-661b, 664f-663f
.popsection
.subsection 1
663: \insn2
664: .org . - (664b-663b) + (662b-661b)
.org . - (662b-661b) + (664b-663b)
.previous
.endif
.endm인자 치환 예: alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.if \enable
661: nop
662: .pushsection .altinstructions, "a"
altinstruction_entry 661b, 663f, ARM64_HAS_DIT, 662b-661b, 664f-663f
.popsection
.subsection 1
663: SET_PSTATE_DIT(1)
664: .org . - (664b-663b) + (662b-661b)
.org . - (662b-661b) + (664b-663b)
.previous
.endifapply_ssbd
CPU별 callback 필요 여부와 태스크 플래그에 따라 펌웨어의 SSBD 설정을 요청합니다. state=1은 진입, 0은 복귀 경로입니다.
arch/arm64/kernel/entry.S L114–L128
.macro apply_ssbd, state, tmp1, tmp2
alternative_cb ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable
b .L__asm_ssbd_skip\@ // Patched to NOP
alternative_cb_end
ldr_this_cpu \tmp2, arm64_ssbd_callback_required, \tmp1
cbz \tmp2, .L__asm_ssbd_skip\@
ldr \tmp2, [tsk, #TSK_TI_FLAGS]
tbnz \tmp2, #TIF_SSBD, .L__asm_ssbd_skip\@
mov w0, #ARM_SMCCC_ARCH_WORKAROUND_2
mov w1, #\state
alternative_cb ARM64_ALWAYS_SYSTEM, smccc_patch_fw_mitigation_conduit
nop // Patched to SMC/HVC #0
alternative_cb_end
.L__asm_ssbd_skip\@:
.endm인자 치환 예: apply_ssbd 1, x22, x23
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
alternative_cb ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable
b .L__asm_ssbd_skip\@ // Patched to NOP
alternative_cb_end
ldr_this_cpu x23, arm64_ssbd_callback_required, x22
cbz x23, .L__asm_ssbd_skip\@
ldr x23, [tsk, #TSK_TI_FLAGS]
tbnz x23, #TIF_SSBD, .L__asm_ssbd_skip\@
mov w0, #ARM_SMCCC_ARCH_WORKAROUND_2
mov w1, #1
alternative_cb ARM64_ALWAYS_SYSTEM, smccc_patch_fw_mitigation_conduit
nop // Patched to SMC/HVC #0
alternative_cb_end
.L__asm_ssbd_skip\@:check_mte_async_tcf
사용자 비동기 태그 오류 상태를 검사하고 발견 시 태스크의 MTE 비동기 오류 플래그를 설정합니다.
arch/arm64/kernel/entry.S L131–L151
.macro check_mte_async_tcf, tmp, ti_flags, thread_sctlr
#ifdef CONFIG_ARM64_MTE
.arch_extension lse
alternative_if_not ARM64_MTE
b 1f
alternative_else_nop_endif
/*
* Asynchronous tag check faults are only possible in ASYNC (2) or
* ASYM (3) modes. In each of these modes bit 1 of SCTLR_EL1.TCF0 is
* set, so skip the check if it is unset.
*/
tbz \thread_sctlr, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
mrs_s \tmp, SYS_TFSRE0_EL1
tbz \tmp, #SYS_TFSR_EL1_TF0_SHIFT, 1f
/* Asynchronous TCF occurred for TTBR0 access, set the TI flag */
mov \tmp, #_TIF_MTE_ASYNC_FAULT
add \ti_flags, tsk, #TSK_TI_FLAGS
stset \tmp, [\ti_flags]
1:
#endif
.endm인자 치환 예: check_mte_async_tcf x22, x23, x0
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
#ifdef CONFIG_ARM64_MTE
.arch_extension lse
alternative_if_not ARM64_MTE
b 1f
alternative_else_nop_endif
/*
* Asynchronous tag check faults are only possible in ASYNC (2) or
* ASYM (3) modes. In each of these modes bit 1 of SCTLR_EL1.TCF0 is
* set, so skip the check if it is unset.
*/
tbz x0, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
mrs_s x22, SYS_TFSRE0_EL1
tbz x22, #SYS_TFSR_EL1_TF0_SHIFT, 1f
/* Asynchronous TCF occurred for TTBR0 access, set the TI flag */
mov x22, #_TIF_MTE_ASYNC_FAULT
add x23, tsk, #TSK_TI_FLAGS
stset x22, [x23]
1:
#endifclear_gp_regs
x0–x29에 XZR을 복사합니다. 원래 값은 그 전에 pt_regs에 저장되어 있습니다.
arch/arm64/kernel/entry.S L33–L37
.macro clear_gp_regs
.irp n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
mov x\n, xzr
.endr
.endm인자 치환 예: clear_gp_regs
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.irp n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
mov x\n, xzr
.endrclear_mte_async_tcf
비동기 태그 검사 모드이면 DSB 후 TFSRE0_EL1을 0으로 지웁니다.
arch/arm64/kernel/entry.S L154–L164
.macro clear_mte_async_tcf thread_sctlr
#ifdef CONFIG_ARM64_MTE
alternative_if ARM64_MTE
/* See comment in check_mte_async_tcf above. */
tbz \thread_sctlr, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
dsb ish
msr_s SYS_TFSRE0_EL1, xzr
1:
alternative_else_nop_endif
#endif
.endm인자 치환 예: clear_mte_async_tcf x0
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
#ifdef CONFIG_ARM64_MTE
alternative_if ARM64_MTE
/* See comment in check_mte_async_tcf above. */
tbz x0, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
dsb ish
msr_s SYS_TFSRE0_EL1, xzr
1:
alternative_else_nop_endif
#endifclearbhb
HINT #22를 출력해 분기 이력 지우기 동작을 요청합니다.
인자 치환 예: clearbhb
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
hint #22disable_daif
DAIFSet #0xf로 디버그·SError·IRQ·FIQ 마스크를 설정합니다.
인자 치환 예: disable_daif
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
msr daifset, #0xfdisable_step_tsk
TIF_SINGLESTEP이 있을 때 MDSCR_EL1.SS를 지우고 ISB를 실행합니다.
arch/arm64/include/asm/assembler.h L58–L65
.macro disable_step_tsk, flgs, tmp
tbz \flgs, #TIF_SINGLESTEP, 9990f
mrs \tmp, mdscr_el1
bic \tmp, \tmp, #MDSCR_EL1_SS
msr mdscr_el1, \tmp
isb // Take effect before a subsequent clear of DAIF.D
9990:
.endm인자 치환 예: disable_step_tsk x19, x20
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
tbz x19, #TIF_SINGLESTEP, 9990f
mrs x20, mdscr_el1
bic x20, x20, #MDSCR_EL1_SS
msr mdscr_el1, x20
isb // Take effect before a subsequent clear of DAIF.D
9990:enable_step_tsk
TIF_SINGLESTEP이 있을 때 MDSCR_EL1.SS를 설정합니다.
arch/arm64/include/asm/assembler.h L68–L74
.macro enable_step_tsk, flgs, tmp
tbz \flgs, #TIF_SINGLESTEP, 9990f
mrs \tmp, mdscr_el1
orr \tmp, \tmp, #MDSCR_EL1_SS
msr mdscr_el1, \tmp
9990:
.endm인자 치환 예: enable_step_tsk x19, x2
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
tbz x19, #TIF_SINGLESTEP, 9990f
mrs x2, mdscr_el1
orr x2, x2, #MDSCR_EL1_SS
msr mdscr_el1, x2
9990:entry_handler
kernel_entry 뒤 SP를 x0로 전달해 해당 C handler를 부르고 ret_to_user 또는 ret_to_kernel로 분기합니다.
arch/arm64/kernel/entry.S L570–L581
.macro entry_handler el:req, ht:req, regsize:req, label:req
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
kernel_entry \el, \regsize
mov x0, sp
bl el\el\ht\()_\regsize\()_\label\()_handler
.if \el == 0
b ret_to_user
.else
b ret_to_kernel
.endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
.endm인자 치환 예: entry_handler 1, t, 64, sync
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
SYM_CODE_START_LOCAL(el1t_64_sync)
kernel_entry 1, 64
mov x0, sp
bl el1t_64_sync_handler
.if 1 == 0
b ret_to_user
.else
b ret_to_kernel
.endif
SYM_CODE_END(el1t_64_sync)generate_el1_vector
상위 EL 슬롯들과 하위 EL의 완화용 trampoline 슬롯들을 생성합니다.
arch/arm64/kernel/entry.S L782–L800
.macro generate_el1_vector, bhb
.Lvector_start\@:
kernel_ventry 1, t, 64, sync // Synchronous EL1t
kernel_ventry 1, t, 64, irq // IRQ EL1t
kernel_ventry 1, t, 64, fiq // FIQ EL1h
kernel_ventry 1, t, 64, error // Error EL1t
kernel_ventry 1, h, 64, sync // Synchronous EL1h
kernel_ventry 1, h, 64, irq // IRQ EL1h
kernel_ventry 1, h, 64, fiq // FIQ EL1h
kernel_ventry 1, h, 64, error // Error EL1h
.rept 4
tramp_ventry .Lvector_start\@, 64, 0, \bhb
.endr
.rept 4
tramp_ventry .Lvector_start\@, 32, 0, \bhb
.endr
.endm인자 치환 예: generate_el1_vector bhb=BHB_MITIGATION_LOOP
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.Lvector_start\@:
kernel_ventry 1, t, 64, sync // Synchronous EL1t
kernel_ventry 1, t, 64, irq // IRQ EL1t
kernel_ventry 1, t, 64, fiq // FIQ EL1h
kernel_ventry 1, t, 64, error // Error EL1t
kernel_ventry 1, h, 64, sync // Synchronous EL1h
kernel_ventry 1, h, 64, irq // IRQ EL1h
kernel_ventry 1, h, 64, fiq // FIQ EL1h
kernel_ventry 1, h, 64, error // Error EL1h
.rept 4
tramp_ventry .Lvector_start\@, 64, 0, BHB_MITIGATION_LOOP
.endr
.rept 4
tramp_ventry .Lvector_start\@, 32, 0, BHB_MITIGATION_LOOP
.endrgenerate_tramp_vector
0x400바이트를 비우고 64비트와 32비트 하위 EL 슬롯을 각각 네 개 생성합니다.
arch/arm64/kernel/entry.S L740–L750
.macro generate_tramp_vector, kpti, bhb
.Lvector_start\@:
.space 0x400
.rept 4
tramp_ventry .Lvector_start\@, 64, \kpti, \bhb
.endr
.rept 4
tramp_ventry .Lvector_start\@, 32, \kpti, \bhb
.endr
.endm인자 치환 예: generate_tramp_vector kpti=1, bhb=BHB_MITIGATION_LOOP
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.Lvector_start\@:
.space 0x400
.rept 4
tramp_ventry .Lvector_start\@, 64, 1, BHB_MITIGATION_LOOP
.endr
.rept 4
tramp_ventry .Lvector_start\@, 32, 1, BHB_MITIGATION_LOOP
.endrkernel_entry
예외 이전 레지스터를 pt_regs에 저장하고 EL0/EL1에 맞는 current·제어 상태·메타데이터를 준비합니다.
arch/arm64/kernel/entry.S L197–L333
.macro kernel_entry, el, regsize = 64
.if \el == 0
alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT
.endif
.if \regsize == 32
mov w0, w0 // zero upper 32 bits of x0
.endif
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
clear_gp_regs
mrs x21, sp_el0
ldr_this_cpu tsk, __entry_task, x20
msr sp_el0, tsk
/*
* Ensure MDSCR_EL1.SS is clear, since we can unmask debug exceptions
* when scheduling.
*/
ldr x19, [tsk, #TSK_TI_FLAGS]
disable_step_tsk x19, x20
/* Check for asynchronous tag check faults in user space */
ldr x0, [tsk, THREAD_SCTLR_USER]
check_mte_async_tcf x22, x23, x0
#ifdef CONFIG_ARM64_PTR_AUTH
alternative_if ARM64_HAS_ADDRESS_AUTH
/*
* Enable IA for in-kernel PAC if the task had it disabled. Although
* this could be implemented with an unconditional MRS which would avoid
* a load, this was measured to be slower on Cortex-A75 and Cortex-A76.
*
* Install the kernel IA key only if IA was enabled in the task. If IA
* was disabled on kernel exit then we would have left the kernel IA
* installed so there is no need to install it again.
*/
tbz x0, SCTLR_ELx_ENIA_SHIFT, 1f
__ptrauth_keys_install_kernel_nosync tsk, x20, x22, x23
b 2f
1:
mrs x0, sctlr_el1
orr x0, x0, SCTLR_ELx_ENIA
msr sctlr_el1, x0
2:
alternative_else_nop_endif
#endif
apply_ssbd 1, x22, x23
mte_set_kernel_gcr x22, x23
/*
* Any non-self-synchronizing system register updates required for
* kernel entry should be placed before this point.
*/
alternative_if ARM64_MTE
isb
b 1f
alternative_else_nop_endif
alternative_if ARM64_HAS_ADDRESS_AUTH
isb
alternative_else_nop_endif
1:
scs_load_current
.else
add x21, sp, #PT_REGS_SIZE
get_current_task tsk
.endif /* \el == 0 */
mrs x22, elr_el1
mrs x23, spsr_el1
stp lr, x21, [sp, #S_LR]
/*
* Create a metadata frame record. The unwinder will use this to
* identify and unwind exception boundaries.
*/
stp xzr, xzr, [sp, #S_STACKFRAME]
.if \el == 0
mov x0, #FRAME_META_TYPE_FINAL
.else
mov x0, #FRAME_META_TYPE_PT_REGS
.endif
str x0, [sp, #S_STACKFRAME_TYPE]
add x29, sp, #S_STACKFRAME
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
alternative_if_not ARM64_HAS_PAN
bl __swpan_entry_el\el
alternative_else_nop_endif
#endif
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]
.endif
#ifdef CONFIG_ARM64_PSEUDO_NMI
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
b .Lskip_pmr_save\@
alternative_else_nop_endif
mrs_s x20, SYS_ICC_PMR_EL1
str w20, [sp, #S_PMR]
mov x20, #GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET
msr_s SYS_ICC_PMR_EL1, x20
.Lskip_pmr_save\@:
#endif
/*
* Registers that may be useful after this macro is invoked:
*
* x20 - ICC_PMR_EL1
* x21 - aborted SP
* x22 - aborted PC
* x23 - aborted PSTATE
*/
.endm인자 치환 예: kernel_entry 1
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.if 1 == 0
alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT
.endif
.if \regsize == 32
mov w0, w0 // zero upper 32 bits of x0
.endif
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 1 == 0
clear_gp_regs
mrs x21, sp_el0
ldr_this_cpu tsk, __entry_task, x20
msr sp_el0, tsk
/*
* Ensure MDSCR_EL1.SS is clear, since we can unmask debug exceptions
* when scheduling.
*/
ldr x19, [tsk, #TSK_TI_FLAGS]
disable_step_tsk x19, x20
/* Check for asynchronous tag check faults in user space */
ldr x0, [tsk, THREAD_SCTLR_USER]
check_mte_async_tcf x22, x23, x0
#ifdef CONFIG_ARM64_PTR_AUTH
alternative_if ARM64_HAS_ADDRESS_AUTH
/*
* Enable IA for in-kernel PAC if the task had it disabled. Although
* this could be implemented with an unconditional MRS which would avoid
* a load, this was measured to be slower on Cortex-A75 and Cortex-A76.
*
* Install the kernel IA key only if IA was enabled in the task. If IA
* was disabled on kernel exit then we would have left the kernel IA
* installed so there is no need to install it again.
*/
tbz x0, SCTLR_ELx_ENIA_SHIFT, 1f
__ptrauth_keys_install_kernel_nosync tsk, x20, x22, x23
b 2f
1:
mrs x0, sctlr_el1
orr x0, x0, SCTLR_ELx_ENIA
msr sctlr_el1, x0
2:
alternative_else_nop_endif
#endif
apply_ssbd 1, x22, x23
mte_set_kernel_gcr x22, x23
/*
* Any non-self-synchronizing system register updates required for
* kernel entry should be placed before this point.
*/
alternative_if ARM64_MTE
isb
b 1f
alternative_else_nop_endif
alternative_if ARM64_HAS_ADDRESS_AUTH
isb
alternative_else_nop_endif
1:
scs_load_current
.else
add x21, sp, #PT_REGS_SIZE
get_current_task tsk
.endif /* 1 == 0 */
mrs x22, elr_el1
mrs x23, spsr_el1
stp lr, x21, [sp, #S_LR]
/*
* Create a metadata frame record. The unwinder will use this to
* identify and unwind exception boundaries.
*/
stp xzr, xzr, [sp, #S_STACKFRAME]
.if 1 == 0
mov x0, #FRAME_META_TYPE_FINAL
.else
mov x0, #FRAME_META_TYPE_PT_REGS
.endif
str x0, [sp, #S_STACKFRAME_TYPE]
add x29, sp, #S_STACKFRAME
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
alternative_if_not ARM64_HAS_PAN
bl __swpan_entry_el1
alternative_else_nop_endif
#endif
stp x22, x23, [sp, #S_PC]
/* Not in a syscall by default (el0_svc overwrites for real syscall) */
.if 1 == 0
mov w21, #NO_SYSCALL
str w21, [sp, #S_SYSCALLNO]
.endif
#ifdef CONFIG_ARM64_PSEUDO_NMI
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
b .Lskip_pmr_save\@
alternative_else_nop_endif
mrs_s x20, SYS_ICC_PMR_EL1
str w20, [sp, #S_PMR]
mov x20, #GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET
msr_s SYS_ICC_PMR_EL1, x20
.Lskip_pmr_save\@:
#endif
/*
* Registers that may be useful after this macro is invoked:
*
* x20 - ICC_PMR_EL1
* x21 - aborted SP
* x22 - aborted PC
* x23 - aborted PSTATE
*/kernel_exit
pt_regs에서 복귀 상태를 복구하고 ERET합니다. EL0의 KPTI 구성은 trampoline 경로로 이어집니다.
arch/arm64/kernel/entry.S L335–L463
.macro kernel_exit, el
.if \el != 0
disable_daif
.endif
#ifdef CONFIG_ARM64_PSEUDO_NMI
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
b .Lskip_pmr_restore\@
alternative_else_nop_endif
ldr w20, [sp, #S_PMR]
msr_s SYS_ICC_PMR_EL1, x20
/* Ensure priority change is seen by redistributor */
alternative_if_not ARM64_HAS_GIC_PRIO_RELAXED_SYNC
dsb sy
alternative_else_nop_endif
.Lskip_pmr_restore\@:
#endif
ldp x21, x22, [sp, #S_PC] // load ELR, SPSR
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
alternative_if_not ARM64_HAS_PAN
bl __swpan_exit_el\el
alternative_else_nop_endif
#endif
.if \el == 0
ldr x23, [sp, #S_SP] // load return stack pointer
msr sp_el0, x23
tst x22, #PSR_MODE32_BIT // native task?
b.eq 3f
#ifdef CONFIG_ARM64_ERRATUM_845719
alternative_if ARM64_WORKAROUND_845719
#ifdef CONFIG_PID_IN_CONTEXTIDR
mrs x29, contextidr_el1
msr contextidr_el1, x29
#else
msr contextidr_el1, xzr
#endif
alternative_else_nop_endif
#endif
3:
scs_save tsk
/* Ignore asynchronous tag check faults in the uaccess routines */
ldr x0, [tsk, THREAD_SCTLR_USER]
clear_mte_async_tcf x0
#ifdef CONFIG_ARM64_PTR_AUTH
alternative_if ARM64_HAS_ADDRESS_AUTH
/*
* IA was enabled for in-kernel PAC. Disable it now if needed, or
* alternatively install the user's IA. All other per-task keys and
* SCTLR bits were updated on task switch.
*
* No kernel C function calls after this.
*/
tbz x0, SCTLR_ELx_ENIA_SHIFT, 1f
__ptrauth_keys_install_user tsk, x0, x1, x2
b 2f
1:
mrs x0, sctlr_el1
bic x0, x0, SCTLR_ELx_ENIA
msr sctlr_el1, x0
2:
alternative_else_nop_endif
#endif
mte_set_user_gcr tsk, x0, x1
apply_ssbd 0, x0, x1
.endif
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
.if \el == 0
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
alternative_insn "b .L_skip_tramp_exit_\@", nop, ARM64_UNMAP_KERNEL_AT_EL0
msr far_el1, x29
ldr_this_cpu x30, this_cpu_vector, x29
tramp_alias x29, tramp_exit
msr vbar_el1, x30 // install vector table
ldr lr, [sp, #S_LR] // restore x30
add sp, sp, #PT_REGS_SIZE // restore sp
br x29
.L_skip_tramp_exit_\@:
#endif
.endif
ldr lr, [sp, #S_LR]
add sp, sp, #PT_REGS_SIZE // restore sp
.if \el == 0
/* This must be after the last explicit memory access */
alternative_if ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD
tlbi vale1, xzr
dsb nsh
alternative_else_nop_endif
.else
/* Ensure any device/NC reads complete */
alternative_insn nop, "dmb sy", ARM64_WORKAROUND_1508412
.endif
eret
sb
.endm인자 치환 예: kernel_exit 1
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.if 1 != 0
disable_daif
.endif
#ifdef CONFIG_ARM64_PSEUDO_NMI
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
b .Lskip_pmr_restore\@
alternative_else_nop_endif
ldr w20, [sp, #S_PMR]
msr_s SYS_ICC_PMR_EL1, x20
/* Ensure priority change is seen by redistributor */
alternative_if_not ARM64_HAS_GIC_PRIO_RELAXED_SYNC
dsb sy
alternative_else_nop_endif
.Lskip_pmr_restore\@:
#endif
ldp x21, x22, [sp, #S_PC] // load ELR, SPSR
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
alternative_if_not ARM64_HAS_PAN
bl __swpan_exit_el1
alternative_else_nop_endif
#endif
.if 1 == 0
ldr x23, [sp, #S_SP] // load return stack pointer
msr sp_el0, x23
tst x22, #PSR_MODE32_BIT // native task?
b.eq 3f
#ifdef CONFIG_ARM64_ERRATUM_845719
alternative_if ARM64_WORKAROUND_845719
#ifdef CONFIG_PID_IN_CONTEXTIDR
mrs x29, contextidr_el1
msr contextidr_el1, x29
#else
msr contextidr_el1, xzr
#endif
alternative_else_nop_endif
#endif
3:
scs_save tsk
/* Ignore asynchronous tag check faults in the uaccess routines */
ldr x0, [tsk, THREAD_SCTLR_USER]
clear_mte_async_tcf x0
#ifdef CONFIG_ARM64_PTR_AUTH
alternative_if ARM64_HAS_ADDRESS_AUTH
/*
* IA was enabled for in-kernel PAC. Disable it now if needed, or
* alternatively install the user's IA. All other per-task keys and
* SCTLR bits were updated on task switch.
*
* No kernel C function calls after this.
*/
tbz x0, SCTLR_ELx_ENIA_SHIFT, 1f
__ptrauth_keys_install_user tsk, x0, x1, x2
b 2f
1:
mrs x0, sctlr_el1
bic x0, x0, SCTLR_ELx_ENIA
msr sctlr_el1, x0
2:
alternative_else_nop_endif
#endif
mte_set_user_gcr tsk, x0, x1
apply_ssbd 0, x0, x1
.endif
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
.if 1 == 0
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
alternative_insn "b .L_skip_tramp_exit_\@", nop, ARM64_UNMAP_KERNEL_AT_EL0
msr far_el1, x29
ldr_this_cpu x30, this_cpu_vector, x29
tramp_alias x29, tramp_exit
msr vbar_el1, x30 // install vector table
ldr lr, [sp, #S_LR] // restore x30
add sp, sp, #PT_REGS_SIZE // restore sp
br x29
.L_skip_tramp_exit_\@:
#endif
.endif
ldr lr, [sp, #S_LR]
add sp, sp, #PT_REGS_SIZE // restore sp
.if 1 == 0
/* This must be after the last explicit memory access */
alternative_if ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD
tlbi vale1, xzr
dsb nsh
alternative_else_nop_endif
.else
/* Ensure any device/NC reads complete */
alternative_insn nop, "dmb sy", ARM64_WORKAROUND_1508412
.endif
eret
sbkernel_ventry
128바이트 슬롯, pt_regs 공간 확보, 스택 초과 검사, 대상 예외 핸들러 분기를 생성합니다. 인자는 EL·SP 모드·실행 폭·예외 종류입니다.
arch/arm64/kernel/entry.S L39–L101
.macro kernel_ventry, el:req, ht:req, regsize:req, label:req
.align 7
.Lventry_start\@:
.if \el == 0
/*
* This must be the first instruction of the EL0 vector entries. It is
* skipped by the trampoline vectors, to trigger the cleanup.
*/
b .Lskip_tramp_vectors_cleanup\@
.if \regsize == 64
mrs x30, tpidrro_el0
msr tpidrro_el0, xzr
.else
mov x30, xzr
.endif
.Lskip_tramp_vectors_cleanup\@:
.endif
sub sp, sp, #PT_REGS_SIZE
/*
* Test whether the SP has overflowed, without corrupting a GPR.
* Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
* should always be zero.
*/
add sp, sp, x0 // sp' = sp + x0
sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sp
tbnz x0, #THREAD_SHIFT, 0f
sub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0
sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = sp
b el\el\ht\()_\regsize\()_\label
0:
/*
* Either we've just detected an overflow, or we've taken an exception
* while on the overflow stack. Either way, we won't return to
* userspace, and can clobber EL0 registers to free up GPRs.
*/
/* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */
msr tpidr_el0, x0
/* Recover the original x0 value and stash it in tpidrro_el0 */
sub x0, sp, x0
msr tpidrro_el0, x0
/* Switch to the overflow stack */
adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0
/*
* Check whether we were already on the overflow stack. This may happen
* after panic() re-enables interrupts.
*/
mrs x0, tpidr_el0 // sp of interrupted context
sub x0, sp, x0 // delta with top of overflow stack
tst x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?
b.ne __bad_stack // no? -> bad stack pointer
/* We were already on the overflow stack. Restore sp/x0 and carry on. */
sub sp, sp, x0
mrs x0, tpidrro_el0
b el\el\ht\()_\regsize\()_\label
.org .Lventry_start\@ + 128 // Did we overflow the ventry slot?
.endm인자 치환 예: kernel_ventry 1, t, 64, sync
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.align 7
.Lventry_start\@:
.if 1 == 0
/*
* This must be the first instruction of the EL0 vector entries. It is
* skipped by the trampoline vectors, to trigger the cleanup.
*/
b .Lskip_tramp_vectors_cleanup\@
.if 64 == 64
mrs x30, tpidrro_el0
msr tpidrro_el0, xzr
.else
mov x30, xzr
.endif
.Lskip_tramp_vectors_cleanup\@:
.endif
sub sp, sp, #PT_REGS_SIZE
/*
* Test whether the SP has overflowed, without corrupting a GPR.
* Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
* should always be zero.
*/
add sp, sp, x0 // sp' = sp + x0
sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sp
tbnz x0, #THREAD_SHIFT, 0f
sub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0
sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = sp
b el1t_64_sync
0:
/*
* Either we've just detected an overflow, or we've taken an exception
* while on the overflow stack. Either way, we won't return to
* userspace, and can clobber EL0 registers to free up GPRs.
*/
/* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */
msr tpidr_el0, x0
/* Recover the original x0 value and stash it in tpidrro_el0 */
sub x0, sp, x0
msr tpidrro_el0, x0
/* Switch to the overflow stack */
adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0
/*
* Check whether we were already on the overflow stack. This may happen
* after panic() re-enables interrupts.
*/
mrs x0, tpidr_el0 // sp of interrupted context
sub x0, sp, x0 // delta with top of overflow stack
tst x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?
b.ne __bad_stack // no? -> bad stack pointer
/* We were already on the overflow stack. Restore sp/x0 and carry on. */
sub sp, sp, x0
mrs x0, tpidrro_el0
b el1t_64_sync
.org .Lventry_start\@ + 128 // Did we overflow the ventry slot?mrs_s
SYS_* 수치 인코딩으로 MRS 명령을 생성합니다. 첫 인자는 값을 받을 일반 레지스터입니다.
arch/arm64/include/asm/sysreg.h L1136–L1138
.macro mrs_s, rt, sreg
__emit_inst(0xd5200000|(\sreg)|(.L__gpr_num_\rt))
.endm인자 치환 예: mrs_s x20, SYS_ICC_PMR_EL1
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
__emit_inst(0xd5200000|(SYS_ICC_PMR_EL1)|(.L__gpr_num_x20))msr_s
SYS_* 수치 인코딩으로 MSR 명령을 생성합니다. 두 번째 인자의 일반 레지스터 값을 첫 번째 시스템 레지스터에 씁니다.
arch/arm64/include/asm/sysreg.h L1140–L1142
.macro msr_s, sreg, rt
__emit_inst(0xd5000000|(\sreg)|(.L__gpr_num_\rt))
.endm인자 치환 예: msr_s SYS_TFSRE0_EL1, xzr
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
__emit_inst(0xd5000000|(SYS_TFSRE0_EL1)|(.L__gpr_num_xzr))mte_set_gcr
태스크의 제외 마스크와 RRND를 조합해 GCR_EL1에 기록합니다.
arch/arm64/kernel/entry.S L166–L172
.macro mte_set_gcr, mte_ctrl, tmp
#ifdef CONFIG_ARM64_MTE
ubfx \tmp, \mte_ctrl, #MTE_CTRL_GCR_USER_EXCL_SHIFT, #16
orr \tmp, \tmp, #SYS_GCR_EL1_RRND
msr_s SYS_GCR_EL1, \tmp
#endif
.endmmte_set_kernel_gcr
하드웨어 태그 KASAN이 활성화된 경우 커널의 GCR 값을 적용합니다.
arch/arm64/kernel/entry.S L174–L183
.macro mte_set_kernel_gcr, tmp, tmp2
#ifdef CONFIG_KASAN_HW_TAGS
alternative_cb ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
b 1f
alternative_cb_end
mov \tmp, KERNEL_GCR_EL1
msr_s SYS_GCR_EL1, \tmp
1:
#endif
.endm인자 치환 예: mte_set_kernel_gcr x22, x23
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
#ifdef CONFIG_KASAN_HW_TAGS
alternative_cb ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
b 1f
alternative_cb_end
mov x22, KERNEL_GCR_EL1
msr_s SYS_GCR_EL1, x22
1:
#endifmte_set_user_gcr
태스크의 THREAD_MTE_CTRL을 읽어 사용자 GCR 값을 적용합니다.
arch/arm64/kernel/entry.S L185–L195
.macro mte_set_user_gcr, tsk, tmp, tmp2
#ifdef CONFIG_KASAN_HW_TAGS
alternative_cb ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
b 1f
alternative_cb_end
ldr \tmp, [\tsk, #THREAD_MTE_CTRL]
mte_set_gcr \tmp, \tmp2
1:
#endif
.endm인자 치환 예: mte_set_user_gcr tsk, x0, x1
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
#ifdef CONFIG_KASAN_HW_TAGS
alternative_cb ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
b 1f
alternative_cb_end
ldr x0, [tsk, #THREAD_MTE_CTRL]
mte_set_gcr x0, x1
1:
#endifptrauth_keys_install_kernel
현재 구성에 맞는 커널 instruction authentication 키를 설치합니다. 실제 키 쓰기와 대체 패치 조건은 정의 원문에 있습니다.
arch/arm64/include/asm/asm_pointer_auth.h L26–L31
.macro ptrauth_keys_install_kernel tsk, tmp1, tmp2, tmp3
alternative_if ARM64_HAS_ADDRESS_AUTH
__ptrauth_keys_install_kernel_nosync \tsk, \tmp1, \tmp2, \tmp3
isb
alternative_else_nop_endif
.endmarch/arm64/include/asm/asm_pointer_auth.h L41–L42
.macro ptrauth_keys_install_kernel tsk, tmp1, tmp2, tmp3
.endm인자 치환 예: ptrauth_keys_install_kernel x1, x8, x9, x10
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
alternative_if ARM64_HAS_ADDRESS_AUTH
__ptrauth_keys_install_kernel_nosync x1, x8, x9, x10
isb
alternative_else_nop_endifrestore_irq
저장했던 인자 값을 DAIF에 다시 씁니다. 이름은 IRQ이지만 이 정의는 DAIF 전체를 복원합니다.
arch/arm64/include/asm/assembler.h L54–L56
.macro restore_irq, flags
msr daif, \flags
.endm인자 치환 예: restore_irq x11
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
msr daif, x11save_and_disable_daif
DAIF를 인자 레지스터에 저장하고 DAIFSet #0xf로 D/A/I/F를 마스크합니다.
arch/arm64/include/asm/assembler.h L44–L47
.macro save_and_disable_daif, flags
mrs \flags, daif
msr daifset, #0xf
.endm인자 치환 예: save_and_disable_daif x11
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
mrs x11, daif
msr daifset, #0xfsb
CPU가 SB를 지원하면 해당 명령으로, 그렇지 않으면 DSB NSH와 ISB로 추측 실행 배리어를 구성합니다.
arch/arm64/include/asm/assembler.h L104–L112
.macro sb
alternative_if_not ARM64_HAS_SB
dsb nsh
isb
alternative_else
SB_BARRIER_INSN
nop
alternative_endif
.endm인자 치환 예: sb
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
alternative_if_not ARM64_HAS_SB
dsb nsh
isb
alternative_else
SB_BARRIER_INSN
nop
alternative_endifscs_load_current
현재 태스크의 shadow stack 포인터를 x18에 읽습니다. 일반 SP와 별도인 반환 주소 보호용 스택입니다.
arch/arm64/include/asm/scs.h L13–L16
.macro scs_load_current
get_current_task scs_sp
ldr scs_sp, [scs_sp, #TSK_TI_SCS_SP]
.endm인자 치환 예: scs_load_current
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
get_current_task scs_sp
ldr scs_sp, [scs_sp, #TSK_TI_SCS_SP]scs_save
SHADOW_CALL_STACK 설정에서 x18의 shadow stack 포인터를 태스크에 저장합니다. 비활성 구성에서는 비어 있습니다.
arch/arm64/include/asm/scs.h L18–L20
.macro scs_save tsk
str scs_sp, [\tsk, #TSK_TI_SCS_SP]
.endm인자 치환 예: scs_save tsk
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
str scs_sp, [tsk, #TSK_TI_SCS_SP]sdei_handler_exit
exit_mode에 따라 SMC/HVC로 이벤트 완료를 요청합니다. 호출이 돌아오면 B . 루프에 머뭅니다.
arch/arm64/kernel/entry.S L911–L919
.macro sdei_handler_exit exit_mode
/* On success, this call never returns... */
cmp \exit_mode, #SDEI_EXIT_SMC
b.ne 99f
smc #0
b .
99: hvc #0
b .
.endm인자 치환 예: sdei_handler_exit exit_mode=x2
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
/* On success, this call never returns... */
cmp x2, #SDEI_EXIT_SMC
b.ne 99f
smc #0
b .
99: hvc #0
b .tramp_alias
TRAMP_VALIAS와 섹션 내 심볼 오프셋을 더한 별칭 주소를 구성합니다.
arch/arm64/kernel/entry.S L103–L108
.macro tramp_alias, dst, sym
.set .Lalias\@, TRAMP_VALIAS + \sym - .entry.tramp.text
movz \dst, :abs_g2_s:.Lalias\@
movk \dst, :abs_g1_nc:.Lalias\@
movk \dst, :abs_g0_nc:.Lalias\@
.endm인자 치환 예: tramp_alias x29, tramp_exit
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.set .Lalias\@, TRAMP_VALIAS + tramp_exit - .entry.tramp.text
movz x29, :abs_g2_s:.Lalias\@
movk x29, :abs_g1_nc:.Lalias\@
movk x29, :abs_g0_nc:.Lalias\@tramp_data_read_var
재배치 설정에서는 별도 rodata 리터럴에서, 그 외에는 MOVZ/MOVK 상수로 심볼 주소를 얻습니다.
arch/arm64/kernel/entry.S L654–L677
.macro tramp_data_read_var dst, var
#ifdef CONFIG_RELOCATABLE
ldr \dst, .L__tramp_data_\var
.ifndef .L__tramp_data_\var
.pushsection ".entry.tramp.rodata", "a", %progbits
.align 3
.L__tramp_data_\var:
.quad \var
.popsection
.endif
#else
/*
* As !RELOCATABLE implies !RANDOMIZE_BASE the address is always a
* compile time constant (and hence not secret and not worth hiding).
*
* As statically allocated kernel code and data always live in the top
* 47 bits of the address space we can sign-extend bit 47 and avoid an
* instruction to load the upper 16 bits (which must be 0xFFFF).
*/
movz \dst, :abs_g2_s:\var
movk \dst, :abs_g1_nc:\var
movk \dst, :abs_g0_nc:\var
#endif
.endm인자 치환 예: tramp_data_read_var x30, vectors
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
#ifdef CONFIG_RELOCATABLE
ldr x30, .L__tramp_data_vectors
.ifndef .L__tramp_data_vectors
.pushsection ".entry.tramp.rodata", "a", %progbits
.align 3
.L__tramp_data_vectors:
.quad vectors
.popsection
.endif
#else
/*
* As !RELOCATABLE implies !RANDOMIZE_BASE the address is always a
* compile time constant (and hence not secret and not worth hiding).
*
* As statically allocated kernel code and data always live in the top
* 47 bits of the address space we can sign-extend bit 47 and avoid an
* instruction to load the upper 16 bits (which must be 0xFFFF).
*/
movz x30, :abs_g2_s:vectors
movk x30, :abs_g1_nc:vectors
movk x30, :abs_g0_nc:vectors
#endiftramp_map_kernel
TTBR1에 고정 오프셋을 더하고 USER_ASID_FLAG를 지워 커널 매핑으로 전환합니다.
arch/arm64/kernel/entry.S L622–L639
.macro tramp_map_kernel, tmp
mrs \tmp, ttbr1_el1
add \tmp, \tmp, #TRAMP_SWAPPER_OFFSET
bic \tmp, \tmp, #USER_ASID_FLAG
msr ttbr1_el1, \tmp
#ifdef CONFIG_QCOM_FALKOR_ERRATUM_1003
alternative_if ARM64_WORKAROUND_QCOM_FALKOR_E1003
/* ASID already in \tmp[63:48] */
movk \tmp, #:abs_g2_nc:(TRAMP_VALIAS >> 12)
movk \tmp, #:abs_g1_nc:(TRAMP_VALIAS >> 12)
/* 2MB boundary containing the vectors, so we nobble the walk cache */
movk \tmp, #:abs_g0_nc:((TRAMP_VALIAS & ~(SZ_2M - 1)) >> 12)
isb
tlbi vae1, \tmp
dsb nsh
alternative_else_nop_endif
#endif /* CONFIG_QCOM_FALKOR_ERRATUM_1003 */
.endm인자 치환 예: tramp_map_kernel x30
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
mrs x30, ttbr1_el1
add x30, x30, #TRAMP_SWAPPER_OFFSET
bic x30, x30, #USER_ASID_FLAG
msr ttbr1_el1, x30
#ifdef CONFIG_QCOM_FALKOR_ERRATUM_1003
alternative_if ARM64_WORKAROUND_QCOM_FALKOR_E1003
/* ASID already in x30[63:48] */
movk x30, #:abs_g2_nc:(TRAMP_VALIAS >> 12)
movk x30, #:abs_g1_nc:(TRAMP_VALIAS >> 12)
/* 2MB boundary containing the vectors, so we nobble the walk cache */
movk x30, #:abs_g0_nc:((TRAMP_VALIAS & ~(SZ_2M - 1)) >> 12)
isb
tlbi vae1, x30
dsb nsh
alternative_else_nop_endif
#endif /* CONFIG_QCOM_FALKOR_ERRATUM_1003 */tramp_unmap_kernel
TTBR1에서 고정 오프셋을 빼고 USER_ASID_FLAG를 설정해 trampoline 매핑으로 전환합니다.
arch/arm64/kernel/entry.S L642–L652
.macro tramp_unmap_kernel, tmp
mrs \tmp, ttbr1_el1
sub \tmp, \tmp, #TRAMP_SWAPPER_OFFSET
orr \tmp, \tmp, #USER_ASID_FLAG
msr ttbr1_el1, \tmp
/*
* We avoid running the post_ttbr_update_workaround here because
* it's only needed by Cavium ThunderX, which requires KPTI to be
* disabled.
*/
.endm인자 치환 예: tramp_unmap_kernel x29
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
mrs x29, ttbr1_el1
sub x29, x29, #TRAMP_SWAPPER_OFFSET
orr x29, x29, #USER_ASID_FLAG
msr ttbr1_el1, x29
/*
* We avoid running the post_ttbr_update_workaround here because
* it's only needed by Cavium ThunderX, which requires KPTI to be
* disabled.
*/tramp_ventry
선택한 KPTI와 BHB 완화 방식에 맞는 128바이트 슬롯을 생성하고 실제 vectors 슬롯의 +4 위치로 RET합니다.
arch/arm64/kernel/entry.S L684–L738
.macro tramp_ventry, vector_start, regsize, kpti, bhb
.align 7
1:
.if \regsize == 64
msr tpidrro_el0, x30 // Restored in kernel_ventry
.endif
.if \bhb == BHB_MITIGATION_LOOP
/*
* This sequence must appear before the first indirect branch. i.e. the
* ret out of tramp_ventry. It appears here because x30 is free.
*/
__mitigate_spectre_bhb_loop x30
.endif // \bhb == BHB_MITIGATION_LOOP
.if \bhb == BHB_MITIGATION_INSN
clearbhb
isb
.endif // \bhb == BHB_MITIGATION_INSN
.if \kpti == 1
/*
* Defend against branch aliasing attacks by pushing a dummy
* entry onto the return stack and using a RET instruction to
* enter the full-fat kernel vectors.
*/
bl 2f
b .
2:
tramp_map_kernel x30
alternative_insn isb, nop, ARM64_WORKAROUND_QCOM_FALKOR_E1003
tramp_data_read_var x30, vectors
alternative_if_not ARM64_WORKAROUND_CAVIUM_TX2_219_PRFM
prfm plil1strm, [x30, #(1b - \vector_start)]
alternative_else_nop_endif
msr vbar_el1, x30
isb
.else
adr_l x30, vectors
.endif // \kpti == 1
.if \bhb == BHB_MITIGATION_FW
/*
* The firmware sequence must appear before the first indirect branch.
* i.e. the ret out of tramp_ventry. But it also needs the stack to be
* mapped to save/restore the registers the SMC clobbers.
*/
__mitigate_spectre_bhb_fw
.endif // \bhb == BHB_MITIGATION_FW
add x30, x30, #(1b - \vector_start + 4)
ret
.org 1b + 128 // Did we overflow the ventry slot?
.endm상수·구조체 오프셋
S_*·THREAD_*·FREGS_* 등의 구조체 오프셋은 빌드한 C 구조체 배치에서 생성됩니다. 숫자를 고정하지 않고 원래 기호를 사용했습니다.
ARM_SMCCC_ARCH_WORKAROUND_2: include/linux/arm-smccc.h:98BHB_MITIGATION_FW: arch/arm64/kernel/entry.S:681BHB_MITIGATION_INSN: arch/arm64/kernel/entry.S:682BHB_MITIGATION_LOOP: arch/arm64/kernel/entry.S:680BHB_MITIGATION_NONE: arch/arm64/kernel/entry.S:679GIC_PRIO_IRQON: arch/arm64/include/asm/ptrace.h:26GIC_PRIO_PSR_I_SET: arch/arm64/include/asm/ptrace.h:29IRQ_STACK_SIZE: arch/arm64/include/asm/memory.h:140KERNEL_GCR_EL1: arch/arm64/include/asm/sysreg.h:960MTE_CTRL_GCR_USER_EXCL_SHIFT: arch/arm64/include/asm/processor.h:19NO_SYSCALL: arch/arm64/include/asm/ptrace.h:95OVERFLOW_STACK_SIZE: arch/arm64/include/asm/memory.h:142SDEI_1_0_FN_SDEI_EVENT_COMPLETE: include/uapi/linux/arm_sdei.h:15SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME: include/uapi/linux/arm_sdei.h:16SDEI_EV_FAILED: include/uapi/linux/arm_sdei.h:58SDEI_EXIT_SMC: arch/arm64/include/asm/sdei.h:8SDEI_STACK_SIZE: arch/arm64/include/asm/sdei.h:10SET_PSTATE_DIT: arch/arm64/include/asm/sysreg.h:105SYM_L_LOCAL: include/linux/linkage.h:100SYS_GCR_EL1: arch/arm64/include/asm/sysreg.h:282SYS_GCR_EL1_RRND: arch/arm64/include/asm/sysreg.h:944SYS_ICC_PMR_EL1: arch/arm64/include/asm/sysreg.h:300SYS_TFSRE0_EL1: arch/arm64/include/asm/sysreg.h:320SYS_TFSR_EL1_TF0_SHIFT: arch/arm64/include/asm/sysreg.h:968SZ_2M: include/linux/sizes.h:36THREAD_SHIFT: arch/arm64/include/asm/memory.h:122 · arch/arm64/include/asm/memory.h:124TRAMP_SWAPPER_OFFSET: arch/arm64/include/asm/memory.h:208TTBR_ASID_MASK: arch/arm64/include/asm/mmu.h:13USER_ASID_BIT: arch/arm64/include/asm/mmu.h:11USER_ASID_FLAG: arch/arm64/include/asm/mmu.h:12