summaryrefslogtreecommitdiffstats
path: root/common/fdt_fixup.c
blob: 1bad74fe623896a355d96565e0c2c3fc9a41899d (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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
/*
 * Copyright (c) 2016-2022, 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.
 * According to this binding, the address-cells and size-cells must match
 * those of the root node.
 *
 * 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[4];
	int ac, sc;
	unsigned int idx = 0;

	ac = fdt_address_cells(dtb, 0);
	sc = fdt_size_cells(dtb, 0);
	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", ac);
		fdt_setprop_u32(dtb, offs, "#size-cells", sc);
		fdt_setprop(dtb, offs, "ranges", NULL, 0);
	}

	if (ac > 1) {
		addresses[idx] = cpu_to_fdt32(HIGH_BITS(base));
		idx++;
	}
	addresses[idx] = cpu_to_fdt32(base & 0xffffffff);
	idx++;
	if (sc > 1) {
		addresses[idx] = cpu_to_fdt32(HIGH_BITS(size));
		idx++;
	}
	addresses[idx] = cpu_to_fdt32(size & 0xffffffff);
	idx++;
	offs = fdt_add_subnode(dtb, offs, node_name);
	fdt_setprop(dtb, offs, "no-map", NULL, 0);
	fdt_setprop(dtb, offs, "reg", addresses, idx * sizeof(uint32_t));

	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;
}

/*******************************************************************************
 * fdt_add_cpu_idle_states() - add PSCI CPU idle states to cpu nodes in the DT
 * @dtb:	pointer to the device tree blob in memory
 * @states:	array of idle state descriptions, ending with empty element
 *
 * Add information about CPU idle states to the devicetree. This function
 * assumes that CPU idle states are not already present in the devicetree, and
 * that all CPU states are equally applicable to all CPUs.
 *
 * See arm/idle-states.yaml and arm/psci.yaml in the (Linux kernel) DT binding
 * documentation for more details.
 *
 * Return: 0 on success, a negative error value otherwise.
 ******************************************************************************/
int fdt_add_cpu_idle_states(void *dtb, const struct psci_cpu_idle_state *state)
{
	int cpu_node, cpus_node, idle_states_node, ret;
	uint32_t count, phandle;

	ret = fdt_find_max_phandle(dtb, &phandle);
	phandle++;
	if (ret < 0) {
		return ret;
	}

	cpus_node = fdt_path_offset(dtb, "/cpus");
	if (cpus_node < 0) {
		return cpus_node;
	}

	/* Create the idle-states node and its child nodes. */
	idle_states_node = fdt_add_subnode(dtb, cpus_node, "idle-states");
	if (idle_states_node < 0) {
		return idle_states_node;
	}

	ret = fdt_setprop_string(dtb, idle_states_node, "entry-method", "psci");
	if (ret < 0) {
		return ret;
	}

	for (count = 0U; state->name != NULL; count++, phandle++, state++) {
		int idle_state_node;

		idle_state_node = fdt_add_subnode(dtb, idle_states_node,
						  state->name);
		if (idle_state_node < 0) {
			return idle_state_node;
		}

		fdt_setprop_string(dtb, idle_state_node, "compatible",
				   "arm,idle-state");
		fdt_setprop_u32(dtb, idle_state_node, "arm,psci-suspend-param",
				state->power_state);
		if (state->local_timer_stop) {
			fdt_setprop_empty(dtb, idle_state_node,
					  "local-timer-stop");
		}
		fdt_setprop_u32(dtb, idle_state_node, "entry-latency-us",
				state->entry_latency_us);
		fdt_setprop_u32(dtb, idle_state_node, "exit-latency-us",
				state->exit_latency_us);
		fdt_setprop_u32(dtb, idle_state_node, "min-residency-us",
				state->min_residency_us);
		if (state->wakeup_latency_us) {
			fdt_setprop_u32(dtb, idle_state_node,
					"wakeup-latency-us",
					state->wakeup_latency_us);
		}
		fdt_setprop_u32(dtb, idle_state_node, "phandle", phandle);
	}

	if (count == 0U) {
		return 0;
	}

	/* Link each cpu node to the idle state nodes. */
	fdt_for_each_subnode(cpu_node, dtb, cpus_node) {
		const char *device_type;
		fdt32_t *value;

		/* Only process child nodes with device_type = "cpu". */
		device_type = fdt_getprop(dtb, cpu_node, "device_type", NULL);
		if (device_type == NULL || strcmp(device_type, "cpu") != 0) {
			continue;
		}

		/* Allocate space for the list of phandles. */
		ret = fdt_setprop_placeholder(dtb, cpu_node, "cpu-idle-states",
					      count * sizeof(phandle),
					      (void **)&value);
		if (ret < 0) {
			return ret;
		}

		/* Fill in the phandles of the idle state nodes. */
		for (uint32_t i = 0U; i < count; ++i) {
			value[i] = cpu_to_fdt32(phandle - count + i);
		}
	}

	return 0;
}

/**
 * fdt_adjust_gic_redist() - Adjust GICv3 redistributor size
 * @dtb: Pointer to the DT blob in memory
 * @nr_cores: Number of CPU cores on this system.
 * @gicr_base: Base address of the first GICR frame, or ~0 if unchanged
 * @gicr_frame_size: Size of the GICR frame per core
 *
 * On a GICv3 compatible interrupt controller, the redistributor provides
 * a number of 64k pages per each supported core. So with a dynamic topology,
 * this size cannot be known upfront and thus can't be hardcoded into the DTB.
 *
 * Find the DT node describing the GICv3 interrupt controller, and adjust
 * the size of the redistributor to match the number of actual cores on
 * this system.
 * A GICv4 compatible redistributor uses four 64K pages per core, whereas GICs
 * without support for direct injection of virtual interrupts use two 64K pages.
 * The @gicr_frame_size parameter should be 262144 and 131072, respectively.
 * Also optionally allow adjusting the GICR frame base address, when this is
 * different due to ITS frames between distributor and redistributor.
 *
 * Return: 0 on success, negative error value otherwise.
 */
int fdt_adjust_gic_redist(void *dtb, unsigned int nr_cores,
			  uintptr_t gicr_base, unsigned int gicr_frame_size)
{
	int offset = fdt_node_offset_by_compatible(dtb, 0, "arm,gic-v3");
	uint64_t reg_64;
	uint32_t reg_32;
	void *val;
	int parent, ret;
	int ac, sc;

	if (offset < 0) {
		return offset;
	}

	parent = fdt_parent_offset(dtb, offset);
	if (parent < 0) {
		return parent;
	}
	ac = fdt_address_cells(dtb, parent);
	sc = fdt_size_cells(dtb, parent);
	if (ac < 0 || sc < 0) {
		return -EINVAL;
	}

	if (gicr_base != INVALID_BASE_ADDR) {
		if (ac == 1) {
			reg_32 = cpu_to_fdt32(gicr_base);
			val = &reg_32;
		} else {
			reg_64 = cpu_to_fdt64(gicr_base);
			val = &reg_64;
		}
		/*
		 * The redistributor base address is the second address in
		 * the "reg" entry, so we have to skip one address and one
		 * size cell.
		 */
		ret = fdt_setprop_inplace_namelen_partial(dtb, offset,
							  "reg", 3,
							  (ac + sc) * 4,
							  val, ac * 4);
		if (ret < 0) {
			return ret;
		}
	}

	if (sc == 1) {
		reg_32 = cpu_to_fdt32(nr_cores * gicr_frame_size);
		val = &reg_32;
	} else {
		reg_64 = cpu_to_fdt64(nr_cores * (uint64_t)gicr_frame_size);
		val = &reg_64;
	}

	/*
	 * The redistributor is described in the second "reg" entry.
	 * So we have to skip one address and one size cell, then another
	 * address cell to get to the second size cell.
	 */
	return fdt_setprop_inplace_namelen_partial(dtb, offset, "reg", 3,
						   (ac + sc + ac) * 4,
						   val, sc * 4);
}
/**
 * fdt_set_mac_address () - store MAC address in device tree
 * @dtb:	pointer to the device tree blob in memory
 * @eth_idx:	number of Ethernet interface in /aliases node
 * @mac_addr:	pointer to 6 byte MAC address to store
 *
 * Use the generic local-mac-address property in a network device DT node
 * to define the MAC address this device should be using. Many platform
 * network devices lack device-specific non-volatile storage to hold this
 * address, and leave it up to firmware to find and store a unique MAC
 * address in the DT.
 * The MAC address could be read from some board or firmware defined storage,
 * or could be derived from some other unique property like a serial number.
 *
 * Return: 0 on success, a negative libfdt error value otherwise.
 */
int fdt_set_mac_address(void *dtb, unsigned int ethernet_idx,
			const uint8_t *mac_addr)
{
	char eth_alias[12];
	const char *path;
	int node;

	if (ethernet_idx > 9U) {
		return -FDT_ERR_BADVALUE;
	}
	snprintf(eth_alias, sizeof(eth_alias), "ethernet%d", ethernet_idx);

	path = fdt_get_alias(dtb, eth_alias);
	if (path == NULL) {
		return -FDT_ERR_NOTFOUND;
	}

	node = fdt_path_offset(dtb, path);
	if (node < 0) {
		ERROR("Path \"%s\" not found in DT: %d\n", path, node);
		return node;
	}

	return fdt_setprop(dtb, node, "local-mac-address", mac_addr, 6);
}