xref: /kvm-unit-tests/lib/libfdt/fdt.c (revision f2cd179cf9d00c00da2ef2604ed1fd8ebc303bb4)
1553125dfSNikos Nikoleris // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
2a322d4c5SAndrew Jones /*
3a322d4c5SAndrew Jones  * libfdt - Flat Device Tree manipulation
4a322d4c5SAndrew Jones  * Copyright (C) 2006 David Gibson, IBM Corporation.
5a322d4c5SAndrew Jones  */
6a322d4c5SAndrew Jones #include "libfdt_env.h"
7a322d4c5SAndrew Jones 
8a322d4c5SAndrew Jones #include <fdt.h>
9a322d4c5SAndrew Jones #include <libfdt.h>
10a322d4c5SAndrew Jones 
11a322d4c5SAndrew Jones #include "libfdt_internal.h"
12a322d4c5SAndrew Jones 
13553125dfSNikos Nikoleris /*
14553125dfSNikos Nikoleris  * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks
15553125dfSNikos Nikoleris  * that the given buffer contains what appears to be a flattened
16553125dfSNikos Nikoleris  * device tree with sane information in its header.
17553125dfSNikos Nikoleris  */
fdt_ro_probe_(const void * fdt)18553125dfSNikos Nikoleris int32_t fdt_ro_probe_(const void *fdt)
19a322d4c5SAndrew Jones {
20553125dfSNikos Nikoleris 	uint32_t totalsize = fdt_totalsize(fdt);
21553125dfSNikos Nikoleris 
22553125dfSNikos Nikoleris 	if (can_assume(VALID_DTB))
23553125dfSNikos Nikoleris 		return totalsize;
24553125dfSNikos Nikoleris 
25*80a6d74fSAndrew Jones 	/* The device tree must be at an 8-byte aligned address */
26*80a6d74fSAndrew Jones 	if ((uintptr_t)fdt & 7)
27*80a6d74fSAndrew Jones 		return -FDT_ERR_ALIGNMENT;
28*80a6d74fSAndrew Jones 
29a322d4c5SAndrew Jones 	if (fdt_magic(fdt) == FDT_MAGIC) {
30a322d4c5SAndrew Jones 		/* Complete tree */
31553125dfSNikos Nikoleris 		if (!can_assume(LATEST)) {
32a322d4c5SAndrew Jones 			if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
33a322d4c5SAndrew Jones 				return -FDT_ERR_BADVERSION;
34553125dfSNikos Nikoleris 			if (fdt_last_comp_version(fdt) >
35553125dfSNikos Nikoleris 					FDT_LAST_SUPPORTED_VERSION)
36a322d4c5SAndrew Jones 				return -FDT_ERR_BADVERSION;
37553125dfSNikos Nikoleris 		}
38a322d4c5SAndrew Jones 	} else if (fdt_magic(fdt) == FDT_SW_MAGIC) {
39a322d4c5SAndrew Jones 		/* Unfinished sequential-write blob */
40553125dfSNikos Nikoleris 		if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0)
41a322d4c5SAndrew Jones 			return -FDT_ERR_BADSTATE;
42a322d4c5SAndrew Jones 	} else {
43a322d4c5SAndrew Jones 		return -FDT_ERR_BADMAGIC;
44a322d4c5SAndrew Jones 	}
45a322d4c5SAndrew Jones 
46553125dfSNikos Nikoleris 	if (totalsize < INT32_MAX)
47553125dfSNikos Nikoleris 		return totalsize;
48553125dfSNikos Nikoleris 	else
49553125dfSNikos Nikoleris 		return -FDT_ERR_TRUNCATED;
50553125dfSNikos Nikoleris }
51553125dfSNikos Nikoleris 
check_off_(uint32_t hdrsize,uint32_t totalsize,uint32_t off)52553125dfSNikos Nikoleris static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off)
53553125dfSNikos Nikoleris {
54553125dfSNikos Nikoleris 	return (off >= hdrsize) && (off <= totalsize);
55553125dfSNikos Nikoleris }
56553125dfSNikos Nikoleris 
check_block_(uint32_t hdrsize,uint32_t totalsize,uint32_t base,uint32_t size)57553125dfSNikos Nikoleris static int check_block_(uint32_t hdrsize, uint32_t totalsize,
58553125dfSNikos Nikoleris 			uint32_t base, uint32_t size)
59553125dfSNikos Nikoleris {
60553125dfSNikos Nikoleris 	if (!check_off_(hdrsize, totalsize, base))
61553125dfSNikos Nikoleris 		return 0; /* block start out of bounds */
62553125dfSNikos Nikoleris 	if ((base + size) < base)
63553125dfSNikos Nikoleris 		return 0; /* overflow */
64553125dfSNikos Nikoleris 	if (!check_off_(hdrsize, totalsize, base + size))
65553125dfSNikos Nikoleris 		return 0; /* block end out of bounds */
66553125dfSNikos Nikoleris 	return 1;
67553125dfSNikos Nikoleris }
68553125dfSNikos Nikoleris 
fdt_header_size_(uint32_t version)69553125dfSNikos Nikoleris size_t fdt_header_size_(uint32_t version)
70553125dfSNikos Nikoleris {
71553125dfSNikos Nikoleris 	if (version <= 1)
72553125dfSNikos Nikoleris 		return FDT_V1_SIZE;
73553125dfSNikos Nikoleris 	else if (version <= 2)
74553125dfSNikos Nikoleris 		return FDT_V2_SIZE;
75553125dfSNikos Nikoleris 	else if (version <= 3)
76553125dfSNikos Nikoleris 		return FDT_V3_SIZE;
77553125dfSNikos Nikoleris 	else if (version <= 16)
78553125dfSNikos Nikoleris 		return FDT_V16_SIZE;
79553125dfSNikos Nikoleris 	else
80553125dfSNikos Nikoleris 		return FDT_V17_SIZE;
81553125dfSNikos Nikoleris }
82553125dfSNikos Nikoleris 
fdt_header_size(const void * fdt)83553125dfSNikos Nikoleris size_t fdt_header_size(const void *fdt)
84553125dfSNikos Nikoleris {
85553125dfSNikos Nikoleris 	return can_assume(LATEST) ? FDT_V17_SIZE :
86553125dfSNikos Nikoleris 		fdt_header_size_(fdt_version(fdt));
87553125dfSNikos Nikoleris }
88553125dfSNikos Nikoleris 
fdt_check_header(const void * fdt)89553125dfSNikos Nikoleris int fdt_check_header(const void *fdt)
90553125dfSNikos Nikoleris {
91553125dfSNikos Nikoleris 	size_t hdrsize;
92553125dfSNikos Nikoleris 
93*80a6d74fSAndrew Jones 	/* The device tree must be at an 8-byte aligned address */
94*80a6d74fSAndrew Jones 	if ((uintptr_t)fdt & 7)
95*80a6d74fSAndrew Jones 		return -FDT_ERR_ALIGNMENT;
96*80a6d74fSAndrew Jones 
97553125dfSNikos Nikoleris 	if (fdt_magic(fdt) != FDT_MAGIC)
98553125dfSNikos Nikoleris 		return -FDT_ERR_BADMAGIC;
99553125dfSNikos Nikoleris 	if (!can_assume(LATEST)) {
100553125dfSNikos Nikoleris 		if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION)
101553125dfSNikos Nikoleris 		    || (fdt_last_comp_version(fdt) >
102553125dfSNikos Nikoleris 			FDT_LAST_SUPPORTED_VERSION))
103553125dfSNikos Nikoleris 			return -FDT_ERR_BADVERSION;
104553125dfSNikos Nikoleris 		if (fdt_version(fdt) < fdt_last_comp_version(fdt))
105553125dfSNikos Nikoleris 			return -FDT_ERR_BADVERSION;
106553125dfSNikos Nikoleris 	}
107553125dfSNikos Nikoleris 	hdrsize = fdt_header_size(fdt);
108553125dfSNikos Nikoleris 	if (!can_assume(VALID_DTB)) {
109553125dfSNikos Nikoleris 
110553125dfSNikos Nikoleris 		if ((fdt_totalsize(fdt) < hdrsize)
111553125dfSNikos Nikoleris 		    || (fdt_totalsize(fdt) > INT_MAX))
112553125dfSNikos Nikoleris 			return -FDT_ERR_TRUNCATED;
113553125dfSNikos Nikoleris 
114553125dfSNikos Nikoleris 		/* Bounds check memrsv block */
115553125dfSNikos Nikoleris 		if (!check_off_(hdrsize, fdt_totalsize(fdt),
116553125dfSNikos Nikoleris 				fdt_off_mem_rsvmap(fdt)))
117553125dfSNikos Nikoleris 			return -FDT_ERR_TRUNCATED;
118553125dfSNikos Nikoleris 	}
119553125dfSNikos Nikoleris 
120553125dfSNikos Nikoleris 	if (!can_assume(VALID_DTB)) {
121553125dfSNikos Nikoleris 		/* Bounds check structure block */
122553125dfSNikos Nikoleris 		if (!can_assume(LATEST) && fdt_version(fdt) < 17) {
123553125dfSNikos Nikoleris 			if (!check_off_(hdrsize, fdt_totalsize(fdt),
124553125dfSNikos Nikoleris 					fdt_off_dt_struct(fdt)))
125553125dfSNikos Nikoleris 				return -FDT_ERR_TRUNCATED;
126553125dfSNikos Nikoleris 		} else {
127553125dfSNikos Nikoleris 			if (!check_block_(hdrsize, fdt_totalsize(fdt),
128553125dfSNikos Nikoleris 					  fdt_off_dt_struct(fdt),
129553125dfSNikos Nikoleris 					  fdt_size_dt_struct(fdt)))
130553125dfSNikos Nikoleris 				return -FDT_ERR_TRUNCATED;
131553125dfSNikos Nikoleris 		}
132553125dfSNikos Nikoleris 
133553125dfSNikos Nikoleris 		/* Bounds check strings block */
134553125dfSNikos Nikoleris 		if (!check_block_(hdrsize, fdt_totalsize(fdt),
135553125dfSNikos Nikoleris 				  fdt_off_dt_strings(fdt),
136553125dfSNikos Nikoleris 				  fdt_size_dt_strings(fdt)))
137553125dfSNikos Nikoleris 			return -FDT_ERR_TRUNCATED;
138553125dfSNikos Nikoleris 	}
139553125dfSNikos Nikoleris 
140a322d4c5SAndrew Jones 	return 0;
141a322d4c5SAndrew Jones }
142a322d4c5SAndrew Jones 
fdt_offset_ptr(const void * fdt,int offset,unsigned int len)143a322d4c5SAndrew Jones const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
144a322d4c5SAndrew Jones {
145*80a6d74fSAndrew Jones 	unsigned int uoffset = offset;
146*80a6d74fSAndrew Jones 	unsigned int absoffset = offset + fdt_off_dt_struct(fdt);
147*80a6d74fSAndrew Jones 
148*80a6d74fSAndrew Jones 	if (offset < 0)
149*80a6d74fSAndrew Jones 		return NULL;
150a322d4c5SAndrew Jones 
151553125dfSNikos Nikoleris 	if (!can_assume(VALID_INPUT))
152*80a6d74fSAndrew Jones 		if ((absoffset < uoffset)
153553125dfSNikos Nikoleris 		    || ((absoffset + len) < absoffset)
154553125dfSNikos Nikoleris 		    || (absoffset + len) > fdt_totalsize(fdt))
155553125dfSNikos Nikoleris 			return NULL;
156553125dfSNikos Nikoleris 
157553125dfSNikos Nikoleris 	if (can_assume(LATEST) || fdt_version(fdt) >= 0x11)
158*80a6d74fSAndrew Jones 		if (((uoffset + len) < uoffset)
159a322d4c5SAndrew Jones 		    || ((offset + len) > fdt_size_dt_struct(fdt)))
160a322d4c5SAndrew Jones 			return NULL;
161a322d4c5SAndrew Jones 
162553125dfSNikos Nikoleris 	return fdt_offset_ptr_(fdt, offset);
163a322d4c5SAndrew Jones }
164a322d4c5SAndrew Jones 
fdt_next_tag(const void * fdt,int startoffset,int * nextoffset)165a322d4c5SAndrew Jones uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
166a322d4c5SAndrew Jones {
167a322d4c5SAndrew Jones 	const fdt32_t *tagp, *lenp;
168a322d4c5SAndrew Jones 	uint32_t tag;
169a322d4c5SAndrew Jones 	int offset = startoffset;
170a322d4c5SAndrew Jones 	const char *p;
171a322d4c5SAndrew Jones 
172a322d4c5SAndrew Jones 	*nextoffset = -FDT_ERR_TRUNCATED;
173a322d4c5SAndrew Jones 	tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
174553125dfSNikos Nikoleris 	if (!can_assume(VALID_DTB) && !tagp)
175a322d4c5SAndrew Jones 		return FDT_END; /* premature end */
176a322d4c5SAndrew Jones 	tag = fdt32_to_cpu(*tagp);
177a322d4c5SAndrew Jones 	offset += FDT_TAGSIZE;
178a322d4c5SAndrew Jones 
179a322d4c5SAndrew Jones 	*nextoffset = -FDT_ERR_BADSTRUCTURE;
180a322d4c5SAndrew Jones 	switch (tag) {
181a322d4c5SAndrew Jones 	case FDT_BEGIN_NODE:
182a322d4c5SAndrew Jones 		/* skip name */
183a322d4c5SAndrew Jones 		do {
184a322d4c5SAndrew Jones 			p = fdt_offset_ptr(fdt, offset++, 1);
185a322d4c5SAndrew Jones 		} while (p && (*p != '\0'));
186553125dfSNikos Nikoleris 		if (!can_assume(VALID_DTB) && !p)
187a322d4c5SAndrew Jones 			return FDT_END; /* premature end */
188a322d4c5SAndrew Jones 		break;
189a322d4c5SAndrew Jones 
190a322d4c5SAndrew Jones 	case FDT_PROP:
191a322d4c5SAndrew Jones 		lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
192553125dfSNikos Nikoleris 		if (!can_assume(VALID_DTB) && !lenp)
193a322d4c5SAndrew Jones 			return FDT_END; /* premature end */
194a322d4c5SAndrew Jones 		/* skip-name offset, length and value */
195a322d4c5SAndrew Jones 		offset += sizeof(struct fdt_property) - FDT_TAGSIZE
196a322d4c5SAndrew Jones 			+ fdt32_to_cpu(*lenp);
197553125dfSNikos Nikoleris 		if (!can_assume(LATEST) &&
198553125dfSNikos Nikoleris 		    fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 &&
199553125dfSNikos Nikoleris 		    ((offset - fdt32_to_cpu(*lenp)) % 8) != 0)
200553125dfSNikos Nikoleris 			offset += 4;
201a322d4c5SAndrew Jones 		break;
202a322d4c5SAndrew Jones 
203a322d4c5SAndrew Jones 	case FDT_END:
204a322d4c5SAndrew Jones 	case FDT_END_NODE:
205a322d4c5SAndrew Jones 	case FDT_NOP:
206a322d4c5SAndrew Jones 		break;
207a322d4c5SAndrew Jones 
208a322d4c5SAndrew Jones 	default:
209a322d4c5SAndrew Jones 		return FDT_END;
210a322d4c5SAndrew Jones 	}
211a322d4c5SAndrew Jones 
212a322d4c5SAndrew Jones 	if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset))
213a322d4c5SAndrew Jones 		return FDT_END; /* premature end */
214a322d4c5SAndrew Jones 
215a322d4c5SAndrew Jones 	*nextoffset = FDT_TAGALIGN(offset);
216a322d4c5SAndrew Jones 	return tag;
217a322d4c5SAndrew Jones }
218a322d4c5SAndrew Jones 
fdt_check_node_offset_(const void * fdt,int offset)219553125dfSNikos Nikoleris int fdt_check_node_offset_(const void *fdt, int offset)
220a322d4c5SAndrew Jones {
221*80a6d74fSAndrew Jones 	if (!can_assume(VALID_INPUT)
222*80a6d74fSAndrew Jones 	    && ((offset < 0) || (offset % FDT_TAGSIZE)))
223*80a6d74fSAndrew Jones 		return -FDT_ERR_BADOFFSET;
224*80a6d74fSAndrew Jones 
225*80a6d74fSAndrew Jones 	if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE)
226a322d4c5SAndrew Jones 		return -FDT_ERR_BADOFFSET;
227a322d4c5SAndrew Jones 
228a322d4c5SAndrew Jones 	return offset;
229a322d4c5SAndrew Jones }
230a322d4c5SAndrew Jones 
fdt_check_prop_offset_(const void * fdt,int offset)231553125dfSNikos Nikoleris int fdt_check_prop_offset_(const void *fdt, int offset)
232a322d4c5SAndrew Jones {
233*80a6d74fSAndrew Jones 	if (!can_assume(VALID_INPUT)
234*80a6d74fSAndrew Jones 	    && ((offset < 0) || (offset % FDT_TAGSIZE)))
235*80a6d74fSAndrew Jones 		return -FDT_ERR_BADOFFSET;
236*80a6d74fSAndrew Jones 
237*80a6d74fSAndrew Jones 	if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP)
238a322d4c5SAndrew Jones 		return -FDT_ERR_BADOFFSET;
239a322d4c5SAndrew Jones 
240a322d4c5SAndrew Jones 	return offset;
241a322d4c5SAndrew Jones }
242a322d4c5SAndrew Jones 
fdt_next_node(const void * fdt,int offset,int * depth)243a322d4c5SAndrew Jones int fdt_next_node(const void *fdt, int offset, int *depth)
244a322d4c5SAndrew Jones {
245a322d4c5SAndrew Jones 	int nextoffset = 0;
246a322d4c5SAndrew Jones 	uint32_t tag;
247a322d4c5SAndrew Jones 
248a322d4c5SAndrew Jones 	if (offset >= 0)
249553125dfSNikos Nikoleris 		if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0)
250a322d4c5SAndrew Jones 			return nextoffset;
251a322d4c5SAndrew Jones 
252a322d4c5SAndrew Jones 	do {
253a322d4c5SAndrew Jones 		offset = nextoffset;
254a322d4c5SAndrew Jones 		tag = fdt_next_tag(fdt, offset, &nextoffset);
255a322d4c5SAndrew Jones 
256a322d4c5SAndrew Jones 		switch (tag) {
257a322d4c5SAndrew Jones 		case FDT_PROP:
258a322d4c5SAndrew Jones 		case FDT_NOP:
259a322d4c5SAndrew Jones 			break;
260a322d4c5SAndrew Jones 
261a322d4c5SAndrew Jones 		case FDT_BEGIN_NODE:
262a322d4c5SAndrew Jones 			if (depth)
263a322d4c5SAndrew Jones 				(*depth)++;
264a322d4c5SAndrew Jones 			break;
265a322d4c5SAndrew Jones 
266a322d4c5SAndrew Jones 		case FDT_END_NODE:
267a322d4c5SAndrew Jones 			if (depth && ((--(*depth)) < 0))
268a322d4c5SAndrew Jones 				return nextoffset;
269a322d4c5SAndrew Jones 			break;
270a322d4c5SAndrew Jones 
271a322d4c5SAndrew Jones 		case FDT_END:
272a322d4c5SAndrew Jones 			if ((nextoffset >= 0)
273a322d4c5SAndrew Jones 			    || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
274a322d4c5SAndrew Jones 				return -FDT_ERR_NOTFOUND;
275a322d4c5SAndrew Jones 			else
276a322d4c5SAndrew Jones 				return nextoffset;
277a322d4c5SAndrew Jones 		}
278a322d4c5SAndrew Jones 	} while (tag != FDT_BEGIN_NODE);
279a322d4c5SAndrew Jones 
280a322d4c5SAndrew Jones 	return offset;
281a322d4c5SAndrew Jones }
282a322d4c5SAndrew Jones 
fdt_first_subnode(const void * fdt,int offset)283a322d4c5SAndrew Jones int fdt_first_subnode(const void *fdt, int offset)
284a322d4c5SAndrew Jones {
285a322d4c5SAndrew Jones 	int depth = 0;
286a322d4c5SAndrew Jones 
287a322d4c5SAndrew Jones 	offset = fdt_next_node(fdt, offset, &depth);
288a322d4c5SAndrew Jones 	if (offset < 0 || depth != 1)
289a322d4c5SAndrew Jones 		return -FDT_ERR_NOTFOUND;
290a322d4c5SAndrew Jones 
291a322d4c5SAndrew Jones 	return offset;
292a322d4c5SAndrew Jones }
293a322d4c5SAndrew Jones 
fdt_next_subnode(const void * fdt,int offset)294a322d4c5SAndrew Jones int fdt_next_subnode(const void *fdt, int offset)
295a322d4c5SAndrew Jones {
296a322d4c5SAndrew Jones 	int depth = 1;
297a322d4c5SAndrew Jones 
298a322d4c5SAndrew Jones 	/*
299a322d4c5SAndrew Jones 	 * With respect to the parent, the depth of the next subnode will be
300a322d4c5SAndrew Jones 	 * the same as the last.
301a322d4c5SAndrew Jones 	 */
302a322d4c5SAndrew Jones 	do {
303a322d4c5SAndrew Jones 		offset = fdt_next_node(fdt, offset, &depth);
304a322d4c5SAndrew Jones 		if (offset < 0 || depth < 1)
305a322d4c5SAndrew Jones 			return -FDT_ERR_NOTFOUND;
306a322d4c5SAndrew Jones 	} while (depth > 1);
307a322d4c5SAndrew Jones 
308a322d4c5SAndrew Jones 	return offset;
309a322d4c5SAndrew Jones }
310a322d4c5SAndrew Jones 
fdt_find_string_(const char * strtab,int tabsize,const char * s)311553125dfSNikos Nikoleris const char *fdt_find_string_(const char *strtab, int tabsize, const char *s)
312a322d4c5SAndrew Jones {
313a322d4c5SAndrew Jones 	int len = strlen(s) + 1;
314a322d4c5SAndrew Jones 	const char *last = strtab + tabsize - len;
315a322d4c5SAndrew Jones 	const char *p;
316a322d4c5SAndrew Jones 
317a322d4c5SAndrew Jones 	for (p = strtab; p <= last; p++)
318a322d4c5SAndrew Jones 		if (memcmp(p, s, len) == 0)
319a322d4c5SAndrew Jones 			return p;
320a322d4c5SAndrew Jones 	return NULL;
321a322d4c5SAndrew Jones }
322a322d4c5SAndrew Jones 
fdt_move(const void * fdt,void * buf,int bufsize)323a322d4c5SAndrew Jones int fdt_move(const void *fdt, void *buf, int bufsize)
324a322d4c5SAndrew Jones {
325*80a6d74fSAndrew Jones 	if (!can_assume(VALID_INPUT) && bufsize < 0)
326*80a6d74fSAndrew Jones 		return -FDT_ERR_NOSPACE;
327*80a6d74fSAndrew Jones 
328553125dfSNikos Nikoleris 	FDT_RO_PROBE(fdt);
329a322d4c5SAndrew Jones 
330*80a6d74fSAndrew Jones 	if (fdt_totalsize(fdt) > (unsigned int)bufsize)
331a322d4c5SAndrew Jones 		return -FDT_ERR_NOSPACE;
332a322d4c5SAndrew Jones 
333a322d4c5SAndrew Jones 	memmove(buf, fdt, fdt_totalsize(fdt));
334a322d4c5SAndrew Jones 	return 0;
335a322d4c5SAndrew Jones }
336