← Documents Documentation/power/suspend-and-cpuhotplug.rst GitHub 원문 ↗

Linux 6.18.37 · Power

Interaction of Suspend code (S3) with the CPU hotplug infrastructure

Suspend와 일반 CPU hotplug의 lock·callback 경로, microcode 관리와 freezer race를 설명합니다.

Source pathDocumentation/power/suspend-and-cpuhotplug.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

suspend-and-cpuhotplug.rst:1-287

Suspend와 일반 CPU hotplug의 lock·callback 경로, microcode 관리와 freezer race를 설명합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ====================================================================
2 Interaction of Suspend code (S3) with the CPU hotplug infrastructure
3 ====================================================================
4
5 (C) 2011 - 2014 Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com>
6
7
8 I. Differences between CPU hotplug and Suspend-to-RAM
9 ======================================================
10
11 How does the regular CPU hotplug code differ from how the Suspend-to-RAM
12 infrastructure uses it internally? And where do they share common code?
13
14 Well, a picture is worth a thousand words... So ASCII art follows :-)
15
16 [This depicts the current design in the kernel, and focuses only on the
17 interactions involving the freezer and CPU hotplug and also tries to explain
18 the locking involved. It outlines the notifications involved as well.
19 But please note that here, only the call paths are illustrated, with the aim
20 of describing where they take different paths and where they share code.
21 What happens when regular CPU hotplug and Suspend-to-RAM race with each other
22 is not depicted here.]
23
24 On a high level, the suspend-resume cycle goes like this::
25
26 |Freeze| -> |Disable nonboot| -> |Do suspend| -> |Enable nonboot| -> |Thaw |
27 |tasks | | cpus | | | | cpus | |tasks|
28
29
30 More details follow::
31
32 Suspend call path
33 -----------------
34
35 Write 'mem' to
36 /sys/power/state
37 sysfs file
38 |
39 v
40 Acquire system_transition_mutex lock
41 |
42 v
43 Send PM_SUSPEND_PREPARE
44 notifications
45 |
46 v
47 Freeze tasks
48 |
49 |
50 v
51 freeze_secondary_cpus()
52 /* start */
53 |
54 v
55 Acquire cpu_add_remove_lock
56 |
57 v
58 Iterate over CURRENTLY
59 online CPUs
60 |
61 |
62 | ----------
63 v | L
64 ======> _cpu_down() |
65 | [This takes cpuhotplug.lock |
66 Common | before taking down the CPU |
67 code | and releases it when done] | O
68 | While it is at it, notifications |
69 | are sent when notable events occur, |
70 ======> by running all registered callbacks. |
71 | | O
72 | |
73 | |
74 v |
75 Note down these cpus in | P
76 frozen_cpus mask ----------
77 |
78 v
79 Disable regular cpu hotplug
80 by increasing cpu_hotplug_disabled
81 |
82 v
83 Release cpu_add_remove_lock
84 |
85 v
86 /* freeze_secondary_cpus() complete */
87 |
88 v
89 Do suspend
90
91
92
93 Resuming back is likewise, with the counterparts being (in the order of
94 execution during resume):
95
96 * thaw_secondary_cpus() which involves::
97
98 | Acquire cpu_add_remove_lock
99 | Decrease cpu_hotplug_disabled, thereby enabling regular cpu hotplug
100 | Call _cpu_up() [for all those cpus in the frozen_cpus mask, in a loop]
101 | Release cpu_add_remove_lock
102 v
103
104 * thaw tasks
105 * send PM_POST_SUSPEND notifications
106 * Release system_transition_mutex lock.
107
108
109 It is to be noted here that the system_transition_mutex lock is acquired at the
110 very beginning, when we are just starting out to suspend, and then released only
111 after the entire cycle is complete (i.e., suspend + resume).
112
113 ::
114
115
116
117 Regular CPU hotplug call path
118 -----------------------------
119
120 Write 0 (or 1) to
121 /sys/devices/system/cpu/cpu*/online
122 sysfs file
123 |
124 |
125 v
126 cpu_down()
127 |
128 v
129 Acquire cpu_add_remove_lock
130 |
131 v
132 If cpu_hotplug_disabled > 0
133 return gracefully
134 |
135 |
136 v
137 ======> _cpu_down()
138 | [This takes cpuhotplug.lock
139 Common | before taking down the CPU
140 code | and releases it when done]
141 | While it is at it, notifications
142 | are sent when notable events occur,
143 ======> by running all registered callbacks.
144 |
145 |
146 v
147 Release cpu_add_remove_lock
148 [That's it!, for
149 regular CPU hotplug]
150
151
152
153 So, as can be seen from the two diagrams (the parts marked as "Common code"),
154 regular CPU hotplug and the suspend code path converge at the _cpu_down() and
155 _cpu_up() functions. They differ in the arguments passed to these functions,
156 in that during regular CPU hotplug, 0 is passed for the 'tasks_frozen'
157 argument. But during suspend, since the tasks are already frozen by the time
158 the non-boot CPUs are offlined or onlined, the _cpu_*() functions are called
159 with the 'tasks_frozen' argument set to 1.
160 [See below for some known issues regarding this.]
161
162
163 Important files and functions/entry points:
164 -------------------------------------------
165
166 - kernel/power/process.c : freeze_processes(), thaw_processes()
167 - kernel/power/suspend.c : suspend_prepare(), suspend_enter(), suspend_finish()
168 - kernel/cpu.c: cpu_[up|down](), _cpu_[up|down](),
169 [disable|enable]_nonboot_cpus()
170
171
172
173 II. What are the issues involved in CPU hotplug?
174 ------------------------------------------------
175
176 There are some interesting situations involving CPU hotplug and microcode
177 update on the CPUs, as discussed below:
178
179 [Please bear in mind that the kernel requests the microcode images from
180 userspace, using the request_firmware() function defined in
181 drivers/base/firmware_loader/main.c]
182
183
184 a. When all the CPUs are identical:
185
186 This is the most common situation and it is quite straightforward: we want
187 to apply the same microcode revision to each of the CPUs.
188 To give an example of x86, the collect_cpu_info() function defined in
189 arch/x86/kernel/microcode_core.c helps in discovering the type of the CPU
190 and thereby in applying the correct microcode revision to it.
191 But note that the kernel does not maintain a common microcode image for the
192 all CPUs, in order to handle case 'b' described below.
193
194
195 b. When some of the CPUs are different than the rest:
196
197 In this case since we probably need to apply different microcode revisions
198 to different CPUs, the kernel maintains a copy of the correct microcode
199 image for each CPU (after appropriate CPU type/model discovery using
200 functions such as collect_cpu_info()).
201
202
203 c. When a CPU is physically hot-unplugged and a new (and possibly different
204 type of) CPU is hot-plugged into the system:
205
206 In the current design of the kernel, whenever a CPU is taken offline during
207 a regular CPU hotplug operation, upon receiving the CPU_DEAD notification
208 (which is sent by the CPU hotplug code), the microcode update driver's
209 callback for that event reacts by freeing the kernel's copy of the
210 microcode image for that CPU.
211
212 Hence, when a new CPU is brought online, since the kernel finds that it
213 doesn't have the microcode image, it does the CPU type/model discovery
214 afresh and then requests the userspace for the appropriate microcode image
215 for that CPU, which is subsequently applied.
216
217 For example, in x86, the mc_cpu_callback() function (which is the microcode
218 update driver's callback registered for CPU hotplug events) calls
219 microcode_update_cpu() which would call microcode_init_cpu() in this case,
220 instead of microcode_resume_cpu() when it finds that the kernel doesn't
221 have a valid microcode image. This ensures that the CPU type/model
222 discovery is performed and the right microcode is applied to the CPU after
223 getting it from userspace.
224
225
226 d. Handling microcode update during suspend/hibernate:
227
228 Strictly speaking, during a CPU hotplug operation which does not involve
229 physically removing or inserting CPUs, the CPUs are not actually powered
230 off during a CPU offline. They are just put to the lowest C-states possible.
231 Hence, in such a case, it is not really necessary to re-apply microcode
232 when the CPUs are brought back online, since they wouldn't have lost the
233 image during the CPU offline operation.
234
235 This is the usual scenario encountered during a resume after a suspend.
236 However, in the case of hibernation, since all the CPUs are completely
237 powered off, during restore it becomes necessary to apply the microcode
238 images to all the CPUs.
239
240 [Note that we don't expect someone to physically pull out nodes and insert
241 nodes with a different type of CPUs in-between a suspend-resume or a
242 hibernate/restore cycle.]
243
244 In the current design of the kernel however, during a CPU offline operation
245 as part of the suspend/hibernate cycle (cpuhp_tasks_frozen is set),
246 the existing copy of microcode image in the kernel is not freed up.
247 And during the CPU online operations (during resume/restore), since the
248 kernel finds that it already has copies of the microcode images for all the
249 CPUs, it just applies them to the CPUs, avoiding any re-discovery of CPU
250 type/model and the need for validating whether the microcode revisions are
251 right for the CPUs or not (due to the above assumption that physical CPU
252 hotplug will not be done in-between suspend/resume or hibernate/restore
253 cycles).
254
255
256 III. Known problems
257 ===================
258
259 Are there any known problems when regular CPU hotplug and suspend race
260 with each other?
261
262 Yes, they are listed below:
263
264 1. When invoking regular CPU hotplug, the 'tasks_frozen' argument passed to
265 the _cpu_down() and _cpu_up() functions is *always* 0.
266 This might not reflect the true current state of the system, since the
267 tasks could have been frozen by an out-of-band event such as a suspend
268 operation in progress. Hence, the cpuhp_tasks_frozen variable will not
269 reflect the frozen state and the CPU hotplug callbacks which evaluate
270 that variable might execute the wrong code path.
271
272 2. If a regular CPU hotplug stress test happens to race with the freezer due
273 to a suspend operation in progress at the same time, then we could hit the
274 situation described below:
275
276 * A regular cpu online operation continues its journey from userspace
277 into the kernel, since the freezing has not yet begun.
278 * Then freezer gets to work and freezes userspace.
279 * If cpu online has not yet completed the microcode update stuff by now,
280 it will now start waiting on the frozen userspace in the
281 TASK_UNINTERRUPTIBLE state, in order to get the microcode image.
282 * Now the freezer continues and tries to freeze the remaining tasks. But
283 due to this wait mentioned above, the freezer won't be able to freeze
284 the cpu online hotplug task and hence freezing of tasks fails.
285
286 As a result of this task freezing failure, the suspend operation gets
287 aborted.
288

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Suspend와 CPU hotplug 비교 범위

1-23

이 문서는 Suspend-to-RAM(S3)이 CPU hotplug infrastructure를 내부적으로 사용하는 방식과 일반 CPU hotplug의 차이·공통 코드를 설명합니다. Freezer, CPU hotplug, 관련 lock과 notification의 call path에 초점을 둡니다.

원문 diagram은 두 경로가 갈라지고 합쳐지는 지점만 나타내며, 일반 hotplug와 suspend가 서로 경쟁할 때의 실제 interleaving은 그리지 않습니다. 해당 race는 마지막 절에서 별도로 다룹니다.

====================================================================
Interaction of Suspend code (S3) with the CPU hotplug infrastructure
====================================================================

(C) 2011 - 2014 Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com>


I. Differences between CPU hotplug and Suspend-to-RAM
======================================================

How does the regular CPU hotplug code differ from how the Suspend-to-RAM
infrastructure uses it internally? And where do they share common code?

Well, a picture is worth a thousand words... So ASCII art follows :-)

