xref: /optee_os/core/kernel/dt_driver.c (revision d8b14b46af9d2c7cee7f875e389e1a659bd123da)
18c0c44c9SEtienne Carriere // SPDX-License-Identifier: BSD-2-Clause
28c0c44c9SEtienne Carriere /*
38c0c44c9SEtienne Carriere  * Copyright (c) 2021, Linaro Limited
48c0c44c9SEtienne Carriere  * Copyright (c) 2021, Bootlin
58c0c44c9SEtienne Carriere  */
68c0c44c9SEtienne Carriere 
73fd340e5SEtienne Carriere #include <initcall.h>
83fd340e5SEtienne Carriere #include <kernel/dt.h>
98c0c44c9SEtienne Carriere #include <kernel/dt_driver.h>
103fd340e5SEtienne Carriere #include <libfdt.h>
113fd340e5SEtienne Carriere #include <malloc.h>
128c0c44c9SEtienne Carriere #include <sys/queue.h>
133fd340e5SEtienne Carriere #include <tee_api_types.h>
148c0c44c9SEtienne Carriere 
15704f6eddSEtienne Carriere /*
16704f6eddSEtienne Carriere  * struct dt_driver_provider - DT related info on probed device
17704f6eddSEtienne Carriere  *
18704f6eddSEtienne Carriere  * Saves information on the probed device so that device
19704f6eddSEtienne Carriere  * drivers can get resources from DT phandle and related arguments.
20704f6eddSEtienne Carriere  *
21704f6eddSEtienne Carriere  * @nodeoffset: Node offset of device referenced in the FDT
22704f6eddSEtienne Carriere  * @type: One of DT_DRIVER_* or DT_DRIVER_NOTYPE.
23704f6eddSEtienne Carriere  * @provider_cells: Cells count in the FDT used by the driver's references
24704f6eddSEtienne Carriere  * @get_of_device: Function to get driver's device ref from phandle data
25704f6eddSEtienne Carriere  * @priv_data: Driver private data passed as @get_of_device argument
26704f6eddSEtienne Carriere  * @link: Reference in DT driver providers list
27704f6eddSEtienne Carriere  */
28704f6eddSEtienne Carriere struct dt_driver_provider {
29704f6eddSEtienne Carriere 	int nodeoffset;
30704f6eddSEtienne Carriere 	enum dt_driver_type type;
31704f6eddSEtienne Carriere 	unsigned int provider_cells;
32704f6eddSEtienne Carriere 	uint32_t phandle;
33704f6eddSEtienne Carriere 	get_of_device_func get_of_device;
34704f6eddSEtienne Carriere 	void *priv_data;
35704f6eddSEtienne Carriere 	SLIST_ENTRY(dt_driver_provider) link;
36704f6eddSEtienne Carriere };
37704f6eddSEtienne Carriere 
38704f6eddSEtienne Carriere static SLIST_HEAD(, dt_driver_provider) dt_driver_provider_list =
398c0c44c9SEtienne Carriere 	SLIST_HEAD_INITIALIZER(dt_driver_provider_list);
403fd340e5SEtienne Carriere 
413fd340e5SEtienne Carriere /*
423fd340e5SEtienne Carriere  * Driver provider registering API functions
433fd340e5SEtienne Carriere  */
443fd340e5SEtienne Carriere 
453fd340e5SEtienne Carriere TEE_Result dt_driver_register_provider(const void *fdt, int nodeoffset,
463fd340e5SEtienne Carriere 				       get_of_device_func get_of_device,
473fd340e5SEtienne Carriere 				       void *priv, enum dt_driver_type type)
483fd340e5SEtienne Carriere {
493fd340e5SEtienne Carriere 	struct dt_driver_provider *prv = NULL;
503fd340e5SEtienne Carriere 	int provider_cells = 0;
513fd340e5SEtienne Carriere 	uint32_t phandle = 0;
523fd340e5SEtienne Carriere 
533fd340e5SEtienne Carriere 	provider_cells = fdt_get_dt_driver_cells(fdt, nodeoffset, type);
543fd340e5SEtienne Carriere 	if (provider_cells < 0) {
553fd340e5SEtienne Carriere 		DMSG("Failed to find provider cells: %d", provider_cells);
563fd340e5SEtienne Carriere 		return TEE_ERROR_GENERIC;
573fd340e5SEtienne Carriere 	}
583fd340e5SEtienne Carriere 
593fd340e5SEtienne Carriere 	phandle = fdt_get_phandle(fdt, nodeoffset);
603fd340e5SEtienne Carriere 	if (!phandle || phandle == (uint32_t)-1) {
613fd340e5SEtienne Carriere 		DMSG("Failed to find provide phandle");
623fd340e5SEtienne Carriere 		return TEE_ERROR_GENERIC;
633fd340e5SEtienne Carriere 	}
643fd340e5SEtienne Carriere 
653fd340e5SEtienne Carriere 	prv = calloc(1, sizeof(*prv));
663fd340e5SEtienne Carriere 	if (!prv)
673fd340e5SEtienne Carriere 		return TEE_ERROR_OUT_OF_MEMORY;
683fd340e5SEtienne Carriere 
693fd340e5SEtienne Carriere 	prv->nodeoffset = nodeoffset;
703fd340e5SEtienne Carriere 	prv->type = type;
713fd340e5SEtienne Carriere 	prv->provider_cells = provider_cells;
723fd340e5SEtienne Carriere 	prv->phandle = phandle;
733fd340e5SEtienne Carriere 	prv->get_of_device = get_of_device;
743fd340e5SEtienne Carriere 	prv->priv_data = priv;
753fd340e5SEtienne Carriere 
763fd340e5SEtienne Carriere 	SLIST_INSERT_HEAD(&dt_driver_provider_list, prv, link);
773fd340e5SEtienne Carriere 
783fd340e5SEtienne Carriere 	return TEE_SUCCESS;
793fd340e5SEtienne Carriere }
803fd340e5SEtienne Carriere 
813fd340e5SEtienne Carriere /* Release driver provider references once all dt_drivers are initialized */
823fd340e5SEtienne Carriere static TEE_Result dt_driver_release_provider(void)
833fd340e5SEtienne Carriere {
843fd340e5SEtienne Carriere 	struct dt_driver_provider *prv = NULL;
853fd340e5SEtienne Carriere 
863fd340e5SEtienne Carriere 	while (!SLIST_EMPTY(&dt_driver_provider_list)) {
873fd340e5SEtienne Carriere 		prv = SLIST_FIRST(&dt_driver_provider_list);
883fd340e5SEtienne Carriere 		SLIST_REMOVE_HEAD(&dt_driver_provider_list, link);
893fd340e5SEtienne Carriere 		free(prv);
903fd340e5SEtienne Carriere 	}
913fd340e5SEtienne Carriere 
923fd340e5SEtienne Carriere 	return TEE_SUCCESS;
933fd340e5SEtienne Carriere }
943fd340e5SEtienne Carriere 
953fd340e5SEtienne Carriere driver_init_late(dt_driver_release_provider);
963fd340e5SEtienne Carriere 
973fd340e5SEtienne Carriere /*
983fd340e5SEtienne Carriere  * Helper functions for dt_drivers querying driver provider information
993fd340e5SEtienne Carriere  */
1003fd340e5SEtienne Carriere 
1013fd340e5SEtienne Carriere int fdt_get_dt_driver_cells(const void *fdt, int nodeoffset,
1023fd340e5SEtienne Carriere 			    enum dt_driver_type type)
1033fd340e5SEtienne Carriere {
1043fd340e5SEtienne Carriere 	const char *cells_name = NULL;
1053fd340e5SEtienne Carriere 	const fdt32_t *c = NULL;
1063fd340e5SEtienne Carriere 	int len = 0;
1073fd340e5SEtienne Carriere 
1083fd340e5SEtienne Carriere 	switch (type) {
1093fd340e5SEtienne Carriere 	case DT_DRIVER_CLK:
1103fd340e5SEtienne Carriere 		cells_name = "#clock-cells";
1113fd340e5SEtienne Carriere 		break;
1123fd340e5SEtienne Carriere 	default:
1133fd340e5SEtienne Carriere 		panic();
1143fd340e5SEtienne Carriere 	}
1153fd340e5SEtienne Carriere 
1163fd340e5SEtienne Carriere 	c = fdt_getprop(fdt, nodeoffset, cells_name, &len);
1173fd340e5SEtienne Carriere 	if (!c)
1183fd340e5SEtienne Carriere 		return len;
1193fd340e5SEtienne Carriere 
1203fd340e5SEtienne Carriere 	if (len != sizeof(*c))
1213fd340e5SEtienne Carriere 		return -FDT_ERR_BADNCELLS;
1223fd340e5SEtienne Carriere 
1233fd340e5SEtienne Carriere 	return fdt32_to_cpu(*c);
1243fd340e5SEtienne Carriere }
1253fd340e5SEtienne Carriere 
1263fd340e5SEtienne Carriere unsigned int dt_driver_provider_cells(struct dt_driver_provider *prv)
1273fd340e5SEtienne Carriere {
1283fd340e5SEtienne Carriere 	return prv->provider_cells;
1293fd340e5SEtienne Carriere }
130f498c404SEtienne Carriere 
131f498c404SEtienne Carriere struct dt_driver_provider *dt_driver_get_provider_by_node(int nodeoffset)
132f498c404SEtienne Carriere {
133f498c404SEtienne Carriere 	struct dt_driver_provider *prv = NULL;
134f498c404SEtienne Carriere 
135f498c404SEtienne Carriere 	SLIST_FOREACH(prv, &dt_driver_provider_list, link)
136f498c404SEtienne Carriere 		if (prv->nodeoffset == nodeoffset)
137f498c404SEtienne Carriere 			return prv;
138f498c404SEtienne Carriere 
139f498c404SEtienne Carriere 	return NULL;
140f498c404SEtienne Carriere }
141f498c404SEtienne Carriere 
142f498c404SEtienne Carriere struct dt_driver_provider *dt_driver_get_provider_by_phandle(uint32_t phandle)
143f498c404SEtienne Carriere {
144f498c404SEtienne Carriere 	struct dt_driver_provider *prv = NULL;
145f498c404SEtienne Carriere 
146f498c404SEtienne Carriere 	SLIST_FOREACH(prv, &dt_driver_provider_list, link)
147f498c404SEtienne Carriere 		if (prv->phandle == phandle)
148f498c404SEtienne Carriere 			return prv;
149f498c404SEtienne Carriere 
150f498c404SEtienne Carriere 	return NULL;
151f498c404SEtienne Carriere }
152a22e85b2SEtienne Carriere 
153a22e85b2SEtienne Carriere static void *device_from_provider_prop(struct dt_driver_provider *prv,
154*d8b14b46SEtienne Carriere 					  const uint32_t *prop,
155*d8b14b46SEtienne Carriere 					  TEE_Result *res)
156a22e85b2SEtienne Carriere {
157a22e85b2SEtienne Carriere 	struct dt_driver_phandle_args *pargs = NULL;
158a22e85b2SEtienne Carriere 	unsigned int n = 0;
159a22e85b2SEtienne Carriere 	void *device = NULL;
160a22e85b2SEtienne Carriere 
161a22e85b2SEtienne Carriere 	pargs = calloc(1, prv->provider_cells * sizeof(uint32_t *) +
162a22e85b2SEtienne Carriere 		       sizeof(*pargs));
163*d8b14b46SEtienne Carriere 	if (!pargs) {
164*d8b14b46SEtienne Carriere 		*res = TEE_ERROR_OUT_OF_MEMORY;
165a22e85b2SEtienne Carriere 		return NULL;
166*d8b14b46SEtienne Carriere 	}
167a22e85b2SEtienne Carriere 
168a22e85b2SEtienne Carriere 	pargs->args_count = prv->provider_cells;
169a22e85b2SEtienne Carriere 	for (n = 0; n < prv->provider_cells; n++)
170a22e85b2SEtienne Carriere 		pargs->args[n] = fdt32_to_cpu(prop[n + 1]);
171a22e85b2SEtienne Carriere 
172*d8b14b46SEtienne Carriere 	device = prv->get_of_device(pargs, prv->priv_data, res);
173a22e85b2SEtienne Carriere 
174a22e85b2SEtienne Carriere 	free(pargs);
175a22e85b2SEtienne Carriere 
176a22e85b2SEtienne Carriere 	return device;
177a22e85b2SEtienne Carriere }
178a22e85b2SEtienne Carriere 
179a22e85b2SEtienne Carriere void *dt_driver_device_from_node_idx_prop(const char *prop_name,
180a22e85b2SEtienne Carriere 					  const void *fdt, int nodeoffset,
181*d8b14b46SEtienne Carriere 					  unsigned int prop_idx,
182*d8b14b46SEtienne Carriere 					  TEE_Result *res)
183a22e85b2SEtienne Carriere {
184a22e85b2SEtienne Carriere 	int len = 0;
185a22e85b2SEtienne Carriere 	int idx = 0;
186a22e85b2SEtienne Carriere 	int idx32 = 0;
187a22e85b2SEtienne Carriere 	int prv_cells = 0;
188a22e85b2SEtienne Carriere 	uint32_t phandle = 0;
189a22e85b2SEtienne Carriere 	const uint32_t *prop = NULL;
190a22e85b2SEtienne Carriere 	struct dt_driver_provider *prv = NULL;
191a22e85b2SEtienne Carriere 
192a22e85b2SEtienne Carriere 	prop = fdt_getprop(fdt, nodeoffset, prop_name, &len);
193*d8b14b46SEtienne Carriere 	if (!prop) {
194*d8b14b46SEtienne Carriere 		*res = TEE_ERROR_GENERIC;
195a22e85b2SEtienne Carriere 		return NULL;
196*d8b14b46SEtienne Carriere 	}
197a22e85b2SEtienne Carriere 
198a22e85b2SEtienne Carriere 	while (idx < len) {
199a22e85b2SEtienne Carriere 		idx32 = idx / sizeof(uint32_t);
200a22e85b2SEtienne Carriere 		phandle = fdt32_to_cpu(prop[idx32]);
201a22e85b2SEtienne Carriere 
202a22e85b2SEtienne Carriere 		prv = dt_driver_get_provider_by_phandle(phandle);
203*d8b14b46SEtienne Carriere 		if (!prv) {
204*d8b14b46SEtienne Carriere 			*res = TEE_ERROR_GENERIC;
205a22e85b2SEtienne Carriere 			return NULL;
206*d8b14b46SEtienne Carriere 		}
207a22e85b2SEtienne Carriere 
208a22e85b2SEtienne Carriere 		prv_cells = dt_driver_provider_cells(prv);
209a22e85b2SEtienne Carriere 		if (prop_idx) {
210a22e85b2SEtienne Carriere 			prop_idx--;
211a22e85b2SEtienne Carriere 			idx += sizeof(phandle) + prv_cells * sizeof(uint32_t);
212a22e85b2SEtienne Carriere 			continue;
213a22e85b2SEtienne Carriere 		}
214a22e85b2SEtienne Carriere 
215*d8b14b46SEtienne Carriere 		return device_from_provider_prop(prv, prop + idx32, res);
216a22e85b2SEtienne Carriere 	}
217a22e85b2SEtienne Carriere 
218*d8b14b46SEtienne Carriere 	*res = TEE_ERROR_GENERIC;
219a22e85b2SEtienne Carriere 	return NULL;
220a22e85b2SEtienne Carriere }
221ef20efc4SEtienne Carriere 
222ef20efc4SEtienne Carriere /* Lookup a compatible driver, possibly of a specific @type, for the FDT node */
223ef20efc4SEtienne Carriere static TEE_Result probe_device_by_compat(const void *fdt, int node,
224ef20efc4SEtienne Carriere 					 const char *compat,
225ef20efc4SEtienne Carriere 					 enum dt_driver_type type)
226ef20efc4SEtienne Carriere {
227ef20efc4SEtienne Carriere 	const struct dt_driver *drv = NULL;
228ef20efc4SEtienne Carriere 	const struct dt_device_match *dm = NULL;
229ef20efc4SEtienne Carriere 
230ef20efc4SEtienne Carriere 	for_each_dt_driver(drv) {
231ef20efc4SEtienne Carriere 		if (drv->type != type)
232ef20efc4SEtienne Carriere 			continue;
233ef20efc4SEtienne Carriere 
234ef20efc4SEtienne Carriere 		for (dm = drv->match_table; dm && dm->compatible; dm++)
235ef20efc4SEtienne Carriere 			if (strcmp(dm->compatible, compat) == 0)
236ef20efc4SEtienne Carriere 				return drv->probe(fdt, node, dm->compat_data);
237ef20efc4SEtienne Carriere 	}
238ef20efc4SEtienne Carriere 
239ef20efc4SEtienne Carriere 	return TEE_ERROR_ITEM_NOT_FOUND;
240ef20efc4SEtienne Carriere }
241ef20efc4SEtienne Carriere 
242ef20efc4SEtienne Carriere TEE_Result dt_driver_probe_device_by_node(const void *fdt, int nodeoffset,
243ef20efc4SEtienne Carriere 					  enum dt_driver_type type)
244ef20efc4SEtienne Carriere {
245ef20efc4SEtienne Carriere 	int idx = 0;
246ef20efc4SEtienne Carriere 	int len = 0;
247ef20efc4SEtienne Carriere 	int count = 0;
248ef20efc4SEtienne Carriere 	const char *compat = NULL;
249ef20efc4SEtienne Carriere 	TEE_Result res = TEE_ERROR_GENERIC;
250ef20efc4SEtienne Carriere 
251ef20efc4SEtienne Carriere 	count = fdt_stringlist_count(fdt, nodeoffset, "compatible");
252ef20efc4SEtienne Carriere 	if (count < 0)
253ef20efc4SEtienne Carriere 		return TEE_ERROR_ITEM_NOT_FOUND;
254ef20efc4SEtienne Carriere 
255ef20efc4SEtienne Carriere 	for (idx = 0; idx < count; idx++) {
256ef20efc4SEtienne Carriere 		compat = fdt_stringlist_get(fdt, nodeoffset, "compatible",
257ef20efc4SEtienne Carriere 					    idx, &len);
258ef20efc4SEtienne Carriere 		if (!compat)
259ef20efc4SEtienne Carriere 			return TEE_ERROR_GENERIC;
260ef20efc4SEtienne Carriere 
261ef20efc4SEtienne Carriere 		res = probe_device_by_compat(fdt, nodeoffset, compat, type);
262ef20efc4SEtienne Carriere 
263ef20efc4SEtienne Carriere 		if (res != TEE_ERROR_ITEM_NOT_FOUND)
264ef20efc4SEtienne Carriere 			return res;
265ef20efc4SEtienne Carriere 	}
266ef20efc4SEtienne Carriere 
267ef20efc4SEtienne Carriere 	return TEE_ERROR_ITEM_NOT_FOUND;
268ef20efc4SEtienne Carriere }
269