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
62enum 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 */
82struct 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 */
116static 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 */
154static inline void
155scmi_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
176static 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 */
210static 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 */
274static 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
318static 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
347static const struct scmi_device_id scmi_id_table[] = {
348 { SCMI_PROTOCOL_SYSTEM, "syspower" },
349 { },
350};
351MODULE_DEVICE_TABLE(scmi, scmi_id_table);
352
353static struct scmi_driver scmi_system_power_driver = {
354 .name = "scmi-system-power",
355 .probe = scmi_syspower_probe,
356 .id_table = scmi_id_table,
357};
358module_scmi_driver(scmi_system_power_driver);
359
360MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
361MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");
362MODULE_LICENSE("GPL");
363

source code of linux/drivers/firmware/arm_scmi/scmi_power_control.c