1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * SCMI Generic SystemPower Control driver. |
4 | * |
5 | * Copyright (C) 2020-2022 ARM Ltd. |
6 | */ |
7 | /* |
8 | * In order to handle platform originated SCMI SystemPower requests (like |
9 | * shutdowns or cold/warm resets) we register an SCMI Notification notifier |
10 | * block to react when such SCMI SystemPower events are emitted by platform. |
11 | * |
12 | * Once such a notification is received we act accordingly to perform the |
13 | * required system transition depending on the kind of request. |
14 | * |
15 | * Graceful requests are routed to userspace through the same API methods |
16 | * (orderly_poweroff/reboot()) used by ACPI when handling ACPI Shutdown bus |
17 | * events. |
18 | * |
19 | * Direct forceful requests are not supported since are not meant to be sent |
20 | * by the SCMI platform to an OSPM like Linux. |
21 | * |
22 | * Additionally, graceful request notifications can carry an optional timeout |
23 | * field stating the maximum amount of time allowed by the platform for |
24 | * completion after which they are converted to forceful ones: the assumption |
25 | * here is that even graceful requests can be upper-bound by a maximum final |
26 | * timeout strictly enforced by the platform itself which can ultimately cut |
27 | * the power off at will anytime; in order to avoid such extreme scenario, we |
28 | * track progress of graceful requests through the means of a reboot notifier |
29 | * converting timed-out graceful requests to forceful ones, so at least we |
30 | * try to perform a clean sync and shutdown/restart before the power is cut. |
31 | * |
32 | * Given the peculiar nature of SCMI SystemPower protocol, that is being in |
33 | * charge of triggering system wide shutdown/reboot events, there should be |
34 | * only one SCMI platform actively emitting SystemPower events. |
35 | * For this reason the SCMI core takes care to enforce the creation of one |
36 | * single unique device associated to the SCMI System Power protocol; no matter |
37 | * how many SCMI platforms are defined on the system, only one can be designated |
38 | * to support System Power: as a consequence this driver will never be probed |
39 | * more than once. |
40 | * |
41 | * For similar reasons as soon as the first valid SystemPower is received by |
42 | * this driver and the shutdown/reboot is started, any further notification |
43 | * possibly emitted by the platform will be ignored. |
44 | */ |
45 | |
46 | #include <linux/math.h> |
47 | #include <linux/module.h> |
48 | #include <linux/mutex.h> |
49 | #include <linux/printk.h> |
50 | #include <linux/reboot.h> |
51 | #include <linux/scmi_protocol.h> |
52 | #include <linux/slab.h> |
53 | #include <linux/time64.h> |
54 | #include <linux/timer.h> |
55 | #include <linux/types.h> |
56 | #include <linux/workqueue.h> |
57 | |
58 | #ifndef MODULE |
59 | #include <linux/fs.h> |
60 | #endif |
61 | |
62 | enum scmi_syspower_state { |
63 | SCMI_SYSPOWER_IDLE, |
64 | SCMI_SYSPOWER_IN_PROGRESS, |
65 | SCMI_SYSPOWER_REBOOTING |
66 | }; |
67 | |
68 | /** |
69 | * struct scmi_syspower_conf - Common configuration |
70 | * |
71 | * @dev: A reference device |
72 | * @state: Current SystemPower state |
73 | * @state_mtx: @state related mutex |
74 | * @required_transition: The requested transition as decribed in the received |
75 | * SCMI SystemPower notification |
76 | * @userspace_nb: The notifier_block registered against the SCMI SystemPower |
77 | * notification to start the needed userspace interactions. |
78 | * @reboot_nb: A notifier_block optionally used to track reboot progress |
79 | * @forceful_work: A worker used to trigger a forceful transition once a |
80 | * graceful has timed out. |
81 | */ |
82 | struct scmi_syspower_conf { |
83 | struct device *dev; |
84 | enum scmi_syspower_state state; |
85 | /* Protect access to state */ |
86 | struct mutex state_mtx; |
87 | enum scmi_system_events required_transition; |
88 | |
89 | struct notifier_block userspace_nb; |
90 | struct notifier_block reboot_nb; |
91 | |
92 | struct delayed_work forceful_work; |
93 | }; |
94 | |
95 | #define userspace_nb_to_sconf(x) \ |
96 | container_of(x, struct scmi_syspower_conf, userspace_nb) |
97 | |
98 | #define reboot_nb_to_sconf(x) \ |
99 | container_of(x, struct scmi_syspower_conf, reboot_nb) |
100 | |
101 | #define dwork_to_sconf(x) \ |
102 | container_of(x, struct scmi_syspower_conf, forceful_work) |
103 | |
104 | /** |
105 | * scmi_reboot_notifier - A reboot notifier to catch an ongoing successful |
106 | * system transition |
107 | * @nb: Reference to the related notifier block |
108 | * @reason: The reason for the ongoing reboot |
109 | * @__unused: The cmd being executed on a restart request (unused) |
110 | * |
111 | * When an ongoing system transition is detected, compatible with the one |
112 | * requested by SCMI, cancel the delayed work. |
113 | * |
114 | * Return: NOTIFY_OK in any case |
115 | */ |
116 | static int scmi_reboot_notifier(struct notifier_block *nb, |
117 | unsigned long reason, void *__unused) |
118 | { |
119 | struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb); |
120 | |
121 | mutex_lock(&sc->state_mtx); |
122 | switch (reason) { |
123 | case SYS_HALT: |
124 | case SYS_POWER_OFF: |
125 | if (sc->required_transition == SCMI_SYSTEM_SHUTDOWN) |
126 | sc->state = SCMI_SYSPOWER_REBOOTING; |
127 | break; |
128 | case SYS_RESTART: |
129 | if (sc->required_transition == SCMI_SYSTEM_COLDRESET || |
130 | sc->required_transition == SCMI_SYSTEM_WARMRESET) |
131 | sc->state = SCMI_SYSPOWER_REBOOTING; |
132 | break; |
133 | default: |
134 | break; |
135 | } |
136 | |
137 | if (sc->state == SCMI_SYSPOWER_REBOOTING) { |
138 | dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n" ); |
139 | cancel_delayed_work_sync(dwork: &sc->forceful_work); |
140 | } |
141 | mutex_unlock(lock: &sc->state_mtx); |
142 | |
143 | return NOTIFY_OK; |
144 | } |
145 | |
146 | /** |
147 | * scmi_request_forceful_transition - Request forceful SystemPower transition |
148 | * @sc: A reference to the configuration data |
149 | * |
150 | * Initiates the required SystemPower transition without involving userspace: |
151 | * just trigger the action at the kernel level after issuing an emergency |
152 | * sync. (if possible at all) |
153 | */ |
154 | static inline void |
155 | scmi_request_forceful_transition(struct scmi_syspower_conf *sc) |
156 | { |
157 | dev_dbg(sc->dev, "Serving forceful request:%d\n" , |
158 | sc->required_transition); |
159 | |
160 | #ifndef MODULE |
161 | emergency_sync(); |
162 | #endif |
163 | switch (sc->required_transition) { |
164 | case SCMI_SYSTEM_SHUTDOWN: |
165 | kernel_power_off(); |
166 | break; |
167 | case SCMI_SYSTEM_COLDRESET: |
168 | case SCMI_SYSTEM_WARMRESET: |
169 | kernel_restart(NULL); |
170 | break; |
171 | default: |
172 | break; |
173 | } |
174 | } |
175 | |
176 | static void scmi_forceful_work_func(struct work_struct *work) |
177 | { |
178 | struct scmi_syspower_conf *sc; |
179 | struct delayed_work *dwork; |
180 | |
181 | if (system_state > SYSTEM_RUNNING) |
182 | return; |
183 | |
184 | dwork = to_delayed_work(work); |
185 | sc = dwork_to_sconf(dwork); |
186 | |
187 | dev_dbg(sc->dev, "Graceful request timed out...forcing !\n" ); |
188 | mutex_lock(&sc->state_mtx); |
189 | /* avoid deadlock by unregistering reboot notifier first */ |
190 | unregister_reboot_notifier(&sc->reboot_nb); |
191 | if (sc->state == SCMI_SYSPOWER_IN_PROGRESS) |
192 | scmi_request_forceful_transition(sc); |
193 | mutex_unlock(lock: &sc->state_mtx); |
194 | } |
195 | |
196 | /** |
197 | * scmi_request_graceful_transition - Request graceful SystemPower transition |
198 | * @sc: A reference to the configuration data |
199 | * @timeout_ms: The desired timeout to wait for the shutdown to complete before |
200 | * system is forcibly shutdown. |
201 | * |
202 | * Initiates the required SystemPower transition, requesting userspace |
203 | * co-operation: it uses the same orderly_ methods used by ACPI Shutdown event |
204 | * processing. |
205 | * |
206 | * Takes care also to register a reboot notifier and to schedule a delayed work |
207 | * in order to detect if userspace actions are taking too long and in such a |
208 | * case to trigger a forceful transition. |
209 | */ |
210 | static void scmi_request_graceful_transition(struct scmi_syspower_conf *sc, |
211 | unsigned int timeout_ms) |
212 | { |
213 | unsigned int adj_timeout_ms = 0; |
214 | |
215 | if (timeout_ms) { |
216 | int ret; |
217 | |
218 | sc->reboot_nb.notifier_call = &scmi_reboot_notifier; |
219 | ret = register_reboot_notifier(&sc->reboot_nb); |
220 | if (!ret) { |
221 | /* Wait only up to 75% of the advertised timeout */ |
222 | adj_timeout_ms = mult_frac(timeout_ms, 3, 4); |
223 | INIT_DELAYED_WORK(&sc->forceful_work, |
224 | scmi_forceful_work_func); |
225 | schedule_delayed_work(dwork: &sc->forceful_work, |
226 | delay: msecs_to_jiffies(m: adj_timeout_ms)); |
227 | } else { |
228 | /* Carry on best effort even without a reboot notifier */ |
229 | dev_warn(sc->dev, |
230 | "Cannot register reboot notifier !\n" ); |
231 | } |
232 | } |
233 | |
234 | dev_dbg(sc->dev, |
235 | "Serving graceful req:%d (timeout_ms:%u adj_timeout_ms:%u)\n" , |
236 | sc->required_transition, timeout_ms, adj_timeout_ms); |
237 | |
238 | switch (sc->required_transition) { |
239 | case SCMI_SYSTEM_SHUTDOWN: |
240 | /* |
241 | * When triggered early at boot-time the 'orderly' call will |
242 | * partially fail due to the lack of userspace itself, but |
243 | * the force=true argument will start anyway a successful |
244 | * forced shutdown. |
245 | */ |
246 | orderly_poweroff(force: true); |
247 | break; |
248 | case SCMI_SYSTEM_COLDRESET: |
249 | case SCMI_SYSTEM_WARMRESET: |
250 | orderly_reboot(); |
251 | break; |
252 | default: |
253 | break; |
254 | } |
255 | } |
256 | |
257 | /** |
258 | * scmi_userspace_notifier - Notifier callback to act on SystemPower |
259 | * Notifications |
260 | * @nb: Reference to the related notifier block |
261 | * @event: The SystemPower notification event id |
262 | * @data: The SystemPower event report |
263 | * |
264 | * This callback is in charge of decoding the received SystemPower report |
265 | * and act accordingly triggering a graceful or forceful system transition. |
266 | * |
267 | * Note that once a valid SCMI SystemPower event starts being served, any |
268 | * other following SystemPower notification received from the same SCMI |
269 | * instance (handle) will be ignored. |
270 | * |
271 | * Return: NOTIFY_OK once a valid SystemPower event has been successfully |
272 | * processed. |
273 | */ |
274 | static int scmi_userspace_notifier(struct notifier_block *nb, |
275 | unsigned long event, void *data) |
276 | { |
277 | struct scmi_system_power_state_notifier_report *er = data; |
278 | struct scmi_syspower_conf *sc = userspace_nb_to_sconf(nb); |
279 | |
280 | if (er->system_state >= SCMI_SYSTEM_POWERUP) { |
281 | dev_err(sc->dev, "Ignoring unsupported system_state: 0x%X\n" , |
282 | er->system_state); |
283 | return NOTIFY_DONE; |
284 | } |
285 | |
286 | if (!SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags)) { |
287 | dev_err(sc->dev, "Ignoring forceful notification.\n" ); |
288 | return NOTIFY_DONE; |
289 | } |
290 | |
291 | /* |
292 | * Bail out if system is already shutting down or an SCMI SystemPower |
293 | * requested is already being served. |
294 | */ |
295 | if (system_state > SYSTEM_RUNNING) |
296 | return NOTIFY_DONE; |
297 | mutex_lock(&sc->state_mtx); |
298 | if (sc->state != SCMI_SYSPOWER_IDLE) { |
299 | dev_dbg(sc->dev, |
300 | "Transition already in progress...ignore.\n" ); |
301 | mutex_unlock(lock: &sc->state_mtx); |
302 | return NOTIFY_DONE; |
303 | } |
304 | sc->state = SCMI_SYSPOWER_IN_PROGRESS; |
305 | mutex_unlock(lock: &sc->state_mtx); |
306 | |
307 | sc->required_transition = er->system_state; |
308 | |
309 | /* Leaving a trace in logs of who triggered the shutdown/reboot. */ |
310 | dev_info(sc->dev, "Serving shutdown/reboot request: %d\n" , |
311 | sc->required_transition); |
312 | |
313 | scmi_request_graceful_transition(sc, timeout_ms: er->timeout); |
314 | |
315 | return NOTIFY_OK; |
316 | } |
317 | |
318 | static int scmi_syspower_probe(struct scmi_device *sdev) |
319 | { |
320 | int ret; |
321 | struct scmi_syspower_conf *sc; |
322 | struct scmi_handle *handle = sdev->handle; |
323 | |
324 | if (!handle) |
325 | return -ENODEV; |
326 | |
327 | ret = handle->devm_protocol_acquire(sdev, SCMI_PROTOCOL_SYSTEM); |
328 | if (ret) |
329 | return ret; |
330 | |
331 | sc = devm_kzalloc(dev: &sdev->dev, size: sizeof(*sc), GFP_KERNEL); |
332 | if (!sc) |
333 | return -ENOMEM; |
334 | |
335 | sc->state = SCMI_SYSPOWER_IDLE; |
336 | mutex_init(&sc->state_mtx); |
337 | sc->required_transition = SCMI_SYSTEM_MAX; |
338 | sc->userspace_nb.notifier_call = &scmi_userspace_notifier; |
339 | sc->dev = &sdev->dev; |
340 | |
341 | return handle->notify_ops->devm_event_notifier_register(sdev, |
342 | SCMI_PROTOCOL_SYSTEM, |
343 | SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER, |
344 | NULL, &sc->userspace_nb); |
345 | } |
346 | |
347 | static const struct scmi_device_id scmi_id_table[] = { |
348 | { SCMI_PROTOCOL_SYSTEM, "syspower" }, |
349 | { }, |
350 | }; |
351 | MODULE_DEVICE_TABLE(scmi, scmi_id_table); |
352 | |
353 | static struct scmi_driver scmi_system_power_driver = { |
354 | .name = "scmi-system-power" , |
355 | .probe = scmi_syspower_probe, |
356 | .id_table = scmi_id_table, |
357 | }; |
358 | module_scmi_driver(scmi_system_power_driver); |
359 | |
360 | MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>" ); |
361 | MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver" ); |
362 | MODULE_LICENSE("GPL" ); |
363 | |