File Index
Files and directories
Sign in
FILE

scmi_power_control.c

File preview with syntax highlighting.

linux-6.19.14/drivers/firmware/arm_scmi/scmi_power_control.c C
  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/pm.h>
 50#include <linux/printk.h>
 51#include <linux/reboot.h>
 52#include <linux/scmi_protocol.h>
 53#include <linux/slab.h>
 54#include <linux/suspend.h>
 55#include <linux/time64.h>
 56#include <linux/timer.h>
 57#include <linux/types.h>
 58#include <linux/workqueue.h>
 59
 60#ifndef MODULE
 61#include <linux/fs.h>
 62#endif
 63
 64enum scmi_syspower_state {
 65	SCMI_SYSPOWER_IDLE,
 66	SCMI_SYSPOWER_IN_PROGRESS,
 67	SCMI_SYSPOWER_REBOOTING
 68};
 69
 70/**
 71 * struct scmi_syspower_conf  -  Common configuration
 72 *
 73 * @dev: A reference device
 74 * @state: Current SystemPower state
 75 * @state_mtx: @state related mutex
 76 * @required_transition: The requested transition as decribed in the received
 77 *			 SCMI SystemPower notification
 78 * @userspace_nb: The notifier_block registered against the SCMI SystemPower
 79 *		  notification to start the needed userspace interactions.
 80 * @reboot_nb: A notifier_block optionally used to track reboot progress
 81 * @forceful_work: A worker used to trigger a forceful transition once a
 82 *		   graceful has timed out.
 83 * @suspend_work: A worker used to trigger system suspend
 84 */
 85struct scmi_syspower_conf {
 86	struct device *dev;
 87	enum scmi_syspower_state state;
 88	/* Protect access to state */
 89	struct mutex state_mtx;
 90	enum scmi_system_events required_transition;
 91
 92	struct notifier_block userspace_nb;
 93	struct notifier_block reboot_nb;
 94
 95	struct delayed_work forceful_work;
 96	struct work_struct suspend_work;
 97};
 98
 99#define userspace_nb_to_sconf(x)	\
