DUJINLABS.COM

Linux v6.18.37 · ARM64

hibernate-asm.S — 최대 절전 이미지의 메모리 복원

복원 대상 메모리에는 기존 커널 코드도 포함됩니다. 이 루틴은 덮어쓰지 않을 안전한 페이지로 복사되어 TTBR0 매핑에서 실행하며, TTBR1의 선형 매핑을 바꿔 복원 작업을 수행합니다.

역할과 입력

  • 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과 복사 후 주소 독립성에 있습니다.

hibernate-asm.S · L1–L59
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 <james.morse@arm.com>
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로 완료를 기다립니다.

hibernate-asm.S · L60–L83
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 주소로 이동합니다.

hibernate-asm.S · L84–L95
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 구조체 배치에서 생성됩니다. 숫자를 고정하지 않고 원래 기호를 사용했습니다.

asm-offsets.c의 생성 식

    맨 위로 ↑