1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3 * fdt_fixup.c - Flat Device Tree parsing helper routines
4 * Implement helper routines to parse FDT nodes on top of
5 * libfdt for OpenSBI usage
6 *
7 * Copyright (C) 2020 Bin Meng <bmeng.cn@gmail.com>
8 */
9
10 #include <libfdt.h>
11 #include <sbi/sbi_console.h>
12 #include <sbi/sbi_domain.h>
13 #include <sbi/sbi_math.h>
14 #include <sbi/sbi_hart.h>
15 #include <sbi/sbi_scratch.h>
16 #include <sbi/sbi_string.h>
17 #include <sbi_utils/fdt/fdt_fixup.h>
18 #include <sbi_utils/fdt/fdt_helper.h>
19
fdt_cpu_fixup(void * fdt)20 void fdt_cpu_fixup(void *fdt)
21 {
22 struct sbi_domain *dom = sbi_domain_thishart_ptr();
23 int err, cpu_offset, cpus_offset, len;
24 const char *mmu_type;
25 u32 hartid;
26
27 err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 32);
28 if (err < 0)
29 return;
30
31 cpus_offset = fdt_path_offset(fdt, "/cpus");
32 if (cpus_offset < 0)
33 return;
34
35 fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
36 err = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
37 if (err)
38 continue;
39
40 /*
41 * Disable a HART DT node if one of the following is true:
42 * 1. The HART is not assigned to the current domain
43 * 2. MMU is not available for the HART
44 */
45
46 mmu_type = fdt_getprop(fdt, cpu_offset, "mmu-type", &len);
47 if (!sbi_domain_is_assigned_hart(dom, hartid) ||
48 !mmu_type || !len)
49 fdt_setprop_string(fdt, cpu_offset, "status",
50 "disabled");
51 }
52 }
53
fdt_plic_fixup(void * fdt,const char * compat)54 void fdt_plic_fixup(void *fdt, const char *compat)
55 {
56 u32 *cells;
57 int i, cells_count;
58 int plic_off;
59
60 plic_off = fdt_node_offset_by_compatible(fdt, 0, compat);
61 if (plic_off < 0)
62 return;
63
64 cells = (u32 *)fdt_getprop(fdt, plic_off,
65 "interrupts-extended", &cells_count);
66 if (!cells)
67 return;
68
69 cells_count = cells_count / sizeof(u32);
70 if (!cells_count)
71 return;
72
73 for (i = 0; i < (cells_count / 2); i++) {
74 if (fdt32_to_cpu(cells[2 * i + 1]) == IRQ_M_EXT)
75 cells[2 * i + 1] = cpu_to_fdt32(0xffffffff);
76 }
77 }
78
fdt_resv_memory_update_node(void * fdt,unsigned long addr,unsigned long size,int index,int parent,bool no_map)79 static int fdt_resv_memory_update_node(void *fdt, unsigned long addr,
80 unsigned long size, int index,
81 int parent, bool no_map)
82 {
83 int na = fdt_address_cells(fdt, 0);
84 int ns = fdt_size_cells(fdt, 0);
85 fdt32_t addr_high, addr_low;
86 fdt32_t size_high, size_low;
87 int subnode, err;
88 fdt32_t reg[4];
89 fdt32_t *val;
90 char name[32];
91
92 addr_high = (u64)addr >> 32;
93 addr_low = addr;
94 size_high = (u64)size >> 32;
95 size_low = size;
96
97 if (na > 1 && addr_high)
98 sbi_snprintf(name, sizeof(name),
99 "mmode_resv%d@%x,%x", index,
100 addr_high, addr_low);
101 else
102 sbi_snprintf(name, sizeof(name),
103 "mmode_resv%d@%x", index,
104 addr_low);
105
106 subnode = fdt_add_subnode(fdt, parent, name);
107 if (subnode < 0)
108 return subnode;
109
110 if (no_map) {
111 /*
112 * Tell operating system not to create a virtual
113 * mapping of the region as part of its standard
114 * mapping of system memory.
115 */
116 err = fdt_setprop_empty(fdt, subnode, "no-map");
117 if (err < 0)
118 return err;
119 }
120
121 /* encode the <reg> property value */
122 val = reg;
123 if (na > 1)
124 *val++ = cpu_to_fdt32(addr_high);
125 *val++ = cpu_to_fdt32(addr_low);
126 if (ns > 1)
127 *val++ = cpu_to_fdt32(size_high);
128 *val++ = cpu_to_fdt32(size_low);
129
130 err = fdt_setprop(fdt, subnode, "reg", reg,
131 (na + ns) * sizeof(fdt32_t));
132 if (err < 0)
133 return err;
134
135 return 0;
136 }
137
138 /**
139 * We use PMP to protect OpenSBI firmware to safe-guard it from buggy S-mode
140 * software, see pmp_init() in lib/sbi/sbi_hart.c. The protected memory region
141 * information needs to be conveyed to S-mode software (e.g.: operating system)
142 * via some well-known method.
143 *
144 * With device tree, this can be done by inserting a child node of the reserved
145 * memory node which is used to specify one or more regions of reserved memory.
146 *
147 * For the reserved memory node bindings, see Linux kernel documentation at
148 * Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt
149 *
150 * Some additional memory spaces may be protected by platform codes via PMP as
151 * well, and corresponding child nodes will be inserted.
152 */
fdt_reserved_memory_fixup(void * fdt)153 int fdt_reserved_memory_fixup(void *fdt)
154 {
155 struct sbi_domain_memregion *reg;
156 struct sbi_domain *dom = sbi_domain_thishart_ptr();
157 struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
158 unsigned long addr, size;
159 int err, parent, i;
160 int na = fdt_address_cells(fdt, 0);
161 int ns = fdt_size_cells(fdt, 0);
162
163 /*
164 * Expand the device tree to accommodate new node
165 * by the following estimated size:
166 *
167 * Each PMP memory region entry occupies 64 bytes.
168 * With 16 PMP memory regions we need 64 * 16 = 1024 bytes.
169 */
170 err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 1024);
171 if (err < 0)
172 return err;
173
174 /* try to locate the reserved memory node */
175 parent = fdt_path_offset(fdt, "/reserved-memory");
176 if (parent < 0) {
177 /* if such node does not exist, create one */
178 parent = fdt_add_subnode(fdt, 0, "reserved-memory");
179 if (parent < 0)
180 return parent;
181
182 /*
183 * reserved-memory node has 3 required properties:
184 * - #address-cells: the same value as the root node
185 * - #size-cells: the same value as the root node
186 * - ranges: should be empty
187 */
188
189 err = fdt_setprop_empty(fdt, parent, "ranges");
190 if (err < 0)
191 return err;
192
193 err = fdt_setprop_u32(fdt, parent, "#size-cells", ns);
194 if (err < 0)
195 return err;
196
197 err = fdt_setprop_u32(fdt, parent, "#address-cells", na);
198 if (err < 0)
199 return err;
200 }
201
202 /*
203 * We assume the given device tree does not contain any memory region
204 * child node protected by PMP. Normally PMP programming happens at
205 * M-mode firmware. The memory space used by OpenSBI is protected.
206 * Some additional memory spaces may be protected by domain memory
207 * regions.
208 *
209 * With above assumption, we create child nodes directly.
210 */
211
212 i = 0;
213 sbi_domain_for_each_memregion(dom, reg) {
214 /* Ignore MMIO or READABLE or WRITABLE or EXECUTABLE regions */
215 if (reg->flags & SBI_DOMAIN_MEMREGION_MMIO)
216 continue;
217 if (reg->flags & SBI_DOMAIN_MEMREGION_READABLE)
218 continue;
219 if (reg->flags & SBI_DOMAIN_MEMREGION_WRITEABLE)
220 continue;
221 if (reg->flags & SBI_DOMAIN_MEMREGION_EXECUTABLE)
222 continue;
223
224 addr = reg->base;
225 size = 1UL << reg->order;
226 fdt_resv_memory_update_node(fdt, addr, size, i, parent,
227 (sbi_hart_pmp_count(scratch)) ? false : true);
228 i++;
229 }
230
231 return 0;
232 }
233
fdt_reserved_memory_nomap_fixup(void * fdt)234 int fdt_reserved_memory_nomap_fixup(void *fdt)
235 {
236 int parent, subnode;
237 int err;
238
239 /* Locate the reserved memory node */
240 parent = fdt_path_offset(fdt, "/reserved-memory");
241 if (parent < 0)
242 return parent;
243
244 fdt_for_each_subnode(subnode, fdt, parent) {
245 /*
246 * Tell operating system not to create a virtual
247 * mapping of the region as part of its standard
248 * mapping of system memory.
249 */
250 err = fdt_setprop_empty(fdt, subnode, "no-map");
251 if (err < 0)
252 return err;
253 }
254
255 return 0;
256 }
257
fdt_fixups(void * fdt)258 void fdt_fixups(void *fdt)
259 {
260 fdt_plic_fixup(fdt, "riscv,plic0");
261
262 fdt_reserved_memory_fixup(fdt);
263 }
264
265
266