[This depicts the current design in the kernel, and focuses only on the
interactions involving the freezer and CPU hotplug and also tries to explain
the locking involved. It outlines the notifications involved as well.
But please note that here, only the call paths are illustrated, with the aim
of describing where they take different paths and where they share code.
What happens when regular CPU hotplug and Suspend-to-RAM race with each other
is not depicted here.]

Suspend call path

24-92

Suspend-resume의 큰 흐름은 task freeze, nonboot CPU disable, suspend, nonboot CPU enable, task thaw 순서입니다.

`/sys/power/state`에 `mem`을 쓰면 `system_transition_mutex`를 획득하고 `PM_SUSPEND_PREPARE` notification을 보낸 뒤 task를 freeze합니다. 이어 `freeze_secondary_cpus()`가 `cpu_add_remove_lock`을 잡고 현재 online CPU를 순회합니다.

각 nonboot CPU에는 공통 코드 `_cpu_down()`을 호출합니다. 이 함수는 CPU를 내리는 동안 `cpuhotplug.lock`을 잡고 notable event마다 등록 callback을 실행합니다. 완료된 CPU는 `frozen_cpus` mask에 기록합니다.

그 뒤 `cpu_hotplug_disabled`를 증가시켜 일반 CPU hotplug를 disable하고 `cpu_add_remove_lock`을 해제한 다음 실제 suspend 단계로 들어갑니다.

