brintos

brintos / linux-shallow public Read only

0
0
Text · 11.6 KiB · 21f467a Raw
382 lines · c
1// SPDX-License-Identifier: GPL-2.02/*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 (like9 * shutdowns or cold/warm resets) we register an SCMI Notification notifier10 * 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 the13 * required system transition depending on the kind of request.14 *15 * Graceful requests are routed to userspace through the same API methods16 * (orderly_poweroff/reboot()) used by ACPI when handling ACPI Shutdown bus17 * events.18 *19 * Direct forceful requests are not supported since are not meant to be sent20 * by the SCMI platform to an OSPM like Linux.21 *22 * Additionally, graceful request notifications can carry an optional timeout23 * field stating the maximum amount of time allowed by the platform for24 * completion after which they are converted to forceful ones: the assumption25 * here is that even graceful requests can be upper-bound by a maximum final26 * timeout strictly enforced by the platform itself which can ultimately cut27 * the power off at will anytime; in order to avoid such extreme scenario, we28 * track progress of graceful requests through the means of a reboot notifier29 * converting timed-out graceful requests to forceful ones, so at least we30 * 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 in33 * charge of triggering system wide shutdown/reboot events, there should be34 * only one SCMI platform actively emitting SystemPower events.35 * For this reason the SCMI core takes care to enforce the creation of one36 * single unique device associated to the SCMI System Power protocol; no matter37 * how many SCMI platforms are defined on the system, only one can be designated38 * to support System Power: as a consequence this driver will never be probed39 * more than once.40 *41 * For similar reasons as soon as the first valid SystemPower is received by42 * this driver and the shutdown/reboot is started, any further notification43 * 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/suspend.h>54#include <linux/time64.h>55#include <linux/timer.h>56#include <linux/types.h>57#include <linux/workqueue.h>58 59#ifndef MODULE60#include <linux/fs.h>61#endif62 63enum scmi_syspower_state {64	SCMI_SYSPOWER_IDLE,65	SCMI_SYSPOWER_IN_PROGRESS,66	SCMI_SYSPOWER_REBOOTING67};68 69/**70 * struct scmi_syspower_conf  -  Common configuration71 *72 * @dev: A reference device73 * @state: Current SystemPower state74 * @state_mtx: @state related mutex75 * @required_transition: The requested transition as decribed in the received76 *			 SCMI SystemPower notification77 * @userspace_nb: The notifier_block registered against the SCMI SystemPower78 *		  notification to start the needed userspace interactions.79 * @reboot_nb: A notifier_block optionally used to track reboot progress80 * @forceful_work: A worker used to trigger a forceful transition once a81 *		   graceful has timed out.82 * @suspend_work: A worker used to trigger system suspend83 */84struct scmi_syspower_conf {85	struct device *dev;86	enum scmi_syspower_state state;87	/* Protect access to state */88	struct mutex state_mtx;89	enum scmi_system_events required_transition;90 91	struct notifier_block userspace_nb;92	struct notifier_block reboot_nb;93 94	struct delayed_work forceful_work;95	struct work_struct suspend_work;96};97 98#define userspace_nb_to_sconf(x)	\99	container_of(x, struct scmi_syspower_conf, userspace_nb)100 101#define reboot_nb_to_sconf(x)		\102	container_of(x, struct scmi_syspower_conf, reboot_nb)103 104#define dwork_to_sconf(x)		\105	container_of(x, struct scmi_syspower_conf, forceful_work)106 107/**108 * scmi_reboot_notifier  - A reboot notifier to catch an ongoing successful109 * system transition110 * @nb: Reference to the related notifier block111 * @reason: The reason for the ongoing reboot112 * @__unused: The cmd being executed on a restart request (unused)113 *114 * When an ongoing system transition is detected, compatible with the one115 * requested by SCMI, cancel the delayed work.116 *117 * Return: NOTIFY_OK in any case118 */119static int scmi_reboot_notifier(struct notifier_block *nb,120				unsigned long reason, void *__unused)121{122	struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb);123 124	mutex_lock(&sc->state_mtx);125	switch (reason) {126	case SYS_HALT:127	case SYS_POWER_OFF:128		if (sc->required_transition == SCMI_SYSTEM_SHUTDOWN)129			sc->state = SCMI_SYSPOWER_REBOOTING;130		break;131	case SYS_RESTART:132		if (sc->required_transition == SCMI_SYSTEM_COLDRESET ||133		    sc->required_transition == SCMI_SYSTEM_WARMRESET)134			sc->state = SCMI_SYSPOWER_REBOOTING;135		break;136	default:137		break;138	}139 140	if (sc->state == SCMI_SYSPOWER_REBOOTING) {141		dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n");142		cancel_delayed_work_sync(&sc->forceful_work);143	}144	mutex_unlock(&sc->state_mtx);145 146	return NOTIFY_OK;147}148 149/**150 * scmi_request_forceful_transition  - Request forceful SystemPower transition151 * @sc: A reference to the configuration data152 *153 * Initiates the required SystemPower transition without involving userspace:154 * just trigger the action at the kernel level after issuing an emergency155 * sync. (if possible at all)156 */157static inline void158scmi_request_forceful_transition(struct scmi_syspower_conf *sc)159{160	dev_dbg(sc->dev, "Serving forceful request:%d\n",161		sc->required_transition);162 163#ifndef MODULE164	emergency_sync();165#endif166	switch (sc->required_transition) {167	case SCMI_SYSTEM_SHUTDOWN:168		kernel_power_off();169		break;170	case SCMI_SYSTEM_COLDRESET:171	case SCMI_SYSTEM_WARMRESET:172		kernel_restart(NULL);173		break;174	default:175		break;176	}177}178 179static void scmi_forceful_work_func(struct work_struct *work)180{181	struct scmi_syspower_conf *sc;182	struct delayed_work *dwork;183 184	if (system_state > SYSTEM_RUNNING)185		return;186 187	dwork = to_delayed_work(work);188	sc = dwork_to_sconf(dwork);189 190	dev_dbg(sc->dev, "Graceful request timed out...forcing !\n");191	mutex_lock(&sc->state_mtx);192	/* avoid deadlock by unregistering reboot notifier first */193	unregister_reboot_notifier(&sc->reboot_nb);194	if (sc->state == SCMI_SYSPOWER_IN_PROGRESS)195		scmi_request_forceful_transition(sc);196	mutex_unlock(&sc->state_mtx);197}198 199/**200 * scmi_request_graceful_transition  - Request graceful SystemPower transition201 * @sc: A reference to the configuration data202 * @timeout_ms: The desired timeout to wait for the shutdown to complete before203 *		system is forcibly shutdown.204 *205 * Initiates the required SystemPower transition, requesting userspace206 * co-operation: it uses the same orderly_ methods used by ACPI Shutdown event207 * processing.208 *209 * Takes care also to register a reboot notifier and to schedule a delayed work210 * in order to detect if userspace actions are taking too long and in such a211 * case to trigger a forceful transition.212 */213static void scmi_request_graceful_transition(struct scmi_syspower_conf *sc,214					     unsigned int timeout_ms)215{216	unsigned int adj_timeout_ms = 0;217 218	if (timeout_ms) {219		int ret;220 221		sc->reboot_nb.notifier_call = &scmi_reboot_notifier;222		ret = register_reboot_notifier(&sc->reboot_nb);223		if (!ret) {224			/* Wait only up to 75% of the advertised timeout */225			adj_timeout_ms = mult_frac(timeout_ms, 3, 4);226			INIT_DELAYED_WORK(&sc->forceful_work,227					  scmi_forceful_work_func);228			schedule_delayed_work(&sc->forceful_work,229					      msecs_to_jiffies(adj_timeout_ms));230		} else {231			/* Carry on best effort even without a reboot notifier */232			dev_warn(sc->dev,233				 "Cannot register reboot notifier !\n");234		}235	}236 237	dev_dbg(sc->dev,238		"Serving graceful req:%d (timeout_ms:%u  adj_timeout_ms:%u)\n",239		sc->required_transition, timeout_ms, adj_timeout_ms);240 241	switch (sc->required_transition) {242	case SCMI_SYSTEM_SHUTDOWN:243		/*244		 * When triggered early at boot-time the 'orderly' call will245		 * partially fail due to the lack of userspace itself, but246		 * the force=true argument will start anyway a successful247		 * forced shutdown.248		 */249		orderly_poweroff(true);250		break;251	case SCMI_SYSTEM_COLDRESET:252	case SCMI_SYSTEM_WARMRESET:253		orderly_reboot();254		break;255	case SCMI_SYSTEM_SUSPEND:256		schedule_work(&sc->suspend_work);257		break;258	default:259		break;260	}261}262 263/**264 * scmi_userspace_notifier  - Notifier callback to act on SystemPower265 * Notifications266 * @nb: Reference to the related notifier block267 * @event: The SystemPower notification event id268 * @data: The SystemPower event report269 *270 * This callback is in charge of decoding the received SystemPower report271 * and act accordingly triggering a graceful or forceful system transition.272 *273 * Note that once a valid SCMI SystemPower event starts being served, any274 * other following SystemPower notification received from the same SCMI275 * instance (handle) will be ignored.276 *277 * Return: NOTIFY_OK once a valid SystemPower event has been successfully278 * processed.279 */280static int scmi_userspace_notifier(struct notifier_block *nb,281				   unsigned long event, void *data)282{283	struct scmi_system_power_state_notifier_report *er = data;284	struct scmi_syspower_conf *sc = userspace_nb_to_sconf(nb);285 286	if (er->system_state >= SCMI_SYSTEM_MAX ||287	    er->system_state == SCMI_SYSTEM_POWERUP) {288		dev_err(sc->dev, "Ignoring unsupported system_state: 0x%X\n",289			er->system_state);290		return NOTIFY_DONE;291	}292 293	if (!SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags)) {294		dev_err(sc->dev, "Ignoring forceful notification.\n");295		return NOTIFY_DONE;296	}297 298	/*299	 * Bail out if system is already shutting down or an SCMI SystemPower300	 * requested is already being served.301	 */302	if (system_state > SYSTEM_RUNNING)303		return NOTIFY_DONE;304	mutex_lock(&sc->state_mtx);305	if (sc->state != SCMI_SYSPOWER_IDLE) {306		dev_dbg(sc->dev,307			"Transition already in progress...ignore.\n");308		mutex_unlock(&sc->state_mtx);309		return NOTIFY_DONE;310	}311	sc->state = SCMI_SYSPOWER_IN_PROGRESS;312	mutex_unlock(&sc->state_mtx);313 314	sc->required_transition = er->system_state;315 316	/* Leaving a trace in logs of who triggered the shutdown/reboot. */317	dev_info(sc->dev, "Serving shutdown/reboot request: %d\n",318		 sc->required_transition);319 320	scmi_request_graceful_transition(sc, er->timeout);321 322	return NOTIFY_OK;323}324 325static void scmi_suspend_work_func(struct work_struct *work)326{327	struct scmi_syspower_conf *sc =328		container_of(work, struct scmi_syspower_conf, suspend_work);329 330	pm_suspend(PM_SUSPEND_MEM);331 332	sc->state = SCMI_SYSPOWER_IDLE;333}334 335static int scmi_syspower_probe(struct scmi_device *sdev)336{337	int ret;338	struct scmi_syspower_conf *sc;339	struct scmi_handle *handle = sdev->handle;340 341	if (!handle)342		return -ENODEV;343 344	ret = handle->devm_protocol_acquire(sdev, SCMI_PROTOCOL_SYSTEM);345	if (ret)346		return ret;347 348	sc = devm_kzalloc(&sdev->dev, sizeof(*sc), GFP_KERNEL);349	if (!sc)350		return -ENOMEM;351 352	sc->state = SCMI_SYSPOWER_IDLE;353	mutex_init(&sc->state_mtx);354	sc->required_transition = SCMI_SYSTEM_MAX;355	sc->userspace_nb.notifier_call = &scmi_userspace_notifier;356	sc->dev = &sdev->dev;357 358	INIT_WORK(&sc->suspend_work, scmi_suspend_work_func);359 360	return handle->notify_ops->devm_event_notifier_register(sdev,361							   SCMI_PROTOCOL_SYSTEM,362					 SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,363						       NULL, &sc->userspace_nb);364}365 366static const struct scmi_device_id scmi_id_table[] = {367	{ SCMI_PROTOCOL_SYSTEM, "syspower" },368	{ },369};370MODULE_DEVICE_TABLE(scmi, scmi_id_table);371 372static struct scmi_driver scmi_system_power_driver = {373	.name = "scmi-system-power",374	.probe = scmi_syspower_probe,375	.id_table = scmi_id_table,376};377module_scmi_driver(scmi_system_power_driver);378 379MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");380MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");381MODULE_LICENSE("GPL");382