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