Suspend에서 nonboot CPU offline
write mem to /sys/power/statelock system_transition_mutexPM_SUSPEND_PREPAREfreeze tasksfreeze_secondary_cpuslock cpu_add_remove_lockloop online CPUs -> _cpu_down + callbacksrecord frozen_cpusincrement cpu_hotplug_disabledunlock -> suspend

원문의 큰 ASCII call path를 lock과 상태 변경 순서로 재구성했습니다.

On a high level, the suspend-resume cycle goes like this::

  |Freeze| -> |Disable nonboot| -> |Do suspend| -> |Enable nonboot| -> |Thaw |
  |tasks |    |     cpus      |    |          |    |     cpus     |    |tasks|


More details follow::

                                Suspend call path
                                -----------------

                                  Write 'mem' to
                                /sys/power/state
                                    sysfs file
                                        |
                                        v
                               Acquire system_transition_mutex lock
                                        |
                                        v
                             Send PM_SUSPEND_PREPARE
                                   notifications
                                        |
                                        v
                                   Freeze tasks
                                        |
                                        |
                                        v
                              freeze_secondary_cpus()
                                   /* start */
                                        |
                                        v
                            Acquire cpu_add_remove_lock
                                        |
                                        v
                             Iterate over CURRENTLY
                                   online CPUs
                                        |
                                        |
                                        |                ----------
                                        v                          | L
             ======>               _cpu_down()                     |
            |              [This takes cpuhotplug.lock             |
  Common    |               before taking down the CPU             |
   code     |               and releases it when done]             | O
            |            While it is at it, notifications          |
            |            are sent when notable events occur,       |
             ======>     by running all registered callbacks.      |
                                        |                          | O
                                        |                          |
                                        |                          |
                                        v                          |
                            Note down these cpus in                | P
                                frozen_cpus mask         ----------
                                        |
                                        v
                           Disable regular cpu hotplug
                        by increasing cpu_hotplug_disabled
                                        |
                                        v
                            Release cpu_add_remove_lock
                                        |
                                        v
                       /* freeze_secondary_cpus() complete */
                                        |
                                        v
                                   Do suspend


