aboutsummaryrefslogtreecommitdiff
path: root/common/fdt_fixup.c
blob: 980e60d45b7b7e1c31361ce6556623a37f695c24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/*
 * Copyright (c) 2016-2020, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/*
 * Contains generic routines to fix up the device tree blob passed on to
 * payloads like BL32 and BL33 (and further down the boot chain).
 * This allows to easily add PSCI nodes, when the original DT does not have
 * it or advertises another method.
 * Also it supports to add reserved memory nodes to describe memory that
 * is used by the secure world, so that non-secure software avoids using
 * that.
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>

#include <libfdt.h>

#include <arch.h>
#include <common/debug.h>
#include <common/fdt_fixup.h>
#include <common/fdt_wrappers.h>
#include <drivers/console.h>
#include <lib/psci/psci.h>
#include <plat/common/platform.h>


static int append_psci_compatible(void *fdt, int offs, const char *str)
{
	return fdt_appendprop(fdt, offs, "compatible", str, strlen(str) + 1);
}

/*
 * Those defines are for PSCI v0.1 legacy clients, which we expect to use
 * the same execution state (AArch32/AArch64) as TF-A.
 * Kernels running in AArch32 on an AArch64 TF-A should use PSCI v0.2.
 */
#ifdef __aarch64__
#define PSCI_CPU_SUSPEND_FNID	PSCI_CPU_SUSPEND_AARCH64
#define PSCI_CPU_ON_FNID	PSCI_CPU_ON_AARCH64
#else
#define PSCI_CPU_SUSPEND_FNID	PSCI_CPU_SUSPEND_AARCH32
#define PSCI_CPU_ON_FNID	PSCI_CPU_ON_AARCH32
#endif

/*******************************************************************************
 * dt_add_psci_node() - Add a PSCI node into an existing device tree
 * @fdt:	pointer to the device tree blob in memory
 *
 * Add a device tree node describing PSCI into the root level of an existing
 * device tree blob in memory.
 * This will add v0.1, v0.2 and v1.0 compatible strings and the standard
 * function IDs for v0.1 compatibility.
 * An existing PSCI node will not be touched, the function will return success
 * in this case. This function will not touch the /cpus enable methods, use
 * dt_add_psci_cpu_enable_methods() for that.
 *
 * Return: 0 on success, -1 otherwise.
 ******************************************************************************/
int dt_add_psci_node(void *fdt)
{
	int offs;

	if (fdt_path_offset(fdt, "/psci") >= 0) {
		WARN("PSCI Device Tree node already exists!\n");
		return 0;
	}

	offs = fdt_path_offset(fdt, "/");
	if (offs < 0)
		return -1;
	offs = fdt_add_subnode(fdt, offs, "psci");
	if (offs < 0)
		return -1;
	if (append_psci_compatible(fdt, offs, "arm,psci-1.0"))
		return -1;
	if (append_psci_compatible(fdt, offs, "arm,psci-0.2"))
		return -1;
	if (append_psci_compatible(fdt, offs, "arm,psci"))
		return -1;
	if (fdt_setprop_string(fdt, offs, "method", "smc"))
		return -1;
	if (fdt_setprop_u32(fdt, offs, "cpu_suspend", PSCI_CPU_SUSPEND_FNID))
		return -1;
	if (fdt_setprop_u32(fdt, offs, "cpu_off", PSCI_CPU_OFF))
		return -1;
	if (fdt_setprop_u32(fdt, offs, "cpu_on", PSCI_CPU_ON_FNID))
		return -1;
	return 0;
}

/*
 * Find the first subnode that has a "device_type" property with the value
 * "cpu" and which's enable-method is not "psci" (yet).
 * Returns 0 if no such subnode is found, so all have already been patched
 * or none have to be patched in the first place.
 * Returns 1 if *one* such subnode has been found and successfully changed
 * to "psci".
 * Returns negative values on error.
 *
 * Call in a loop until it returns 0. Recalculate the node offset after
 * it has returned 1.
 */
