1 /*
2  * Copyright 2020 The Hafnium Authors.
3  *
4  * Use of this source code is governed by a BSD-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/BSD-3-Clause.
7  */
8 
9 #include "hf/fdt_patch.h"
10 
11 #include <libfdt.h>
12 
13 #include "hf/boot_params.h"
14 #include "hf/dlog.h"
15 #include "hf/fdt.h"
16 #include "hf/fdt_handler.h"
17 #include "hf/layout.h"
18 #include "hf/mm.h"
19 
patch_uint(void * fdt,int off,const char * prop,uint64_t val)20 static bool patch_uint(void *fdt, int off, const char *prop, uint64_t val)
21 {
22 	const void *data;
23 	int lenp;
24 
25 	data = fdt_getprop(fdt, off, prop, &lenp);
26 	if (data == NULL) {
27 		return false;
28 	}
29 
30 	switch (lenp) {
31 	case sizeof(uint64_t): {
32 		return fdt_setprop_inplace_u64(fdt, off, prop, val) == 0;
33 	}
34 	case sizeof(uint32_t): {
35 		return (val <= UINT32_MAX) &&
36 		       (fdt_setprop_inplace_u32(fdt, off, prop, val) == 0);
37 	}
38 	default: {
39 		return false;
40 	}
41 	}
42 }
43 
add_mem_reservation(void * fdt,paddr_t begin,paddr_t end)44 static bool add_mem_reservation(void *fdt, paddr_t begin, paddr_t end)
45 {
46 	size_t len = pa_difference(begin, end);
47 
48 	return fdt_add_mem_rsv(fdt, pa_addr(begin), len) == 0;
49 }
50 
fdt_patch(struct mm_stage1_locked stage1_locked,paddr_t fdt_addr,struct boot_params_update * p,struct mpool * ppool)51 bool fdt_patch(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
52 	       struct boot_params_update *p, struct mpool *ppool)
53 {
54 	void *fdt;
55 	int buf_size;
56 	int off;
57 	bool ret = false;
58 	bool rsv;
59 	size_t i;
60 
61 	/* Map the fdt header in. */
62 	fdt = mm_identity_map(stage1_locked, fdt_addr,
63 			      pa_add(fdt_addr, FDT_V17_HEADER_SIZE), MM_MODE_R,
64 			      ppool);
65 	if (!fdt) {
66 		dlog_error("Unable to map FDT header.\n");
67 		return false;
68 	}
69 
70 	if (fdt_check_header(fdt) != 0) {
71 		dlog_error("FDT failed header validation.\n");
72 		goto err_unmap_fdt_header;
73 	}
74 
75 	/* Map the fdt (+ a page) in r/w mode in preparation for updating it. */
76 	buf_size = fdt_totalsize(fdt) + PAGE_SIZE;
77 	fdt = mm_identity_map(stage1_locked, fdt_addr,
78 			      pa_add(fdt_addr, buf_size), MM_MODE_R | MM_MODE_W,
79 			      ppool);
80 	if (!fdt) {
81 		dlog_error("Unable to map FDT in r/w mode.\n");
82 		goto err_unmap_fdt_header;
83 	}
84 
85 	if (fdt_check_full(fdt, buf_size) != 0) {
86 		dlog_error("FDT failed validation.\n");
87 		goto out_unmap_fdt;
88 	}
89 
90 	/* Allow some extra room for the modifications to the FDT. */
91 	if (fdt_open_into(fdt, fdt, buf_size) != 0) {
92 		dlog_error("FDT failed to open_into.\n");
93 		goto out_unmap_fdt;
94 	}
95 
96 	off = fdt_path_offset(fdt, "/chosen");
97 	if (off < 0) {
98 		dlog_error("Unable to find FDT '/chosen' node.\n");
99 		goto out_unmap_fdt;
100 	}
101 
102 	/* Patch FDT to point to new ramdisk. */
103 	if (!patch_uint(fdt, off, FDT_PROP_INITRD_START,
104 			pa_addr(p->initrd_begin))) {
105 		dlog_error("Unable to write" FDT_PROP_INITRD_START "\n");
106 		goto out_unmap_fdt;
107 	}
108 
109 	if (!patch_uint(fdt, off, FDT_PROP_INITRD_END,
110 			pa_addr(p->initrd_end))) {
111 		dlog_error("Unable to write " FDT_PROP_INITRD_END "\n");
112 		goto out_unmap_fdt;
113 	}
114 
115 	/*
116 	 * Patch FDT to reserve hypervisor memory so the primary VM doesn't try
117 	 * to use it.
118 	 */
119 	rsv = true;
120 	rsv &= add_mem_reservation(fdt, layout_text_begin(), layout_text_end());
121 	rsv &= add_mem_reservation(fdt, layout_rodata_begin(),
122 				   layout_rodata_end());
123 	rsv &= add_mem_reservation(fdt, layout_data_begin(), layout_data_end());
124 	rsv &= add_mem_reservation(fdt, layout_stacks_begin(),
125 				   layout_stacks_end());
126 
127 	/* Patch FDT to reserve memory for secondary VMs. */
128 	for (i = 0; i < p->reserved_ranges_count; ++i) {
129 		struct mem_range range = p->reserved_ranges[i];
130 
131 		rsv &= add_mem_reservation(fdt, range.begin, range.end);
132 	}
133 
134 	if (!rsv) {
135 		dlog_error("Unable to add memory reservations to FDT.\n");
136 		goto out_unmap_fdt;
137 	}
138 
139 	if (fdt_pack(fdt) != 0) {
140 		dlog_error("Failed to pack FDT.\n");
141 		goto out_unmap_fdt;
142 	}
143 
144 	ret = true;
145 
146 out_unmap_fdt:
147 	/* Unmap FDT. */
148 	if (!mm_unmap(stage1_locked, fdt_addr,
149 		      pa_add(fdt_addr, fdt_totalsize(fdt) + PAGE_SIZE),
150 		      ppool)) {
151 		dlog_error("Unable to unmap writable FDT.\n");
152 		return false;
153 	}
154 	return ret;
155 
156 err_unmap_fdt_header:
157 	mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, FDT_V17_HEADER_SIZE),
158 		 ppool);
159 	return false;
160 }
161 
fdt_patch_mem(struct mm_stage1_locked stage1_locked,paddr_t fdt_addr,size_t fdt_max_size,paddr_t mem_begin,paddr_t mem_end,struct mpool * ppool)162 bool fdt_patch_mem(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
163 		   size_t fdt_max_size, paddr_t mem_begin, paddr_t mem_end,
164 		   struct mpool *ppool)
165 {
166 	int ret = 0;
167 	uint64_t mem_start_addr = pa_addr(mem_begin);
168 	size_t mem_size = pa_difference(mem_begin, mem_end);
169 	struct fdt_header *fdt;
170 	int fdt_memory_node;
171 	int root_offset;
172 
173 	/* Map the fdt in r/w mode in preparation for updating it. */
174 	fdt = mm_identity_map(stage1_locked, fdt_addr,
175 			      pa_add(fdt_addr, fdt_max_size),
176 			      MM_MODE_R | MM_MODE_W, ppool);
177 
178 	if (!fdt) {
179 		dlog_error("Unable to map FDT in r/w mode.\n");
180 		return false;
181 	}
182 
183 	ret = fdt_check_full(fdt, fdt_max_size);
184 	if (ret != 0) {
185 		dlog_error("FDT failed validation. Error: %d\n", ret);
186 		goto out_unmap_fdt;
187 	}
188 
189 	/* Allow some extra room for patches to the FDT. */
190 	ret = fdt_open_into(fdt, fdt, (int)fdt_max_size);
191 	if (ret != 0) {
192 		dlog_error("FDT failed to open_into. Error: %d\n", ret);
193 		goto out_unmap_fdt;
194 	}
195 
196 	root_offset = fdt_path_offset(fdt, "/");
197 	if (ret < 0) {
198 		dlog_error("FDT cannot find root offset. Error: %d\n", ret);
199 		goto out_unmap_fdt;
200 	}
201 
202 	/* Add a node to hold the memory information. */
203 	fdt_memory_node = fdt_add_subnode(fdt, root_offset, "memory");
204 	if (fdt_memory_node < 0) {
205 		ret = fdt_memory_node;
206 		dlog_error("FDT cannot add memory node. Error: %d\n", ret);
207 		goto out_unmap_fdt;
208 	}
209 
210 	/* Set the values for the VM's memory in the FDT. */
211 	ret = fdt_appendprop_addrrange(fdt, root_offset, fdt_memory_node, "reg",
212 				       mem_start_addr, mem_size);
213 	if (ret != 0) {
214 		dlog_error(
215 			"FDT failed to append address range property for "
216 			"memory. Error: %d\n",
217 			ret);
218 		goto out_unmap_fdt;
219 	}
220 
221 	ret = fdt_appendprop_string(fdt, fdt_memory_node, "device_type",
222 				    "memory");
223 	if (ret != 0) {
224 		dlog_error(
225 			"FDT failed to append device_type property for memory. "
226 			"Error: %d\n",
227 			ret);
228 		goto out_unmap_fdt;
229 	}
230 
231 	ret = fdt_pack(fdt);
232 	if (ret != 0) {
233 		dlog_error("Failed to pack FDT. Error: %d\n", ret);
234 		goto out_unmap_fdt;
235 	}
236 
237 out_unmap_fdt:
238 	/* Unmap FDT. */
239 	if (!mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, fdt_max_size),
240 		      ppool)) {
241 		dlog_error("Unable to unmap writable FDT.\n");
242 		return false;
243 	}
244 
245 	return ret == 0;
246 }
247