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