static int dt_update_one_cpu_node(void *fdt, int offset)
{
	int offs;

	/* Iterate over all subnodes to find those with device_type = "cpu". */
	for (offs = fdt_first_subnode(fdt, offset); offs >= 0;
	     offs = fdt_next_subnode(fdt, offs)) {
		const char *prop;
		int len;
		int ret;

		prop = fdt_getprop(fdt, offs, "device_type", &len);
		if (prop == NULL)
			continue;
		if ((strcmp(prop, "cpu") != 0) || (len != 4))
			continue;

		/* Ignore any nodes which already use "psci". */
		prop = fdt_getprop(fdt, offs, "enable-method", &len);
		if ((prop != NULL) &&
		    (strcmp(prop, "psci") == 0) && (len == 5))
			continue;

		ret = fdt_setprop_string(fdt, offs, "enable-method", "psci");
		if (ret < 0)
			return ret;
		/*
		 * Subnode found and patched.
		 * Restart to accommodate potentially changed offsets.
		 */
		return 1;
	}

	if (offs == -FDT_ERR_NOTFOUND)
		return 0;

	return offs;
}

/*******************************************************************************
 * dt_add_psci_cpu_enable_methods() - switch CPU nodes in DT to use PSCI
 * @fdt:	pointer to the device tree blob in memory
 *
 * Iterate over all CPU device tree nodes (/cpus/cpu@x) in memory to change
 * the enable-method to PSCI. This will add the enable-method properties, if
 * required, or will change existing properties to read "psci".
 *
 * Return: 0 on success, or a negative error value otherwise.
 ******************************************************************************/

int dt_add_psci_cpu_enable_methods(void *fdt)
{
	int offs, ret;

	do {
		offs = fdt_path_offset(fdt, "/cpus");
		if (offs < 0)
			return offs;

		ret = dt_update_one_cpu_node(fdt, offs);
	} while (ret > 0);

	return ret;
}

#define HIGH_BITS(x) ((sizeof(x) > 4) ? ((x) >> 32) : (typeof(x))0)

/*******************************************************************************
 * fdt_add_reserved_memory() - reserve (secure) memory regions in DT
 * @dtb:	pointer to the device tree blob in memory
 * @node_name:	name of the subnode to be used
 * @base:	physical base address of the reserved region
 * @size:	size of the reserved region
 *
 * Add a region of memory to the /reserved-memory node in a device tree in
 * memory, creating that node if required. Each region goes into a subnode
 * of that node and has a @node_name, a @base address and a @size.
 * This will prevent any device tree consumer from using that memory. It
 * can be used to announce secure memory regions, as it adds the "no-map"
 * property to prevent mapping and speculative operations on that region.
 *
 * See reserved-memory/reserved-memory.txt in the (Linux kernel) DT binding
 * documentation for details.
 *
 * Return: 0 on success, a negative error value otherwise.
 ******************************************************************************/
int fdt_add_reserved_memory(void *dtb, const char *node_name,
			    uintptr_t base, size_t size)
{
	int offs = fdt_path_offset(dtb, "/reserved-memory");
	uint32_t addresses[3];

	if (offs < 0) {			/* create if not existing yet */
		offs = fdt_add_subnode(dtb, 0, "reserved-memory");
		if (offs < 0)
			return offs;
		fdt_setprop_u32(dtb, offs, "#address-cells", 2);
		fdt_setprop_u32(dtb, offs, "#size-cells", 1);
		fdt_setprop(dtb, offs, "ranges", NULL, 0);
	}

	addresses[0] = cpu_to_fdt32(HIGH_BITS(base));
	addresses[1] = cpu_to_fdt32(base & 0xffffffff);
	addresses[2] = cpu_to_fdt32(size & 0xffffffff);
	offs = fdt_add_subnode(dtb, offs, node_name);
	fdt_setprop(dtb, offs, "no-map", NULL, 0);
	fdt_setprop(dtb, offs, "reg", addresses, 12);

	return 0;
}

/*******************************************************************************
 * fdt_add_cpu()	Add a new CPU node to the DT
 * @dtb:		Pointer to the device tree blob in memory
 * @parent:		Offset of the parent node
 * @mpidr:		MPIDR for the current CPU
 *
 * Create and add a new cpu node to a DTB.
 *
 * Return the offset of the new node or a negative value in case of error
 ******************************************************************************/