Resume path와 전환 lock

93-112

Resume에서는 `thaw_secondary_cpus()`가 `cpu_add_remove_lock`을 획득하고 `cpu_hotplug_disabled`를 감소시켜 일반 hotplug를 다시 허용합니다. 그런 다음 `frozen_cpus` mask의 CPU마다 `_cpu_up()`을 호출하고 lock을 해제합니다.

이후 task를 thaw하고 `PM_POST_SUSPEND` notification을 보낸 뒤 `system_transition_mutex`를 해제합니다. 이 mutex는 suspend 시작 직전에 잡혀 suspend와 resume 전체 cycle이 끝날 때까지 유지됩니다.

Resume 순서
lock cpu_add_remove_lockdecrement cpu_hotplug_disabledloop frozen_cpus -> _cpu_upunlockthaw tasksPM_POST_SUSPENDunlock system_transition_mutex

Suspend에서 기록한 frozen_cpus만 다시 online합니다.

Resuming back is likewise, with the counterparts being (in the order of
execution during resume):

* thaw_secondary_cpus() which involves::

   |  Acquire cpu_add_remove_lock
   |  Decrease cpu_hotplug_disabled, thereby enabling regular cpu hotplug
   |  Call _cpu_up() [for all those cpus in the frozen_cpus mask, in a loop]
   |  Release cpu_add_remove_lock
   v

* thaw tasks
* send PM_POST_SUSPEND notifications
* Release system_transition_mutex lock.


It is to be noted here that the system_transition_mutex lock is acquired at the
very beginning, when we are just starting out to suspend, and then released only
after the entire cycle is complete (i.e., suspend + resume).

일반 CPU hotplug와 공통 코드

113-162

일반 CPU offline은 `/sys/devices/system/cpu/cpu*/online`에 0을 쓰면서 시작합니다. `cpu_down()`이 `cpu_add_remove_lock`을 잡고 `cpu_hotplug_disabled > 0`이면 정상적으로 빠져나옵니다. 허용되면 `_cpu_down()`이 `cpuhotplug.lock` 아래 CPU를 내리고 등록 callback을 실행한 뒤 바깥 lock을 해제합니다.

CPU online은 대응하는 `cpu_up()`과 `_cpu_up()` 경로를 사용합니다. 따라서 일반 hotplug와 suspend 경로는 `_cpu_down()` 및 `_cpu_up()`에서 합쳐집니다.

