1b7c94e43SEtienne Carriere // SPDX-License-Identifier: BSD-2-Clause
2b7c94e43SEtienne Carriere /*
3b7c94e43SEtienne Carriere * Copyright (c) 2018, Linaro Limited
4b7c94e43SEtienne Carriere */
5b7c94e43SEtienne Carriere
6b7c94e43SEtienne Carriere #include <keep.h>
7b7c94e43SEtienne Carriere #include <kernel/panic.h>
8b7c94e43SEtienne Carriere #include <kernel/pm.h>
9*da5e7ba5SGatien Chevallier #include <kernel/spinlock.h>
105920ec25SEtienne Carriere #include <malloc.h>
11b7c94e43SEtienne Carriere #include <mm/core_memprot.h>
12b7c94e43SEtienne Carriere #include <string.h>
13b7c94e43SEtienne Carriere #include <types_ext.h>
14b7c94e43SEtienne Carriere
15b7c94e43SEtienne Carriere #define PM_FLAG_SUSPENDED BIT(0)
16b7c94e43SEtienne Carriere
17*da5e7ba5SGatien Chevallier static unsigned int pm_list_lock = SPINLOCK_UNLOCK;
18b7c94e43SEtienne Carriere static struct pm_callback_handle *pm_cb_ref;
19b7c94e43SEtienne Carriere static size_t pm_cb_count;
20b7c94e43SEtienne Carriere
21502e23adSEtienne Carriere static const char no_name[] = "no-name";
22502e23adSEtienne Carriere DECLARE_KEEP_PAGER(no_name);
23502e23adSEtienne Carriere
verify_cb_args(struct pm_callback_handle * pm_hdl)24b7c94e43SEtienne Carriere static void verify_cb_args(struct pm_callback_handle *pm_hdl)
25b7c94e43SEtienne Carriere {
26b7c94e43SEtienne Carriere if (is_unpaged((void *)(vaddr_t)pm_change_state) &&
27b7c94e43SEtienne Carriere (!is_unpaged((void *)(vaddr_t)pm_hdl->callback) ||
28b7c94e43SEtienne Carriere (pm_hdl->handle && !is_unpaged(pm_hdl->handle)))) {
29b7c94e43SEtienne Carriere EMSG("PM callbacks mandates unpaged arguments: %p %p",
30b7c94e43SEtienne Carriere (void *)(vaddr_t)pm_hdl->callback, pm_hdl->handle);
31b7c94e43SEtienne Carriere panic();
32b7c94e43SEtienne Carriere }
33b7c94e43SEtienne Carriere }
34b7c94e43SEtienne Carriere
register_pm_cb(struct pm_callback_handle * pm_hdl)35b7c94e43SEtienne Carriere void register_pm_cb(struct pm_callback_handle *pm_hdl)
36b7c94e43SEtienne Carriere {
37502e23adSEtienne Carriere struct pm_callback_handle *ref = NULL;
38502e23adSEtienne Carriere const char *name = pm_hdl->name;
39b7c94e43SEtienne Carriere size_t count = pm_cb_count;
40*da5e7ba5SGatien Chevallier uint32_t exceptions = 0;
41b7c94e43SEtienne Carriere
42b7c94e43SEtienne Carriere verify_cb_args(pm_hdl);
43b7c94e43SEtienne Carriere
44502e23adSEtienne Carriere if (!name)
45502e23adSEtienne Carriere name = no_name;
46502e23adSEtienne Carriere
47502e23adSEtienne Carriere if (!is_unpaged((void *)name)) {
48502e23adSEtienne Carriere name = strdup(name);
49502e23adSEtienne Carriere if (!name)
50502e23adSEtienne Carriere panic();
51502e23adSEtienne Carriere }
52502e23adSEtienne Carriere
53*da5e7ba5SGatien Chevallier exceptions = cpu_spin_lock_xsave(&pm_list_lock);
54*da5e7ba5SGatien Chevallier
55b7c94e43SEtienne Carriere ref = realloc(pm_cb_ref, sizeof(*ref) * (count + 1));
56b7c94e43SEtienne Carriere if (!ref)
57b7c94e43SEtienne Carriere panic();
58b7c94e43SEtienne Carriere
59b7c94e43SEtienne Carriere ref[count] = *pm_hdl;
60b7c94e43SEtienne Carriere ref[count].flags = 0;
61502e23adSEtienne Carriere ref[count].name = name;
62b7c94e43SEtienne Carriere
63b7c94e43SEtienne Carriere pm_cb_count = count + 1;
64b7c94e43SEtienne Carriere pm_cb_ref = ref;
65*da5e7ba5SGatien Chevallier
66*da5e7ba5SGatien Chevallier cpu_spin_unlock_xrestore(&pm_list_lock, exceptions);
67*da5e7ba5SGatien Chevallier }
68*da5e7ba5SGatien Chevallier
unregister_pm_cb(struct pm_callback_handle * pm_hdl)69*da5e7ba5SGatien Chevallier void unregister_pm_cb(struct pm_callback_handle *pm_hdl)
70*da5e7ba5SGatien Chevallier {
71*da5e7ba5SGatien Chevallier uint32_t exceptions = 0;
72*da5e7ba5SGatien Chevallier size_t n = 0;
73*da5e7ba5SGatien Chevallier
74*da5e7ba5SGatien Chevallier exceptions = cpu_spin_lock_xsave(&pm_list_lock);
75*da5e7ba5SGatien Chevallier
76*da5e7ba5SGatien Chevallier for (n = 0; n < pm_cb_count; n++)
77*da5e7ba5SGatien Chevallier if (pm_cb_ref[n].callback == pm_hdl->callback &&
78*da5e7ba5SGatien Chevallier pm_cb_ref[n].handle == pm_hdl->handle &&
79*da5e7ba5SGatien Chevallier pm_cb_ref[n].order == pm_hdl->order)
80*da5e7ba5SGatien Chevallier break;
81*da5e7ba5SGatien Chevallier
82*da5e7ba5SGatien Chevallier if (n < pm_cb_count) {
83*da5e7ba5SGatien Chevallier pm_cb_count--;
84*da5e7ba5SGatien Chevallier memmove(pm_cb_ref + n, pm_cb_ref + n + 1,
85*da5e7ba5SGatien Chevallier (pm_cb_count - n) * sizeof(*pm_cb_ref));
86*da5e7ba5SGatien Chevallier }
87*da5e7ba5SGatien Chevallier
88*da5e7ba5SGatien Chevallier cpu_spin_unlock_xrestore(&pm_list_lock, exceptions);
89b7c94e43SEtienne Carriere }
90b7c94e43SEtienne Carriere
do_pm_callback(enum pm_op op,uint32_t pm_hint,struct pm_callback_handle * hdl)915920ec25SEtienne Carriere static TEE_Result do_pm_callback(enum pm_op op, uint32_t pm_hint,
925920ec25SEtienne Carriere struct pm_callback_handle *hdl)
93b7c94e43SEtienne Carriere {
945920ec25SEtienne Carriere TEE_Result res = TEE_ERROR_GENERIC;
955920ec25SEtienne Carriere bool suspending = op == PM_OP_SUSPEND;
96b7c94e43SEtienne Carriere
973430d816SLionel Debieve if (suspending == (bool)(hdl->flags & PM_FLAG_SUSPENDED))
985920ec25SEtienne Carriere return TEE_SUCCESS;
99b7c94e43SEtienne Carriere
1005920ec25SEtienne Carriere DMSG("%s %s", suspending ? "Suspend" : "Resume", hdl->name);
101502e23adSEtienne Carriere
102b7c94e43SEtienne Carriere res = hdl->callback(op, pm_hint, hdl);
1035920ec25SEtienne Carriere if (res) {
1045920ec25SEtienne Carriere EMSG("%s %s (%p) failed: %#"PRIx32, suspending ? "Suspend" :
1055920ec25SEtienne Carriere "Resume", hdl->name, (void *)(vaddr_t)hdl->callback, res);
106b7c94e43SEtienne Carriere return res;
1075920ec25SEtienne Carriere }
108b7c94e43SEtienne Carriere
1095920ec25SEtienne Carriere if (suspending)
110b7c94e43SEtienne Carriere hdl->flags |= PM_FLAG_SUSPENDED;
111b7c94e43SEtienne Carriere else
112b7c94e43SEtienne Carriere hdl->flags &= ~PM_FLAG_SUSPENDED;
1135920ec25SEtienne Carriere
1145920ec25SEtienne Carriere return TEE_SUCCESS;
1155920ec25SEtienne Carriere }
1165920ec25SEtienne Carriere
call_callbacks(enum pm_op op,uint32_t pm_hint,enum pm_callback_order order)1175920ec25SEtienne Carriere static TEE_Result call_callbacks(enum pm_op op, uint32_t pm_hint,
1185920ec25SEtienne Carriere enum pm_callback_order order)
1195920ec25SEtienne Carriere {
1205920ec25SEtienne Carriere struct pm_callback_handle *hdl = NULL;
1215920ec25SEtienne Carriere TEE_Result res = TEE_ERROR_GENERIC;
1225920ec25SEtienne Carriere size_t n = 0;
1235920ec25SEtienne Carriere
1245920ec25SEtienne Carriere /*
1255920ec25SEtienne Carriere * Suspend first the last registered instances.
1265920ec25SEtienne Carriere * Resume first the first registered instances.
1275920ec25SEtienne Carriere */
1285920ec25SEtienne Carriere if (op == PM_OP_SUSPEND)
1295920ec25SEtienne Carriere hdl = pm_cb_ref + pm_cb_count - 1;
1305920ec25SEtienne Carriere else
1315920ec25SEtienne Carriere hdl = pm_cb_ref;
1325920ec25SEtienne Carriere
1335920ec25SEtienne Carriere for (n = 0; n < pm_cb_count; n++) {
1345920ec25SEtienne Carriere if (hdl->order == order) {
1355920ec25SEtienne Carriere res = do_pm_callback(op, pm_hint, hdl);
1365920ec25SEtienne Carriere if (res)
1375920ec25SEtienne Carriere return res;
1385920ec25SEtienne Carriere }
1395920ec25SEtienne Carriere
1405920ec25SEtienne Carriere if (op == PM_OP_SUSPEND)
1415920ec25SEtienne Carriere hdl--;
1425920ec25SEtienne Carriere else
1435920ec25SEtienne Carriere hdl++;
144b7c94e43SEtienne Carriere }
145b7c94e43SEtienne Carriere
146b7c94e43SEtienne Carriere return TEE_SUCCESS;
147b7c94e43SEtienne Carriere }
148b7c94e43SEtienne Carriere
pm_change_state(enum pm_op op,uint32_t pm_hint)149b7c94e43SEtienne Carriere TEE_Result pm_change_state(enum pm_op op, uint32_t pm_hint)
150b7c94e43SEtienne Carriere {
15136ebac6dSEtienne Carriere enum pm_callback_order cnt = PM_CB_ORDER_DRIVER;
15236ebac6dSEtienne Carriere TEE_Result res = TEE_ERROR_GENERIC;
153b7c94e43SEtienne Carriere
154b7c94e43SEtienne Carriere switch (op) {
155b7c94e43SEtienne Carriere case PM_OP_SUSPEND:
156b7c94e43SEtienne Carriere for (cnt = PM_CB_ORDER_DRIVER; cnt < PM_CB_ORDER_MAX; cnt++) {
157b7c94e43SEtienne Carriere res = call_callbacks(op, pm_hint, cnt);
158b7c94e43SEtienne Carriere if (res)
159b7c94e43SEtienne Carriere return res;
160b7c94e43SEtienne Carriere }
161b7c94e43SEtienne Carriere break;
162b7c94e43SEtienne Carriere case PM_OP_RESUME:
163b7c94e43SEtienne Carriere for (cnt = PM_CB_ORDER_MAX; cnt > PM_CB_ORDER_DRIVER; cnt--) {
164b7c94e43SEtienne Carriere res = call_callbacks(op, pm_hint, cnt - 1);
165b7c94e43SEtienne Carriere if (res)
166b7c94e43SEtienne Carriere return res;
167b7c94e43SEtienne Carriere }
168b7c94e43SEtienne Carriere break;
169b7c94e43SEtienne Carriere default:
170b7c94e43SEtienne Carriere panic();
171b7c94e43SEtienne Carriere }
172b7c94e43SEtienne Carriere
173b7c94e43SEtienne Carriere return TEE_SUCCESS;
174b7c94e43SEtienne Carriere }
175