static int fdt_add_cpu(void *dtb, int parent, u_register_t mpidr)
{
	int cpu_offs;
	int err;
	char snode_name[15];
	uint64_t reg_prop;

	reg_prop = mpidr & MPID_MASK & ~MPIDR_MT_MASK;

	snprintf(snode_name, sizeof(snode_name), "cpu@%x",
					(unsigned int)reg_prop);

	cpu_offs = fdt_add_subnode(dtb, parent, snode_name);
	if (cpu_offs < 0) {
		ERROR ("FDT: add subnode \"%s\" failed: %i\n",
							snode_name, cpu_offs);
		return cpu_offs;
	}

	err = fdt_setprop_string(dtb, cpu_offs, "compatible", "arm,armv8");
	if (err < 0) {
		ERROR ("FDT: write to \"%s\" property of node at offset %i failed\n",
			"compatible", cpu_offs);
		return err;
	}

	err = fdt_setprop_u64(dtb, cpu_offs, "reg", reg_prop);
	if (err < 0) {
		ERROR ("FDT: write to \"%s\" property of node at offset %i failed\n",
			"reg", cpu_offs);
		return err;
	}

	err = fdt_setprop_string(dtb, cpu_offs, "device_type", "cpu");
	if (err < 0) {
		ERROR ("FDT: write to \"%s\" property of node at offset %i failed\n",
			"device_type", cpu_offs);
		return err;
	}

	err = fdt_setprop_string(dtb, cpu_offs, "enable-method", "psci");
	if (err < 0) {
		ERROR ("FDT: write to \"%s\" property of node at offset %i failed\n",
			"enable-method", cpu_offs);
		return err;
	}

	return cpu_offs;
}

/******************************************************************************
 * fdt_add_cpus_node() - Add the cpus node to the DTB
 * @dtb:		pointer to the device tree blob in memory
 * @afflv0:		Maximum number of threads per core (affinity level 0).
 * @afflv1:		Maximum number of CPUs per cluster (affinity level 1).
 * @afflv2:		Maximum number of clusters (affinity level 2).
 *
 * Iterate over all the possible MPIDs given the maximum affinity levels and
 * add a cpus node to the DTB with all the valid CPUs on the system.
 * If there is already a /cpus node, exit gracefully
 *
 * A system with two CPUs would generate a node equivalent or similar to:
 *
 *	cpus {
 *		#address-cells = <2>;
 *		#size-cells = <0>;
 *
 *		cpu0: cpu@0 {
 *			compatible = "arm,armv8";
 *			reg = <0x0 0x0>;
 *			device_type = "cpu";
 *			enable-method = "psci";
 *		};
 *		cpu1: cpu@10000 {
 *			compatible = "arm,armv8";
 *			reg = <0x0 0x100>;
 *			device_type = "cpu";
 *			enable-method = "psci";
 *		};
 *	};
 *
 * Full documentation about the CPU bindings can be found at:
 * https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/cpus.txt
 *
 * Return the offset of the node or a negative value on error.
 ******************************************************************************/

int fdt_add_cpus_node(void *dtb, unsigned int afflv0,
		      unsigned int afflv1, unsigned int afflv2)
{
	int offs;
	int err;
	unsigned int i, j, k;
	u_register_t mpidr;
	int cpuid;

	if (fdt_path_offset(dtb, "/cpus") >= 0) {
		return -EEXIST;
	}

	offs = fdt_add_subnode(dtb, 0, "cpus");
	if (offs < 0) {
		ERROR ("FDT: add subnode \"cpus\" node to parent node failed");
		return offs;
	}

	err = fdt_setprop_u32(dtb, offs, "#address-cells", 2);
	if (err < 0) {
		ERROR ("FDT: write to \"%s\" property of node at offset %i failed\n",
			"#address-cells", offs);
		return err;
	}

	err = fdt_setprop_u32(dtb, offs, "#size-cells", 0);
	if (err < 0) {
		ERROR ("FDT: write to \"%s\" property of node at offset %i failed\n",
			"#size-cells", offs);
		return err;
	}

	/*
	 * Populate the node with the CPUs.
	 * As libfdt prepends subnodes within a node, reverse the index count
	 * so the CPU nodes would be better ordered.
	 */
	for (i = afflv2; i > 0U; i--) {
		for (j = afflv1; j > 0U; j--) {
			for (k = afflv0; k > 0U; k--) {
				mpidr = ((i - 1) << MPIDR_AFF2_SHIFT) |
					((j - 1) << MPIDR_AFF1_SHIFT) |
					((k - 1) << MPIDR_AFF0_SHIFT) |
					(read_mpidr_el1() & MPIDR_MT_MASK);

				cpuid = plat_core_pos_by_mpidr(mpidr);
				if (cpuid >= 0) {
					/* Valid MPID found */
					err = fdt_add_cpu(dtb, offs, mpidr);
					if (err < 0) {
						ERROR ("FDT: %s 0x%08x\n",
							"error adding CPU",
							(uint32_t)mpidr);
						return err;
					}
				}
			}
		}
	}

	return offs;
}