차이는 `tasks_frozen` 인수입니다. 일반 hotplug는 항상 0을 넘기지만 suspend에서는 nonboot CPU를 offline/online할 때 이미 task가 freeze되어 있으므로 1을 넘깁니다.

Regular CPU hotplug
write cpu*/onlinecpu_downlock cpu_add_remove_lockcpu_hotplug_disabled > 0 ? return_cpu_down + cpuhp callbacksunlock

cpu_hotplug_disabled 검사를 통과한 뒤 suspend와 같은 저수준 함수를 사용합니다.

두 경로의 합류
경로공통 함수tasks_frozen
Regular hotplug_cpu_down / _cpu_up0
Suspend/resume_cpu_down / _cpu_up1

공통 저수준 함수는 같지만 freezer 상태 인수가 다릅니다.

::



                          Regular CPU hotplug call path
                          -----------------------------

                                Write 0 (or 1) to
                       /sys/devices/system/cpu/cpu*/online
                                    sysfs file
                                        |
                                        |
                                        v
                                    cpu_down()
                                        |
                                        v
                           Acquire cpu_add_remove_lock
                                        |
                                        v
                          If cpu_hotplug_disabled > 0
                                return gracefully
                                        |
                                        |
                                        v
             ======>                _cpu_down()
            |              [This takes cpuhotplug.lock
  Common    |               before taking down the CPU
   code     |               and releases it when done]
            |            While it is at it, notifications
            |           are sent when notable events occur,
             ======>    by running all registered callbacks.
                                        |
                                        |
                                        v
                          Release cpu_add_remove_lock
                               [That's it!, for
                              regular CPU hotplug]



So, as can be seen from the two diagrams (the parts marked as "Common code"),
regular CPU hotplug and the suspend code path converge at the _cpu_down() and
_cpu_up() functions. They differ in the arguments passed to these functions,
in that during regular CPU hotplug, 0 is passed for the 'tasks_frozen'
argument. But during suspend, since the tasks are already frozen by the time
the non-boot CPUs are offlined or onlined, the _cpu_*() functions are called
with the 'tasks_frozen' argument set to 1.
[See below for some known issues regarding this.]

중요 파일과 microcode 문제

163-183

Freezer entry point는 `kernel/power/process.c`의 `freeze_processes()`와 `thaw_processes()`입니다. Suspend entry point는 `kernel/power/suspend.c`의 `suspend_prepare()`, `suspend_enter()`, `suspend_finish()`입니다. CPU hotplug 쪽은 `kernel/cpu.c`의 `cpu_[up|down]()`, `_cpu_[up|down]()`, `[disable|enable]_nonboot_cpus()`입니다.

CPU hotplug에서 중요한 추가 문제는 CPU microcode update입니다. Kernel은 `drivers/base/firmware_loader/main.c`의 `request_firmware()`로 userspace에 microcode image를 요청한다는 점을 전제로 다음 경우를 구분합니다.

관련 source
파일함수
kernel/power/process.cfreeze_processes, thaw_processes
kernel/power/suspend.csuspend_prepare, suspend_enter, suspend_finish
kernel/cpu.ccpu_up/down, _cpu_up/down, enable/disable_nonboot_cpus
drivers/base/firmware_loader/main.crequest_firmware

Freezer, suspend, CPU hotplug의 경계를 따라가야 합니다.

Important files and functions/entry points:
-------------------------------------------

- kernel/power/process.c : freeze_processes(), thaw_processes()
- kernel/power/suspend.c : suspend_prepare(), suspend_enter(), suspend_finish()
- kernel/cpu.c: cpu_[up|down](), _cpu_[up|down](),
  [disable|enable]_nonboot_cpus()



II. What are the issues involved in CPU hotplug?
------------------------------------------------

There are some interesting situations involving CPU hotplug and microcode
update on the CPUs, as discussed below:

[Please bear in mind that the kernel requests the microcode images from
userspace, using the request_firmware() function defined in
drivers/base/firmware_loader/main.c]

동일하거나 다른 CPU의 microcode

184-202

모든 CPU가 동일하면 같은 microcode revision을 각각 적용합니다. x86의 `arch/x86/kernel/microcode_core.c`에 있는 `collect_cpu_info()`가 CPU type을 찾아 올바른 revision을 적용하도록 돕습니다.