100	container_of(x, struct scmi_syspower_conf, userspace_nb)
101
102#define reboot_nb_to_sconf(x)		\
103	container_of(x, struct scmi_syspower_conf, reboot_nb)
104
105#define dwork_to_sconf(x)		\
106	container_of(x, struct scmi_syspower_conf, forceful_work)
107
108/**
109 * scmi_reboot_notifier  - A reboot notifier to catch an ongoing successful
110 * system transition
111 * @nb: Reference to the related notifier block
112 * @reason: The reason for the ongoing reboot
113 * @__unused: The cmd being executed on a restart request (unused)
114 *
115 * When an ongoing system transition is detected, compatible with the one
116 * requested by SCMI, cancel the delayed work.
117 *
118 * Return: NOTIFY_OK in any case
119 */
120static int scmi_reboot_notifier(struct notifier_block *nb,
121				unsigned long reason, void *__unused)
122{
123	struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb);
124
125	mutex_lock(&sc->state_mtx);
126	switch (reason) {
127	case SYS_HALT:
128	case SYS_POWER_OFF:
129		if (sc->required_transition == SCMI_SYSTEM_SHUTDOWN)
130			sc->state = SCMI_SYSPOWER_REBOOTING;
131		break;
132	case SYS_RESTART:
133		if (sc->required_transition == SCMI_SYSTEM_COLDRESET ||
134		    sc->required_transition == SCMI_SYSTEM_WARMRESET)
135			sc->state = SCMI_SYSPOWER_REBOOTING;
136		break;
137	default:
138		break;
139	}
140
141	if (sc->state == SCMI_SYSPOWER_REBOOTING) {
142		dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n");
143		cancel_delayed_work_sync(&sc->forceful_work);
144	}
145	mutex_unlock(&sc->state_mtx);
146
147	return NOTIFY_OK;
148}
149
150/**
151 * scmi_request_forceful_transition  - Request forceful SystemPower transition
152 * @sc: A reference to the configuration data
153 *
154 * Initiates the required SystemPower transition without involving userspace:
155 * just trigger the action at the kernel level after issuing an emergency
156 * sync. (if possible at all)
157 */
158static inline void
159scmi_request_forceful_transition(struct scmi_syspower_conf *sc)
160{
161	dev_dbg(sc->dev, "Serving forceful request:%d\n",
162		sc->required_transition);
163
164#ifndef MODULE
165	emergency_sync();
166#endif
167	switch (sc->required_transition) {
168	case SCMI_SYSTEM_SHUTDOWN:
169		kernel_power_off();
170		break;
171	case SCMI_SYSTEM_COLDRESET:
172	case SCMI_SYSTEM_WARMRESET:
173		kernel_restart(NULL);
174		break;
175	default:
176		break;
177	}
178}
179
180static void scmi_forceful_work_func(struct work_struct *work)
181{
182	struct scmi_syspower_conf *sc;
183	struct delayed_work *dwork;
184
185	if (system_state > SYSTEM_RUNNING)
186		return;
187
188	dwork = to_delayed_work(work);
189	sc = dwork_to_sconf(dwork);
190
191	dev_dbg(sc->dev, "Graceful request timed out...forcing !\n");
192	mutex_lock(&sc->state_mtx);
193	/* avoid deadlock by unregistering reboot notifier first */
194	unregister_reboot_notifier(&sc->reboot_nb);
195	if (sc->state == SCMI_SYSPOWER_IN_PROGRESS)
196		scmi_request_forceful_transition(sc);
197	mutex_unlock(&sc->state_mtx);
198}
199
200/**
201 * scmi_request_graceful_transition  - Request graceful SystemPower transition
202 * @sc: A reference to the configuration data
203 * @timeout_ms: The desired timeout to wait for the shutdown to complete before
204 *		system is forcibly shutdown.
205 *
206 * Initiates the required SystemPower transition, requesting userspace
207 * co-operation: it uses the same orderly_ methods used by ACPI Shutdown event
208 * processing.
209 *
210 * Takes care also to register a reboot notifier and to schedule a delayed work
211 * in order to detect if userspace actions are taking too long and in such a
212 * case to trigger a forceful transition.
213 */
214static void scmi_request_graceful_transition(struct scmi_syspower_conf *sc,
215					     unsigned int timeout_ms)
216{
217	unsigned int adj_timeout_ms = 0;
218
219	if (timeout_ms) {
220		int ret;
221
222		sc->reboot_nb.notifier_call = &scmi_reboot_notifier;
223		ret = register_reboot_notifier(&sc->reboot_nb);
224		if (!ret) {
225			/* Wait only up to 75% of the advertised timeout */
226			adj_timeout_ms = mult_frac(timeout_ms, 3, 4);
227			INIT_DELAYED_WORK(&sc->forceful_work,
228					  scmi_forceful_work_func);
229			schedule_delayed_work(&sc->forceful_work,
230					      msecs_to_jiffies(adj_timeout_ms));
231		} else {
232			/* Carry on best effort even without a reboot notifier */
233			dev_warn(sc->dev,
234				 "Cannot register reboot notifier !\n");
235		}
236	}
237
238	dev_dbg(sc->dev,
239		"Serving graceful req:%d (timeout_ms:%u  adj_timeout_ms:%u)\n",
240		sc->required_transition, timeout_ms, adj_timeout_ms);
241
242	switch (sc->required_transition) {
243	case SCMI_SYSTEM_SHUTDOWN:
244		/*
245		 * When triggered early at boot-time the 'orderly' call will
246		 * partially fail due to the lack of userspace itself, but
247		 * the force=true argument will start anyway a successful
248		 * forced shutdown.
249		 */
250		orderly_poweroff(true);
251		break;
252	case SCMI_SYSTEM_COLDRESET:
253	case SCMI_SYSTEM_WARMRESET:
254		orderly_reboot();
255		break;
256	case SCMI_SYSTEM_SUSPEND:
257		schedule_work(&sc->suspend_work);
258		break;
259	default:
260		break;
261	}
262}
263
264/**
265 * scmi_userspace_notifier  - Notifier callback to act on SystemPower
266 * Notifications
267 * @nb: Reference to the related notifier block
268 * @event: The SystemPower notification event id
269 * @data: The SystemPower event report
270 *
271 * This callback is in charge of decoding the received SystemPower report
272 * and act accordingly triggering a graceful or forceful system transition.
273 *
274 * Note that once a valid SCMI SystemPower event starts being served, any
275 * other following SystemPower notification received from the same SCMI
276 * instance (handle) will be ignored.
277 *
278 * Return: NOTIFY_OK once a valid SystemPower event has been successfully
279 * processed.
280 */
281static int scmi_userspace_notifier(struct notifier_block *nb,
282				   unsigned long event, void *data)
283{
284	struct scmi_system_power_state_notifier_report *er = data;
285	struct scmi_syspower_conf *sc = userspace_nb_to_sconf(nb);
286
287	if (er->system_state >= SCMI_SYSTEM_MAX ||
288	    er->system_state == SCMI_SYSTEM_POWERUP) {
289		dev_err(sc->dev, "Ignoring unsupported system_state: 0x%X\n",
290			er->system_state);
291		return NOTIFY_DONE;
292	}
293
294	if (!SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags)) {
295		dev_err(sc->dev, "Ignoring forceful notification.\n");
296		return NOTIFY_DONE;
297	}
298
299	/*
300	 * Bail out if system is already shutting down or an SCMI SystemPower
301	 * requested is already being served.
302	 */
303	if (system_state > SYSTEM_RUNNING)
304		return NOTIFY_DONE;
305	mutex_lock(&sc->state_mtx);
306	if (sc->state != SCMI_SYSPOWER_IDLE) {
307		dev_dbg(sc->dev,
308			"Transition already in progress...ignore.\n");
309		mutex_unlock(&sc->state_mtx);
310		return NOTIFY_DONE;
311	}
312	sc->state = SCMI_SYSPOWER_IN_PROGRESS;
313	mutex_unlock(&sc->state_mtx);
314
315	sc->required_transition = er->system_state;
316
317	/* Leaving a trace in logs of who triggered the shutdown/reboot. */
318	dev_info(sc->dev, "Serving shutdown/reboot request: %d\n",
319		 sc->required_transition);
320
321	scmi_request_graceful_transition(sc, er->timeout);
322
323	return NOTIFY_OK;
324}
325
326static void scmi_suspend_work_func(struct work_struct *work)
327{
328	pm_suspend(PM_SUSPEND_MEM);
329}
330
331static int scmi_syspower_probe(struct scmi_device *sdev)
332{
333	int ret;
334	struct scmi_syspower_conf *sc;
335	struct scmi_handle *handle = sdev->handle;
336
337	if (!handle)
338		return -ENODEV;
339
340	ret = handle->devm_protocol_acquire(sdev, SCMI_PROTOCOL_SYSTEM);
341	if (ret)
342		return ret;
343
344	sc = devm_kzalloc(&sdev->dev, sizeof(*sc), GFP_KERNEL);
345	if (!sc)
346		return -ENOMEM;
347
348	sc->state = SCMI_SYSPOWER_IDLE;
349	mutex_init(&sc->state_mtx);
350	sc->required_transition = SCMI_SYSTEM_MAX;
351	sc->userspace_nb.notifier_call = &scmi_userspace_notifier;
352	sc->dev = &sdev->dev;
353	dev_set_drvdata(&sdev->dev, sc);
354
355	INIT_WORK(&sc->suspend_work, scmi_suspend_work_func);
356
357	return handle->notify_ops->devm_event_notifier_register(sdev,
358							   SCMI_PROTOCOL_SYSTEM,
359					 SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
360						       NULL, &sc->userspace_nb);
361}
362
363static int scmi_system_power_resume(struct device *dev)
364{
365	struct scmi_syspower_conf *sc = dev_get_drvdata(dev);
366
367	sc->state = SCMI_SYSPOWER_IDLE;
368	return 0;
369}
370
371static const struct dev_pm_ops scmi_system_power_pmops = {
372	SYSTEM_SLEEP_PM_OPS(NULL, scmi_system_power_resume)
373};
374
375static const struct scmi_device_id scmi_id_table[] = {
376	{ SCMI_PROTOCOL_SYSTEM, "syspower" },
377	{ },
378};
379MODULE_DEVICE_TABLE(scmi, scmi_id_table);
380
381static struct scmi_driver scmi_system_power_driver = {
382	.driver	= {
383		.pm = pm_sleep_ptr(&scmi_system_power_pmops),
384	},
385	.name = "scmi-system-power",
386	.probe = scmi_syspower_probe,
387	.id_table = scmi_id_table,
388};
389module_scmi_driver(scmi_system_power_driver);
390
391MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
392MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");
393MODULE_LICENSE("GPL");
~/ Read-only file preview.