summaryrefslogtreecommitdiff
path: root/lib/utils/fdt/fdt_helper.c
blob: d95a056b4410e659420589238fdad4f0d44b21bc (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
// SPDX-License-Identifier: BSD-2-Clause
/*
 * fdt_helper.c - Flat Device Tree manipulation helper routines
 * Implement helper routines on top of libfdt for OpenSBI usage
 *
 * Copyright (C) 2020 Bin Meng <bmeng.cn@gmail.com>
 */

#include <libfdt.h>
#include <sbi/riscv_asm.h>
#include <sbi/sbi_console.h>
#include <sbi/sbi_platform.h>
#include <sbi/sbi_scratch.h>

/**
 * We use PMP to protect OpenSBI firmware to safe-guard it from buggy S-mode
 * software, see pmp_init() in lib/sbi/sbi_hart.c. The protected memory region
 * information needs to be conveyed to S-mode software (e.g.: operating system)
 * via some well-known method.
 *
 * With device tree, this can be done by inserting a child node of the reserved
 * memory node which is used to specify one or more regions of reserved memory.
 *
 * For the reserved memory node bindings, see Linux kernel documentation at
 * Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt
 *
 * Some additional memory spaces may be protected by platform codes via PMP as
 * well, and corresponding child nodes will be inserted.
 */
int fdt_reserved_memory_fixup(void *fdt)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
	const struct sbi_platform *plat = sbi_platform_ptr(scratch);
	unsigned long prot, addr, size;
	int na = fdt_address_cells(fdt, 0);
	int ns = fdt_size_cells(fdt, 0);
	fdt32_t addr_high, addr_low;
	fdt32_t size_high, size_low;
	fdt32_t reg[4];
	fdt32_t *val;
	char name[32];
	int parent, subnode;
	int i, j;
	int err;

	if (!sbi_platform_has_pmp(plat))
		return 0;

	/* expand the device tree to accommodate new node */
	err  = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 256);
	if (err < 0)
		return err;

	/* try to locate the reserved memory node */
	parent = fdt_path_offset(fdt, "/reserved-memory");
	if (parent < 0) {
		/* if such node does not exist, create one */
		parent = fdt_add_subnode(fdt, 0, "reserved-memory");
		if (parent < 0)
			return parent;

		/*
		 * reserved-memory node has 3 required properties:
		 * - #address-cells: the same value as the root node
		 * - #size-cells: the same value as the root node
		 * - ranges: should be empty
		 */

		err = fdt_setprop_empty(fdt, parent, "ranges");
		if (err < 0)
			return err;

		err = fdt_setprop_u32(fdt, parent, "#size-cells", ns);
		if (err < 0)
			return err;

		err = fdt_setprop_u32(fdt, parent, "#address-cells", na);
		if (err < 0)
			return err;
	}

	/*
	 * We assume the given device tree does not contain any memory region
	 * child node protected by PMP. Normally PMP programming happens at
	 * M-mode firmware. The memory space used by OpenSBI is protected.
	 * Some additional memory spaces may be protected by platform codes.
	 *
	 * With above assumption, we create child nodes directly.
	 */

	for (i = 0, j = 0; i < PMP_COUNT; i++) {
		pmp_get(i, &prot, &addr, &size);
		if (!(prot & PMP_A))
			continue;
		if (!(prot & (PMP_R | PMP_W | PMP_X))) {
			addr_high = (u64)addr >> 32;
			addr_low = addr;
			size_high = (u64)size >> 32;
			size_low = size;

			if (na > 1 && addr_high)
				sbi_snprintf(name, sizeof(name),
					     "mmode_pmp%d@%x,%x", j,
					     addr_high, addr_low);
			else
				sbi_snprintf(name, sizeof(name),
					     "mmode_pmp%d@%x", j,
					     addr_low);

			subnode = fdt_add_subnode(fdt, parent, name);
			if (subnode < 0)
				return subnode;

			/*
			 * Tell operating system not to create a virtual
			 * mapping of the region as part of its standard
			 * mapping of system memory.
			 */
			err = fdt_setprop_empty(fdt, subnode, "no-map");
			if (err < 0)
				return err;

			/* encode the <reg> property value */
			val = reg;
			if (na > 1)
				*val++ = cpu_to_fdt32(addr_high);
			*val++ = cpu_to_fdt32(addr_low);
			if (ns > 1)
				*val++ = cpu_to_fdt32(size_high);
			*val++ = cpu_to_fdt32(size_low);

			err = fdt_setprop(fdt, subnode, "reg", reg,
					  (na + ns) * sizeof(fdt32_t));
			if (err < 0)
				return err;

			j++;
		}
	}

	return 0;
}