Kernel은 아래의 이기종 CPU 경우를 처리하기 위해 모든 CPU가 같더라도 공통 image 하나만 유지하지 않습니다. 일부 CPU가 다르면 `collect_cpu_info()` 같은 함수로 type/model을 찾은 뒤 CPU마다 올바른 microcode image 사본을 유지합니다.

Microcode 보관
CPU 구성처리
모두 동일같은 revision 적용, type discovery 수행
일부가 다름CPU별 type/model discovery와 image 사본 유지

CPU 구성이 같아도 per-CPU image 관리 원칙을 유지합니다.

a. When all the CPUs are identical:

   This is the most common situation and it is quite straightforward: we want
   to apply the same microcode revision to each of the CPUs.
   To give an example of x86, the collect_cpu_info() function defined in
   arch/x86/kernel/microcode_core.c helps in discovering the type of the CPU
   and thereby in applying the correct microcode revision to it.
   But note that the kernel does not maintain a common microcode image for the
   all CPUs, in order to handle case 'b' described below.


b. When some of the CPUs are different than the rest:

   In this case since we probably need to apply different microcode revisions
   to different CPUs, the kernel maintains a copy of the correct microcode
   image for each CPU (after appropriate CPU type/model discovery using
   functions such as collect_cpu_info()).

Physical CPU 교체

203-225

CPU를 물리적으로 hot-unplug하고 다른 type의 CPU를 넣을 수 있는 일반 hotplug에서는 offline 시 `CPU_DEAD` notification을 받은 microcode driver callback이 해당 CPU의 kernel-side image 사본을 해제합니다.

새 CPU를 online하면 image가 없으므로 type/model discovery를 다시 수행하고 userspace에 적절한 image를 요청한 뒤 적용합니다.

x86에서는 hotplug event에 등록된 `mc_cpu_callback()`이 `microcode_update_cpu()`를 호출합니다. Valid image가 없으면 `microcode_resume_cpu()` 대신 `microcode_init_cpu()`로 들어가 discovery와 userspace image 획득을 다시 수행합니다.

CPU 교체 시 microcode
physical CPU offlineCPU_DEADfree per-CPU imagenew CPU onlinecollect type/modelrequest firmwaremicrocode_init_cpu

CPU_DEAD에서 이전 image를 버려 새 CPU type을 반드시 재판별합니다.

