역할과 입력
- x0=임시 테이블 PA, x1=복원할 테이블 PA, x2=cpu_resume
- x3=복원 목록 VA, x4=EL2 벡터 PA 또는 0, x5=제로 페이지 PA
원본 .S · 빌드 조건: Makefile · 명령·주소 표기 읽는 법
95줄 · SHA-256: 739294a439ffe6521e7b571254f9631f4e27a33d05307231b798cff1af349e85
구조와 흐름
안전한 실행 페이지와 테이블 전환
break_before_make_ttbr_switch는 잠시 빈 테이블을 설치하고 TLB를 정리한 뒤 새 테이블을 설치합니다. x21은 최종 테이블, x30은 cpu_resume 주소, x24는 EL2 처리 여부, x25는 제로 페이지를 보존합니다. 이 루틴 내부에서 일반 함수 BL을 하지 않는 이유도 x30과 복사 후 주소 독립성에 있습니다.
1/* SPDX-License-Identifier: GPL-2.0-only */
2/*
3 * Hibernate low-level support
4 *
5 * Copyright (C) 2016 ARM Ltd.
6 * Author: James Morse <[email protected]>
7 */
8#include <linux/linkage.h>
9#include <linux/errno.h>
10
11#include <asm/asm-offsets.h>
12#include <asm/assembler.h>
13#include <asm/cputype.h>
14#include <asm/memory.h>
15#include <asm/page.h>
16#include <asm/virt.h>
17
18/*
19 * Resume from hibernate
20 *
21 * Loads temporary page tables then restores the memory image.
22 * Finally branches to cpu_resume() to restore the state saved by
23 * swsusp_arch_suspend().
24 *
25 * Because this code has to be copied to a 'safe' page, it can't call out to
26 * other functions by PC-relative address. Also remember that it may be
27 * mid-way through over-writing other functions. For this reason it contains
28 * code from caches_clean_inval_pou() and uses the copy_page() macro.
29 *
30 * This 'safe' page is mapped via ttbr0, and executed from there. This function
31 * switches to a copy of the linear map in ttbr1, performs the restore, then
32 * switches ttbr1 to the original kernel's swapper_pg_dir.
33 *
34 * All of memory gets written to, including code. We need to clean the kernel
35 * text to the Point of Coherence (PoC) before secondary cores can be booted.
36 * Because the kernel modules and executable pages mapped to user space are
37 * also written as data, we clean all pages we touch to the Point of
38 * Unification (PoU).
39 *
40 * x0: physical address of temporary page tables
41 * x1: physical address of swapper page tables
42 * x2: address of cpu_resume
43 * x3: linear map address of restore_pblist in the current kernel
44 * x4: physical address of __hyp_stub_vectors, or 0
45 * x5: physical address of a zero page that remains zero after resume
46 */
47.pushsection ".hibernate_exit.text", "ax"
48SYM_CODE_START(swsusp_arch_suspend_exit)
49 /*
50 * We execute from ttbr0, change ttbr1 to our copied linear map tables
51 * with a break-before-make via the zero page
52 */
53 break_before_make_ttbr_switch x5, x0, x6, x8
54
55 mov x21, x1
56 mov x30, x2
57 mov x24, x4
58 mov x25, x5
59
- L8
- 복사해서 실행할 저수준 코드의 시작·끝을 심볼로 표시하는 linkage 매크로를 가져옵니다. 호출·복귀 명령 자체는 아래에 별도로 작성됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L9
- 커널 오류 번호 정의를 포함합니다. 다만 이 어셈블리 본문은 복원 성공 시 cpu_resume으로 이동하는 경로이며 errno 반환을 수행하는 명령은 없습니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L11
- 복원 페이지 목록 구조체에서 원본 주소·목적지 주소·다음 항목이 위치한 오프셋을 가져옵니다. HIBERN_PBE_ADDR·ORIG·NEXT가 C 구조체와 일치하도록 빌드 과정에서 생성됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L12
- 페이지 복사, 데이터 캐시 라인 크기 계산, 안전한 TTBR 전환 매크로를 가져옵니다. 복원 도중 외부 함수 본문이 덮어써질 수 있어 필요한 작업을 여기의 매크로로 펼칩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L13
- CPU 식별·기능에 관한 ARM64 정의를 포함합니다. 이 본문에서 CPU별 캐시 문제 대응은 alternative_insn과 ARM64_WORKAROUND_CLEAN_CACHE를 통해 선택됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L14
- ARM64 커널 메모리 배치와 크기 관련 정의를 포함합니다. 이 코드는 임시 linear map에서 복원할 페이지 주소를 다루므로 현재 주소 변환 환경과 함께 읽어야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L15
- 커널 페이지 크기 등의 정의를 가져옵니다. 한 항목이 복원하는 크기와 캐시 처리 끝 주소는 PAGE_SIZE를 사용하며 4KiB로 고정해 읽으면 안 됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L16
- ARM64의 EL2 stub 호출 번호와 부팅 모드 등 가상화 공통 정의를 포함합니다. 실제 EL2 재설정은 아래 HVC와 C 쪽 임시 벡터 준비를 함께 봐야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L47
- 복원 도중 덮어써질 커널 본문과 구분하여, 안전한 페이지로 복사할 실행 코드를 이 섹션에 모읍니다. 복사본은 TTBR0의 별도 매핑에서 실행되므로 복원 대상 페이지를 덮어써도 계속 동작할 수 있습니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다. 이전 출력 섹션을 함께 기억하므로 뒤의 .popsection으로 되돌릴 수 있습니다.
- L48
- 하이버네이션 이미지의 메모리를 원래 위치에 덮어쓰는 마지막 복원 단계입니다. x0~x5로 임시 페이지 테이블, 복원할 커널의 페이지 테이블·재개 주소, 복사 목록, EL2 벡터, 빈 페이지 주소를 받습니다. 이 매크로는 정렬된 코드 시작 심벌을 정의하고 다른 오브젝트에서도 참조할 수 있게 합니다.
- L53
- TTBR1을 잠시 빈 페이지 x5로 바꾸고 기존 TLB를 무효화한 뒤 임시 페이지 테이블 x0를 설치합니다. 복원 중 원래 테이블까지 덮어쓰므로 별도로 복사한 linear map이 필요하며, x6·x8은 이 전환의 작업 레지스터입니다.
- L55
- 메모리 복원이 끝난 뒤 설치할 원래 커널의 페이지 테이블 물리 주소를 x21에 보관합니다. 복사 루프에서 x1을 다른 용도로 쓰기 전에 남겨 둡니다. MOV는 x21의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L56
- 최종 재개 주소를 LR인 x30에 넣습니다. 마지막 RET은 이 함수를 호출한 복원용 커널로 돌아가는 대신 저장된 커널의 cpu_resume으로 이동하게 됩니다. MOV는 x30의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L57
- 복원할 커널의 EL2 벡터 물리 주소를 x24에 보관합니다. 0이면 EL2 벡터 복원이 필요하지 않은 경로입니다. MOV는 x24의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L58
- 빈 페이지 물리 주소를 x25에 보관합니다. 루프가 x5를 작업용으로 덮어쓴 뒤에도 원래 페이지 테이블로 바꾸는 break-before-make에 다시 필요합니다. MOV는 x25의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
복원 목록 순회
HIBERN_PBE_ORIG는 목적지, HIBERN_PBE_ADDR는 복원할 데이터가 있는 주소입니다. copy_page는 포인터를 변경하므로 목적지 시작을 x10에 따로 보관합니다. 캐시 라인 크기를 얻어 목적지 시작을 정렬하고 CVAU 또는 대체 CIVAC를 적용합니다. NEXT가 0이 될 때까지 반복한 뒤 DSB ISH로 완료를 기다립니다.
60 /* walk the restore_pblist and use copy_page() to over-write memory */
61 mov x19, x3
62
631: ldr x10, [x19, #HIBERN_PBE_ORIG]
64 mov x0, x10
65 ldr x1, [x19, #HIBERN_PBE_ADDR]
66
67 copy_page x0, x1, x2, x3, x4, x5, x6, x7, x8, x9
68
69 add x1, x10, #PAGE_SIZE
70 /* Clean the copied page to PoU - based on caches_clean_inval_pou() */
71 raw_dcache_line_size x2, x3
72 sub x3, x2, #1
73 bic x4, x10, x3
742: /* clean D line / unified line */
75alternative_insn "dc cvau, x4", "dc civac, x4", ARM64_WORKAROUND_CLEAN_CACHE
76 add x4, x4, x2
77 cmp x4, x1
78 b.lo 2b
79
80 ldr x19, [x19, #HIBERN_PBE_NEXT]
81 cbnz x19, 1b
82 dsb ish /* wait for PoU cleaning to finish */
83
- L61
- 복원할 페이지 목록 restore_pblist의 첫 항목을 x19에 둡니다. 각 항목에는 임시 보관 위치, 원래 위치, 다음 항목의 포인터가 들어 있습니다. MOV는 x19의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L63
- 현재 항목의 orig_address를 x10에 읽습니다. 이미지에서 되살릴 한 페이지의 원래 주소이며 복사 후 캐시 처리에도 다시 사용합니다. 실제로 x19 + (HIBERN_PBE_ORIG) 주소의 메모리 8바이트를 x10에 읽습니다. 이 주소 형식은 기준 레지스터 x19 자체를 갱신하지 않습니다.
- L64
- copy_page의 첫 번째 인자인 목적지 레지스터 x0에 원래 페이지 주소를 넣습니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
- L65
- 현재 항목의 address를 x1에 읽습니다. 디스크에서 읽은 내용을 임시로 보관한 페이지가 복사 원본이 됩니다. 실제로 x19 + (HIBERN_PBE_ADDR) 주소의 메모리 8바이트를 x1에 읽습니다. 이 주소 형식은 기준 레지스터 x19 자체를 갱신하지 않습니다.
- L67
- x1의 임시 페이지를 x0의 원래 위치로 PAGE_SIZE만큼 복사합니다. x2~x9는 데이터를 운반하는 작업 레지스터이며 x0·x1도 페이지 끝까지 증가합니다. 외부 함수를 호출하지 않는 매크로이므로 복원 중 다른 커널 코드가 덮어써져도 사용할 수 있습니다.
- L69
- 원래 주소 x10에 PAGE_SIZE를 더해 캐시 처리 범위의 끝 주소를 x1에 구합니다. 복사된 데이터가 나중에 코드로 실행될 수도 있으므로 페이지 전체를 처리합니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L71
- 현재 CPU의 CTR_EL0에서 최소 데이터 캐시 라인 크기를 읽어 바이트 단위로 x2에 구합니다. x3은 계산용이며, 다음 루프는 이 크기씩 주소를 이동합니다. CTR_EL0.DminLine은 바이트 수 자체가 아닙니다. 4를 이 필드 값만큼 왼쪽으로 이동하여 바이트 수를 구합니다.
- L72
- 캐시 라인 크기에서 1을 빼 정렬 마스크를 만듭니다. 예를 들어 64바이트 라인이면 63이지만 실제 값은 앞서 읽은 CPU 정보에 따릅니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L73
- 페이지 시작 주소의 하위 정렬 비트를 지워 첫 캐시 라인 시작 주소를 x4에 만듭니다. 페이지의 첫 바이트를 포함하는 라인부터 처리합니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.
- L74
- 복사한 페이지에서 캐시 라인 하나를 정리하는 루프의 시작입니다. 뒤의 B.LO 2b가 이 위치로 돌아오며 숫자 라벨 자체는 명령을 추가하지 않습니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
- L75
- 일반 CPU에서는 DC CVAU로 복사한 내용을 명령·데이터가 만나는 PoU까지 정리합니다. ARM64_WORKAROUND_CLEAN_CACHE가 필요한 CPU에는 DC CIVAC로 바꿔 PoC까지 clean하고 invalidate합니다. 이 선택은 부팅 때 CPU 기능에 따른 alternatives 패치로 이루어집니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.
- L76
- 현재 주소를 데이터 캐시 라인 크기만큼 증가시켜 다음 라인을 준비합니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
- L77
- 다음 캐시 라인 주소와 페이지 끝을 비교합니다. 두 주소의 대소로 아직 처리할 라인이 남았는지 판단합니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.
- L78
- 끝 주소보다 작으면 같은 페이지의 다음 캐시 라인을 처리합니다. 메모리 주소 비교이므로 unsigned 조건인 LO를 사용합니다. 앞서 계산한 NZCV 중 C=0 조건으로 분기합니다. 이 줄은 두 값을 새로 비교하지 않으며 조건이 맞지 않으면 다음 명령으로 진행합니다.
- L80
- 복원을 마친 항목에서 다음 페이지 항목의 포인터를 읽습니다. 실제로 x19 + (HIBERN_PBE_NEXT) 주소의 메모리 8바이트를 x19에 읽습니다.
- L81
- 다음 항목이 NULL이 아니면 다시 페이지 복사부터 수행합니다. NULL이면 모든 복원 페이지의 데이터 저장과 캐시 정리 요청이 끝난 상태입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L82
- Inner Shareable 영역의 앞선 메모리·캐시 작업 완료를 기다립니다. 복원한 내용을 이후 실행 단계에서 볼 수 있도록, 페이지 테이블 전환 전에 복사 결과를 확정합니다.
복원된 커널의 실행 상태로 이동
원래 커널의 TTBR1을 다시 설치하고 IC IALLUIS로 명령어 캐시를 무효화합니다. x24가 0이면 HVC를 건너뜁니다. RET는 이 루틴을 호출한 위치가 아니라 56행에서 x30에 보존한 cpu_resume 주소로 이동합니다.
84 /* switch to the restored kernels page tables */
85 break_before_make_ttbr_switch x25, x21, x6, x8
86
87 ic ialluis
88 dsb ish
89 isb
90
91 cbz x24, 3f /* Do we need to re-initialise EL2? */
92 hvc #0
933: ret
94SYM_CODE_END(swsusp_arch_suspend_exit)
95.popsection
- L85
- x25의 빈 페이지를 거쳐 x21에 저장해 둔 원래 커널의 TTBR1 테이블로 바꿉니다. 이제 임시 복사 테이블 대신 하이버네이션 이미지와 함께 복원된 주소 공간을 사용합니다.
- L87
- Inner Shareable 영역의 명령 캐시를 무효화합니다. 같은 주소에 예전과 다른 명령 바이트를 덮어썼으므로 낡은 명령을 실행하지 않게 해야 합니다. 무효화 요청과 이후 명령 실행에 대한 동기화는 구분되며, 뒤의 DSB·ISB가 그 순서를 마무리합니다.
- L88
- 앞서 요청한 명령 캐시 무효화가 완료될 때까지 기다립니다.
- L89
- 이후 명령을 갱신된 실행 상태에서 다시 가져오게 합니다. 복원한 코드로 분기하기 전에 캐시 동기화 순서를 마무리합니다.
- L91
- 보관한 EL2 벡터 주소 x24가 0이면 HVC를 건너뜁니다. 이 하이버네이션 이미지가 EL2 재설정을 필요로 하는지에 따라 실행 중 선택하는 분기입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
- L92
- 주석상 목적은 복원된 커널에 맞게 EL2를 다시 설정하는 것입니다. HVC #0의 0은 호출 번호가 아니며 실제 서비스는 x0로 구분됩니다. 다만 이 v6.18.37 함수에는 HVC_SET_VECTORS와 새 벡터 주소를 x0·x1에 준비하는 코드가 보이지 않으므로, 이 한 줄을 두고 벡터 설치가 완료된다고 설명할 수는 없습니다. 호출 인자를 요구하는 trans_pgd_stub_vectors와 함께 확인해야 하는 부분입니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.
- L93
- 앞서 x30에 저장해 둔 cpu_resume 주소로 이동합니다. 복원용 커널의 호출 지점으로 돌아가는 것이 아니라, 복원된 커널이 CPU 레지스터와 스택을 되살리는 절차를 시작합니다.
- L94
- (swsusp_arch_suspend_exit)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
- L95
- 별도 복사용 .hibernate_exit.text의 생성을 끝내고 이전 섹션으로 돌아갑니다. 실행 중 cpu_resume에서 돌아온다는 뜻은 아닙니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다.
매크로 정의와 확장
각 정의는 v6.18.37 원문입니다. 역슬래시 인자는 사용 위치의 값으로 치환되며, 안쪽 매크로와 조건부 블록은 이후 단계에서 다시 처리됩니다.
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)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 "dc cvau, x4", "dc civac, x4", ARM64_WORKAROUND_CLEAN_CACHE
1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.
.if x4"
661: "dc cvau
662: .pushsection .altinstructions, "a"
altinstruction_entry 661b, 663f, "dc civac, 662b-661b, 664f-663f
.popsection
.subsection 1
663: x4"
664: .org . - (664b-663b) + (662b-661b)
.org . - (662b-661b) + (664b-663b)
.previous
.endif상수·구조체 오프셋
S_*·THREAD_*·FREGS_* 등의 구조체 오프셋은 빌드한 C 구조체 배치에서 생성됩니다. 숫자를 고정하지 않고 원래 기호를 사용했습니다.