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 | /* |
14 | * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks |
15 | * that the given buffer contains what appears to be a flattened |
16 | * device tree with sane information in its header. |
17 | */ |
18 | int32_t fdt_ro_probe_(const void *fdt) |
19 | { |
20 | uint32_t totalsize = fdt_totalsize(fdt); |
21 | |
22 | if (fdt_magic(fdt) == FDT_MAGIC) { |
23 | /* Complete tree */ |
24 | if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) |
25 | return -FDT_ERR_BADVERSION; |
26 | if (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION) |
27 | return -FDT_ERR_BADVERSION; |
28 | } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { |
29 | /* Unfinished sequential-write blob */ |
30 | if (fdt_size_dt_struct(fdt) == 0) |
31 | return -FDT_ERR_BADSTATE; |
32 | } else { |
33 | return -FDT_ERR_BADMAGIC; |
34 | } |
35 | |
36 | if (totalsize < INT32_MAX) |
37 | return totalsize; |
38 | else |
39 | return -FDT_ERR_TRUNCATED; |
40 | } |
41 | |
42 | static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) |
43 | { |
44 | return (off >= hdrsize) && (off <= totalsize); |
45 | } |
46 | |
47 | static int check_block_(uint32_t hdrsize, uint32_t totalsize, |
48 | uint32_t base, uint32_t size) |
49 | { |
50 | if (!check_off_(hdrsize, totalsize, base)) |
51 | return 0; /* block start out of bounds */ |
52 | if ((base + size) < base) |
53 | return 0; /* overflow */ |
54 | if (!check_off_(hdrsize, totalsize, base + size)) |
55 | return 0; /* block end out of bounds */ |
56 | return 1; |
57 | } |
58 | |
59 | size_t (uint32_t version) |
60 | { |
61 | if (version <= 1) |
62 | return FDT_V1_SIZE; |
63 | else if (version <= 2) |
64 | return FDT_V2_SIZE; |
65 | else if (version <= 3) |
66 | return FDT_V3_SIZE; |
67 | else if (version <= 16) |
68 | return FDT_V16_SIZE; |
69 | else |
70 | return FDT_V17_SIZE; |
71 | } |
72 | |
73 | int (const void *fdt) |
74 | { |
75 | size_t hdrsize; |
76 | |
77 | if (fdt_magic(fdt) != FDT_MAGIC) |
78 | return -FDT_ERR_BADMAGIC; |
79 | hdrsize = fdt_header_size(fdt); |
80 | if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) |
81 | || (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION)) |
82 | return -FDT_ERR_BADVERSION; |
83 | if (fdt_version(fdt) < fdt_last_comp_version(fdt)) |
84 | return -FDT_ERR_BADVERSION; |
85 | |
86 | if ((fdt_totalsize(fdt) < hdrsize) |
87 | || (fdt_totalsize(fdt) > INT_MAX)) |
88 | return -FDT_ERR_TRUNCATED; |
89 | |
90 | /* Bounds check memrsv block */ |
91 | if (!check_off_(hdrsize, fdt_totalsize(fdt), fdt_off_mem_rsvmap(fdt))) |
92 | return -FDT_ERR_TRUNCATED; |
93 | |
94 | /* Bounds check structure block */ |
95 | if (fdt_version(fdt) < 17) { |
96 | if (!check_off_(hdrsize, fdt_totalsize(fdt), |
97 | fdt_off_dt_struct(fdt))) |
98 | return -FDT_ERR_TRUNCATED; |
99 | } else { |
100 | if (!check_block_(hdrsize, fdt_totalsize(fdt), |
101 | fdt_off_dt_struct(fdt), |
102 | fdt_size_dt_struct(fdt))) |
103 | return -FDT_ERR_TRUNCATED; |
104 | } |
105 | |
106 | /* Bounds check strings block */ |
107 | if (!check_block_(hdrsize, fdt_totalsize(fdt), |
108 | fdt_off_dt_strings(fdt), fdt_size_dt_strings(fdt))) |
109 | return -FDT_ERR_TRUNCATED; |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) |
115 | { |
116 | unsigned absoffset = offset + fdt_off_dt_struct(fdt); |
117 | |
118 | if ((absoffset < offset) |
119 | || ((absoffset + len) < absoffset) |
120 | || (absoffset + len) > fdt_totalsize(fdt)) |
121 | return NULL; |
122 | |
123 | if (fdt_version(fdt) >= 0x11) |
124 | if (((offset + len) < offset) |
125 | || ((offset + len) > fdt_size_dt_struct(fdt))) |
126 | return NULL; |
127 | |
128 | return fdt_offset_ptr_(fdt, offset); |
129 | } |
130 | |
131 | uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) |
132 | { |
133 | const fdt32_t *tagp, *lenp; |
134 | uint32_t tag; |
135 | int offset = startoffset; |
136 | const char *p; |
137 | |
138 | *nextoffset = -FDT_ERR_TRUNCATED; |
139 | tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); |
140 | if (!tagp) |
141 | return FDT_END; /* premature end */ |
142 | tag = fdt32_to_cpu(*tagp); |
143 | offset += FDT_TAGSIZE; |
144 | |
145 | *nextoffset = -FDT_ERR_BADSTRUCTURE; |
146 | switch (tag) { |
147 | case FDT_BEGIN_NODE: |
148 | /* skip name */ |
149 | do { |
150 | p = fdt_offset_ptr(fdt, offset++, 1); |
151 | } while (p && (*p != '\0')); |
152 | if (!p) |
153 | return FDT_END; /* premature end */ |
154 | break; |
155 | |
156 | case FDT_PROP: |
157 | lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); |
158 | if (!lenp) |
159 | return FDT_END; /* premature end */ |
160 | /* skip-name offset, length and value */ |
161 | offset += sizeof(struct fdt_property) - FDT_TAGSIZE |
162 | + fdt32_to_cpu(*lenp); |
163 | if (fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 && |
164 | ((offset - fdt32_to_cpu(*lenp)) % 8) != 0) |
165 | offset += 4; |
166 | break; |
167 | |
168 | case FDT_END: |
169 | case FDT_END_NODE: |
170 | case FDT_NOP: |
171 | break; |
172 | |
173 | default: |
174 | return FDT_END; |
175 | } |
176 | |
177 | if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) |
178 | return FDT_END; /* premature end */ |
179 | |
180 | *nextoffset = FDT_TAGALIGN(offset); |
181 | return tag; |
182 | } |
183 | |
184 | int fdt_check_node_offset_(const void *fdt, int offset) |
185 | { |
186 | if ((offset < 0) || (offset % FDT_TAGSIZE) |
187 | || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE)) |
188 | return -FDT_ERR_BADOFFSET; |
189 | |
190 | return offset; |
191 | } |
192 | |
193 | int fdt_check_prop_offset_(const void *fdt, int offset) |
194 | { |
195 | if ((offset < 0) || (offset % FDT_TAGSIZE) |
196 | || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP)) |
197 | return -FDT_ERR_BADOFFSET; |
198 | |
199 | return offset; |
200 | } |
201 | |
202 | int fdt_next_node(const void *fdt, int offset, int *depth) |
203 | { |
204 | int nextoffset = 0; |
205 | uint32_t tag; |
206 | |
207 | if (offset >= 0) |
208 | if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) |
209 | return nextoffset; |
210 | |
211 | do { |
212 | offset = nextoffset; |
213 | tag = fdt_next_tag(fdt, offset, &nextoffset); |
214 | |
215 | switch (tag) { |
216 | case FDT_PROP: |
217 | case FDT_NOP: |
218 | break; |
219 | |
220 | case FDT_BEGIN_NODE: |
221 | if (depth) |
222 | (*depth)++; |
223 | break; |
224 | |
225 | case FDT_END_NODE: |
226 | if (depth && ((--(*depth)) < 0)) |
227 | return nextoffset; |
228 | break; |
229 | |
230 | case FDT_END: |
231 | if ((nextoffset >= 0) |
232 | || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) |
233 | return -FDT_ERR_NOTFOUND; |
234 | else |
235 | return nextoffset; |
236 | } |
237 | } while (tag != FDT_BEGIN_NODE); |
238 | |
239 | return offset; |
240 | } |
241 | |
242 | int fdt_first_subnode(const void *fdt, int offset) |
243 | { |
244 | int depth = 0; |
245 | |
246 | offset = fdt_next_node(fdt, offset, &depth); |
247 | if (offset < 0 || depth != 1) |
248 | return -FDT_ERR_NOTFOUND; |
249 | |
250 | return offset; |
251 | } |
252 | |
253 | int fdt_next_subnode(const void *fdt, int offset) |
254 | { |
255 | int depth = 1; |
256 | |
257 | /* |
258 | * With respect to the parent, the depth of the next subnode will be |
259 | * the same as the last. |
260 | */ |
261 | do { |
262 | offset = fdt_next_node(fdt, offset, &depth); |
263 | if (offset < 0 || depth < 1) |
264 | return -FDT_ERR_NOTFOUND; |
265 | } while (depth > 1); |
266 | |
267 | return offset; |
268 | } |
269 | |
270 | const char *fdt_find_string_(const char *strtab, int tabsize, const char *s) |
271 | { |
272 | int len = strlen(s) + 1; |
273 | const char *last = strtab + tabsize - len; |
274 | const char *p; |
275 | |
276 | for (p = strtab; p <= last; p++) |
277 | if (memcmp(p, s, len) == 0) |
278 | return p; |
279 | return NULL; |
280 | } |
281 | |
282 | int fdt_move(const void *fdt, void *buf, int bufsize) |
283 | { |
284 | FDT_RO_PROBE(fdt); |
285 | |
286 | if (fdt_totalsize(fdt) > bufsize) |
287 | return -FDT_ERR_NOSPACE; |
288 | |
289 | memmove(buf, fdt, fdt_totalsize(fdt)); |
290 | return 0; |
291 | } |
292 | |