xref: /linux/scripts/dtc/libfdt/fdt_rw.c (revision 9792d660a4e91d31a6b1af105ae3f1c29107e94b)
1 // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
2 /*
3  * libfdt - Flat Device Tree manipulation
4  * Copyright (C) 2006 David Gibson, IBM Corporation.
5  */
6 #include "libfdt_env.h"
7 
8 #include <fdt.h>
9 #include <libfdt.h>
10 
11 #include "libfdt_internal.h"
12 
13 static int fdt_blocks_misordered_(const void *fdt,
14 				  int mem_rsv_size, int struct_size)
15 {
16 	return (fdt_off_mem_rsvmap(fdt) < FDT_ALIGN(sizeof(struct fdt_header), 8))
17 		|| (fdt_off_dt_struct(fdt) <
18 		    (fdt_off_mem_rsvmap(fdt) + mem_rsv_size))
19 		|| (fdt_off_dt_strings(fdt) <
20 		    (fdt_off_dt_struct(fdt) + struct_size))
21 		|| (fdt_totalsize(fdt) <
22 		    (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt)));
23 }
24 
25 static int fdt_rw_probe_(void *fdt)
26 {
27 	if (can_assume(VALID_DTB))
28 		return 0;
29 	FDT_RO_PROBE(fdt);
30 
31 	if (!can_assume(LATEST) && fdt_version(fdt) < 17)
32 		return -FDT_ERR_BADVERSION;
33 	if (fdt_blocks_misordered_(fdt, sizeof(struct fdt_reserve_entry),
34 				   fdt_size_dt_struct(fdt)))
35 		return -FDT_ERR_BADLAYOUT;
36 	if (!can_assume(LATEST) && fdt_version(fdt) > 17)
37 		fdt_set_version(fdt, 17);
38 
39 	return 0;
40 }
41 
42 #define FDT_RW_PROBE(fdt) \
43 	{ \
44 		int err_; \
45 		if ((err_ = fdt_rw_probe_(fdt)) != 0) \
46 			return err_; \
47 	}
48 
49 static inline unsigned int fdt_data_size_(void *fdt)
50 {
51 	return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt);
52 }
53 
54 static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen)
55 {
56 	char *p = splicepoint;
57 	unsigned int dsize = fdt_data_size_(fdt);
58 	size_t soff = p - (char *)fdt;
59 
60 	if ((oldlen < 0) || (soff + oldlen < soff) || (soff + oldlen > dsize))
61 		return -FDT_ERR_BADOFFSET;
62 	if ((p < (char *)fdt) || (dsize + newlen < (unsigned)oldlen))
63 		return -FDT_ERR_BADOFFSET;
64 	if (dsize - oldlen + newlen > fdt_totalsize(fdt))
65 		return -FDT_ERR_NOSPACE;
66 	memmove(p + newlen, p + oldlen, ((char *)fdt + dsize) - (p + oldlen));
67 	return 0;
68 }
69 
70 static int fdt_splice_mem_rsv_(void *fdt, struct fdt_reserve_entry *p,
71 			       int oldn, int newn)
72 {
73 	int delta = (newn - oldn) * sizeof(*p);
74 	int err;
75 	err = fdt_splice_(fdt, p, oldn * sizeof(*p), newn * sizeof(*p));
76 	if (err)
77 		return err;
78 	fdt_set_off_dt_struct(fdt, fdt_off_dt_struct(fdt) + delta);
79 	fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta);
80 	return 0;
81 }
82 
83 static int fdt_splice_struct_(void *fdt, void *p,
84 			      int oldlen, int newlen)
85 {
86 	int delta = newlen - oldlen;
87 	int err;
88 
89 	if ((err = fdt_splice_(fdt, p, oldlen, newlen)))
90 		return err;
91 
92 	fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta);
93 	fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta);
94 	return 0;
95 }
96 
97 /* Must only be used to roll back in case of error */
98 static void fdt_del_last_string_(void *fdt, const char *s)
99 {
100 	int newlen = strlen(s) + 1;
101 
102 	fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen);
103 }
104 
105 static int fdt_splice_string_(void *fdt, int newlen)
106 {
107 	void *p = (char *)fdt
108 		+ fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt);
109 	int err;
110 
111 	if ((err = fdt_splice_(fdt, p, 0, newlen)))
112 		return err;
113 
114 	fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen);
115 	return 0;
116 }
117 
118 /**
119  * fdt_find_add_string_() - Find or allocate a string
120  *
121  * @fdt: pointer to the device tree to check/adjust
122  * @s: string to find/add
123  * @allocated: Set to 0 if the string was found, 1 if not found and so
124  *	allocated. Ignored if can_assume(NO_ROLLBACK)
125  * @return offset of string in the string table (whether found or added)
126  */
127 static int fdt_find_add_string_(void *fdt, const char *s, int slen,
128 				int *allocated)
129 {
130 	char *strtab = (char *)fdt + fdt_off_dt_strings(fdt);
131 	const char *p;
132 	char *new;
133 	int err;
134 
135 	if (!can_assume(NO_ROLLBACK))
136 		*allocated = 0;
137 
138 	p = fdt_find_string_len_(strtab, fdt_size_dt_strings(fdt), s, slen);
139 	if (p)
140 		/* found it */
141 		return (p - strtab);
142 
143 	new = strtab + fdt_size_dt_strings(fdt);
144 	err = fdt_splice_string_(fdt, slen + 1);
145 	if (err)
146 		return err;
147 
148 	if (!can_assume(NO_ROLLBACK))
149 		*allocated = 1;
150 
151 	memcpy(new, s, slen);
152 	new[slen] = '\0';
153 
154 	return (new - strtab);
155 }
156 
157 int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size)
158 {
159 	struct fdt_reserve_entry *re;
160 	int err;
161 
162 	FDT_RW_PROBE(fdt);
163 
164 	re = fdt_mem_rsv_w_(fdt, fdt_num_mem_rsv(fdt));
165 	err = fdt_splice_mem_rsv_(fdt, re, 0, 1);
166 	if (err)
167 		return err;
168 
169 	re->address = cpu_to_fdt64(address);
170 	re->size = cpu_to_fdt64(size);
171 	return 0;
172 }
173 
174 int fdt_del_mem_rsv(void *fdt, int n)
175 {
176 	struct fdt_reserve_entry *re = fdt_mem_rsv_w_(fdt, n);
177 
178 	FDT_RW_PROBE(fdt);
179 
180 	if (n >= fdt_num_mem_rsv(fdt))
181 		return -FDT_ERR_NOTFOUND;
182 
183 	return fdt_splice_mem_rsv_(fdt, re, 1, 0);
184 }
185 
186 static int fdt_resize_property_(void *fdt, int nodeoffset,
187 				const char *name, int namelen,
188 				int len, struct fdt_property **prop)
189 {
190 	int oldlen;
191 	int err;
192 
193 	*prop = fdt_get_property_namelen_w(fdt, nodeoffset, name, namelen,
194 					   &oldlen);
195 	if (!*prop)
196 		return oldlen;
197 
198 	if ((err = fdt_splice_struct_(fdt, (*prop)->data, FDT_TAGALIGN(oldlen),
199 				      FDT_TAGALIGN(len))))
200 		return err;
201 
202 	(*prop)->len = cpu_to_fdt32(len);
203 	return 0;
204 }
205 
206 static int fdt_add_property_(void *fdt, int nodeoffset, const char *name,
207 			     int namelen, int len, struct fdt_property **prop)
208 {
209 	int proplen;
210 	int nextoffset;
211 	int namestroff;
212 	int err;
213 	int allocated;
214 
215 	if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0)
216 		return nextoffset;
217 
218 	namestroff = fdt_find_add_string_(fdt, name, namelen, &allocated);
219 	if (namestroff < 0)
220 		return namestroff;
221 
222 	*prop = fdt_offset_ptr_w_(fdt, nextoffset);
223 	proplen = sizeof(**prop) + FDT_TAGALIGN(len);
224 
225 	err = fdt_splice_struct_(fdt, *prop, 0, proplen);
226 	if (err) {
227 		/* Delete the string if we failed to add it */
228 		if (!can_assume(NO_ROLLBACK) && allocated)
229 			fdt_del_last_string_(fdt, name);
230 		return err;
231 	}
232 
233 	(*prop)->tag = cpu_to_fdt32(FDT_PROP);
234 	(*prop)->nameoff = cpu_to_fdt32(namestroff);
235 	(*prop)->len = cpu_to_fdt32(len);
236 	return 0;
237 }
238 
239 int fdt_set_name(void *fdt, int nodeoffset, const char *name)
240 {
241 	char *namep;
242 	int oldlen, newlen;
243 	int err;
244 
245 	FDT_RW_PROBE(fdt);
246 
247 	namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen);
248 	if (!namep)
249 		return oldlen;
250 
251 	newlen = strlen(name);
252 
253 	err = fdt_splice_struct_(fdt, namep, FDT_TAGALIGN(oldlen+1),
254 				 FDT_TAGALIGN(newlen+1));
255 	if (err)
256 		return err;
257 
258 	memcpy(namep, name, newlen+1);
259 	return 0;
260 }
261 
262 int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name,
263 				    int namelen, int len, void **prop_data)
264 {
265 	struct fdt_property *prop;
266 	int err;
267 
268 	FDT_RW_PROBE(fdt);
269 
270 	err = fdt_resize_property_(fdt, nodeoffset, name, namelen, len, &prop);
271 	if (err == -FDT_ERR_NOTFOUND)
272 		err = fdt_add_property_(fdt, nodeoffset, name, namelen, len,
273 					&prop);
274 	if (err)
275 		return err;
276 
277 	*prop_data = prop->data;
278 	return 0;
279 }
280 
281 int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name,
282 			int namelen, const void *val, int len)
283 {
284 	void *prop_data;
285 	int err;
286 
287 	err = fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, namelen,
288 					      len, &prop_data);
289 	if (err)
290 		return err;
291 
292 	if (len)
293 		memcpy(prop_data, val, len);
294 	return 0;
295 }
296 
297 int fdt_appendprop(void *fdt, int nodeoffset, const char *name,
298 		   const void *val, int len)
299 {
300 	struct fdt_property *prop;
301 	int err, oldlen, newlen;
302 
303 	FDT_RW_PROBE(fdt);
304 
305 	prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen);
306 	if (prop) {
307 		newlen = len + oldlen;
308 		err = fdt_splice_struct_(fdt, prop->data,
309 					 FDT_TAGALIGN(oldlen),
310 					 FDT_TAGALIGN(newlen));
311 		if (err)
312 			return err;
313 		prop->len = cpu_to_fdt32(newlen);
314 		memcpy(prop->data + oldlen, val, len);
315 	} else {
316 		err = fdt_add_property_(fdt, nodeoffset, name, strlen(name),
317 					len, &prop);
318 		if (err)
319 			return err;
320 		memcpy(prop->data, val, len);
321 	}
322 	return 0;
323 }
324 
325 int fdt_delprop(void *fdt, int nodeoffset, const char *name)
326 {
327 	struct fdt_property *prop;
328 	int len, proplen;
329 
330 	FDT_RW_PROBE(fdt);
331 
332 	prop = fdt_get_property_w(fdt, nodeoffset, name, &len);
333 	if (!prop)
334 		return len;
335 
336 	proplen = sizeof(*prop) + FDT_TAGALIGN(len);
337 	return fdt_splice_struct_(fdt, prop, proplen, 0);
338 }
339 
340 int fdt_add_subnode_namelen(void *fdt, int parentoffset,
341 			    const char *name, int namelen)
342 {
343 	struct fdt_node_header *nh;
344 	int offset, nextoffset;
345 	int nodelen;
346 	int err;
347 	uint32_t tag;
348 	fdt32_t *endtag;
349 
350 	FDT_RW_PROBE(fdt);
351 
352 	offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen);
353 	if (offset >= 0)
354 		return -FDT_ERR_EXISTS;
355 	else if (offset != -FDT_ERR_NOTFOUND)
356 		return offset;
357 
358 	/* Try to place the new node after the parent's properties */
359 	tag = fdt_next_tag(fdt, parentoffset, &nextoffset);
360 	/* the fdt_subnode_offset_namelen() should ensure this never hits */
361 	if (!can_assume(LIBFDT_FLAWLESS) && (tag != FDT_BEGIN_NODE))
362 		return -FDT_ERR_INTERNAL;
363 	do {
364 		offset = nextoffset;
365 		tag = fdt_next_tag(fdt, offset, &nextoffset);
366 	} while ((tag == FDT_PROP) || (tag == FDT_NOP));
367 
368 	nh = fdt_offset_ptr_w_(fdt, offset);
369 	nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE;
370 
371 	err = fdt_splice_struct_(fdt, nh, 0, nodelen);
372 	if (err)
373 		return err;
374 
375 	nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE);
376 	memset(nh->name, 0, FDT_TAGALIGN(namelen+1));
377 	memcpy(nh->name, name, namelen);
378 	endtag = (fdt32_t *)((char *)nh + nodelen - FDT_TAGSIZE);
379 	*endtag = cpu_to_fdt32(FDT_END_NODE);
380 
381 	return offset;
382 }
383 
384 int fdt_add_subnode(void *fdt, int parentoffset, const char *name)
385 {
386 	return fdt_add_subnode_namelen(fdt, parentoffset, name, strlen(name));
387 }
388 
389 int fdt_del_node(void *fdt, int nodeoffset)
390 {
391 	int endoffset;
392 
393 	FDT_RW_PROBE(fdt);
394 
395 	endoffset = fdt_node_end_offset_(fdt, nodeoffset);
396 	if (endoffset < 0)
397 		return endoffset;
398 
399 	return fdt_splice_struct_(fdt, fdt_offset_ptr_w_(fdt, nodeoffset),
400 				  endoffset - nodeoffset, 0);
401 }
402 
403 static void fdt_packblocks_(const char *old, char *new,
404 			    int mem_rsv_size,
405 			    int struct_size,
406 			    int strings_size)
407 {
408 	int mem_rsv_off, struct_off, strings_off;
409 
410 	mem_rsv_off = FDT_ALIGN(sizeof(struct fdt_header), 8);
411 	struct_off = mem_rsv_off + mem_rsv_size;
412 	strings_off = struct_off + struct_size;
413 
414 	memmove(new + mem_rsv_off, old + fdt_off_mem_rsvmap(old), mem_rsv_size);
415 	fdt_set_off_mem_rsvmap(new, mem_rsv_off);
416 
417 	memmove(new + struct_off, old + fdt_off_dt_struct(old), struct_size);
418 	fdt_set_off_dt_struct(new, struct_off);
419 	fdt_set_size_dt_struct(new, struct_size);
420 
421 	memmove(new + strings_off, old + fdt_off_dt_strings(old), strings_size);
422 	fdt_set_off_dt_strings(new, strings_off);
423 	fdt_set_size_dt_strings(new, fdt_size_dt_strings(old));
424 }
425 
426 int fdt_open_into(const void *fdt, void *buf, int bufsize)
427 {
428 	int err;
429 	int mem_rsv_size, struct_size;
430 	int newsize;
431 	const char *fdtstart = fdt;
432 	const char *fdtend = fdtstart + fdt_totalsize(fdt);
433 	char *tmp;
434 
435 	FDT_RO_PROBE(fdt);
436 
437 	mem_rsv_size = (fdt_num_mem_rsv(fdt)+1)
438 		* sizeof(struct fdt_reserve_entry);
439 
440 	if (can_assume(LATEST) || fdt_version(fdt) >= 17) {
441 		struct_size = fdt_size_dt_struct(fdt);
442 	} else if (fdt_version(fdt) == 16) {
443 		struct_size = 0;
444 		while (fdt_next_tag(fdt, struct_size, &struct_size) != FDT_END)
445 			;
446 		if (struct_size < 0)
447 			return struct_size;
448 	} else {
449 		return -FDT_ERR_BADVERSION;
450 	}
451 
452 	if (can_assume(LIBFDT_ORDER) ||
453 	    !fdt_blocks_misordered_(fdt, mem_rsv_size, struct_size)) {
454 		/* no further work necessary */
455 		err = fdt_move(fdt, buf, bufsize);
456 		if (err)
457 			return err;
458 		fdt_set_version(buf, 17);
459 		fdt_set_size_dt_struct(buf, struct_size);
460 		fdt_set_totalsize(buf, bufsize);
461 		return 0;
462 	}
463 
464 	/* Need to reorder */
465 	newsize = FDT_ALIGN(sizeof(struct fdt_header), 8) + mem_rsv_size
466 		+ struct_size + fdt_size_dt_strings(fdt);
467 
468 	if (bufsize < newsize)
469 		return -FDT_ERR_NOSPACE;
470 
471 	/* First attempt to build converted tree at beginning of buffer */
472 	tmp = buf;
473 	/* But if that overlaps with the old tree... */
474 	if (((tmp + newsize) > fdtstart) && (tmp < fdtend)) {
475 		/* Try right after the old tree instead */
476 		tmp = (char *)(uintptr_t)fdtend;
477 		if ((tmp + newsize) > ((char *)buf + bufsize))
478 			return -FDT_ERR_NOSPACE;
479 	}
480 
481 	fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size,
482 			fdt_size_dt_strings(fdt));
483 	memmove(buf, tmp, newsize);
484 
485 	fdt_set_magic(buf, FDT_MAGIC);
486 	fdt_set_totalsize(buf, bufsize);
487 	fdt_set_version(buf, 17);
488 	fdt_set_last_comp_version(buf, 16);
489 	fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt));
490 
491 	return 0;
492 }
493 
494 int fdt_pack(void *fdt)
495 {
496 	int mem_rsv_size;
497 
498 	FDT_RW_PROBE(fdt);
499 
500 	mem_rsv_size = (fdt_num_mem_rsv(fdt)+1)
501 		* sizeof(struct fdt_reserve_entry);
502 	fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt),
503 			fdt_size_dt_strings(fdt));
504 	fdt_set_totalsize(fdt, fdt_data_size_(fdt));
505 
506 	return 0;
507 }
508