Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/firmware/arm_scmi/scmi_power_control.c
26428 views
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
64
enum 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
*/
85
struct 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
*/
120
static 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
*/
158
static inline void
159
scmi_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
180
static 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
*/
214
static 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
*/
281
static 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
326
static void scmi_suspend_work_func(struct work_struct *work)
327
{
328
pm_suspend(PM_SUSPEND_MEM);
329
}
330
331
static 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
363
static 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
371
static const struct dev_pm_ops scmi_system_power_pmops = {
372
SYSTEM_SLEEP_PM_OPS(NULL, scmi_system_power_resume)
373
};
374
375
static const struct scmi_device_id scmi_id_table[] = {
376
{ SCMI_PROTOCOL_SYSTEM, "syspower" },
377
{ },
378
};
379
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
380
381
static 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
};
389
module_scmi_driver(scmi_system_power_driver);
390
391
MODULE_AUTHOR("Cristian Marussi <[email protected]>");
392
MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");
393
MODULE_LICENSE("GPL");
394
395