c. When a CPU is physically hot-unplugged and a new (and possibly different
   type of) CPU is hot-plugged into the system:

   In the current design of the kernel, whenever a CPU is taken offline during
   a regular CPU hotplug operation, upon receiving the CPU_DEAD notification
   (which is sent by the CPU hotplug code), the microcode update driver's
   callback for that event reacts by freeing the kernel's copy of the
   microcode image for that CPU.

   Hence, when a new CPU is brought online, since the kernel finds that it
   doesn't have the microcode image, it does the CPU type/model discovery
   afresh and then requests the userspace for the appropriate microcode image
   for that CPU, which is subsequently applied.

   For example, in x86, the mc_cpu_callback() function (which is the microcode
   update driver's callback registered for CPU hotplug events) calls
   microcode_update_cpu() which would call microcode_init_cpu() in this case,
   instead of microcode_resume_cpu() when it finds that the kernel doesn't
   have a valid microcode image. This ensures that the CPU type/model
   discovery is performed and the right microcode is applied to the CPU after
   getting it from userspace.

Suspend와 hibernation의 microcode

226-255

CPU를 물리적으로 제거하지 않는 일반 offline에서는 CPU 전원을 완전히 끄지 않고 가능한 가장 낮은 C-state에 둘 뿐이므로 online 때 microcode를 다시 적용할 필요가 없습니다. 이는 suspend 뒤 resume에서 흔한 상황입니다.

Hibernation에서는 모든 CPU가 완전히 꺼지므로 restore할 때 모든 CPU에 microcode image를 다시 적용해야 합니다. Suspend/resume 또는 hibernate/restore 사이에 다른 type CPU를 물리적으로 교체하지 않는다는 가정을 사용합니다.

현재 kernel은 suspend/hibernate cycle의 CPU offline에서 `cpuhp_tasks_frozen`이 set되어 있으면 기존 microcode image를 해제하지 않습니다. Resume/restore에서 image가 이미 있으므로 type/model을 다시 찾거나 revision 적합성을 재검증하지 않고 곧바로 CPU에 적용합니다.

Sleep 종류와 microcode
상황CPU 전원Resume/restore 처리
Suspend낮은 C-state, 완전 off 아님보관 image 사용, 대개 재적용 불필요
Hibernation완전 off보관 image를 모든 CPU에 재적용

전원 손실 여부와 image 보관 정책이 다릅니다.

d. Handling microcode update during suspend/hibernate:

   Strictly speaking, during a CPU hotplug operation which does not involve
   physically removing or inserting CPUs, the CPUs are not actually powered
   off during a CPU offline. They are just put to the lowest C-states possible.
   Hence, in such a case, it is not really necessary to re-apply microcode
   when the CPUs are brought back online, since they wouldn't have lost the
   image during the CPU offline operation.

   This is the usual scenario encountered during a resume after a suspend.
   However, in the case of hibernation, since all the CPUs are completely
   powered off, during restore it becomes necessary to apply the microcode
   images to all the CPUs.

   [Note that we don't expect someone to physically pull out nodes and insert
   nodes with a different type of CPUs in-between a suspend-resume or a
   hibernate/restore cycle.]

   In the current design of the kernel however, during a CPU offline operation
   as part of the suspend/hibernate cycle (cpuhp_tasks_frozen is set),
   the existing copy of microcode image in the kernel is not freed up.
   And during the CPU online operations (during resume/restore), since the
   kernel finds that it already has copies of the microcode images for all the
   CPUs, it just applies them to the CPUs, avoiding any re-discovery of CPU
   type/model and the need for validating whether the microcode revisions are
   right for the CPUs or not (due to the above assumption that physical CPU
   hotplug will not be done in-between suspend/resume or hibernate/restore
   cycles).

알려진 CPU hotplug race

256-287

첫 번째 문제는 일반 CPU hotplug가 `_cpu_down()`과 `_cpu_up()`에 `tasks_frozen=0`을 항상 넘긴다는 점입니다. 동시에 suspend 같은 out-of-band event가 task를 freeze했을 수 있으므로 실제 상태와 달라지고, `cpuhp_tasks_frozen`을 검사하는 callback이 잘못된 경로를 실행할 수 있습니다.

두 번째 문제는 CPU hotplug stress와 suspend freezer의 race입니다. Freezing 시작 전에 userspace의 CPU online request가 kernel로 들어온 뒤 userspace가 freeze될 수 있습니다. CPU online이 아직 microcode update를 끝내지 않았다면 image를 얻으려고 freeze된 userspace를 `TASK_UNINTERRUPTIBLE` 상태로 기다립니다.

Freezer는 남은 task를 freeze하려 하지만 이 wait 때문에 CPU online hotplug task를 freeze하지 못합니다. 결국 task freezing이 실패하고 suspend가 중단됩니다.

Microcode request와 freezer 교착성 실패
CPU online enters kernelfreezer freezes userspacehotplug calls request_firmwarewaits on frozen userspace in TASK_UNINTERRUPTIBLEfreezer cannot freeze hotplug tasksuspend aborts

Userspace firmware 공급자가 먼저 freeze되면 hotplug task를 freeze하지 못합니다.

III. Known problems
===================

Are there any known problems when regular CPU hotplug and suspend race
with each other?

Yes, they are listed below:

1. When invoking regular CPU hotplug, the 'tasks_frozen' argument passed to
   the _cpu_down() and _cpu_up() functions is *always* 0.
   This might not reflect the true current state of the system, since the
   tasks could have been frozen by an out-of-band event such as a suspend
   operation in progress. Hence, the cpuhp_tasks_frozen variable will not
   reflect the frozen state and the CPU hotplug callbacks which evaluate
   that variable might execute the wrong code path.

2. If a regular CPU hotplug stress test happens to race with the freezer due
   to a suspend operation in progress at the same time, then we could hit the
   situation described below:

    * A regular cpu online operation continues its journey from userspace
      into the kernel, since the freezing has not yet begun.
    * Then freezer gets to work and freezes userspace.
    * If cpu online has not yet completed the microcode update stuff by now,
      it will now start waiting on the frozen userspace in the
      TASK_UNINTERRUPTIBLE state, in order to get the microcode image.
    * Now the freezer continues and tries to freeze the remaining tasks. But
      due to this wait mentioned above, the freezer won't be able to freeze
      the cpu online hotplug task and hence freezing of tasks fails.

   As a result of this task freezing failure, the suspend operation gets
   aborted.