xref: /optee_os/core/kernel/pm.c (revision da5e7ba54949aa4baf98e4976f18eb9564bb7cf1)
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