]>
Commit | Line | Data |
---|---|---|
f0921f50 | 1 | // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) |
c0e032e0 TR |
2 | /* |
3 | * libfdt - Flat Device Tree manipulation | |
4 | * Copyright (C) 2006 David Gibson, IBM Corporation. | |
c0e032e0 TR |
5 | */ |
6 | #include "libfdt_env.h" | |
7 | ||
8 | #include <fdt.h> | |
9 | #include <libfdt.h> | |
10 | ||
11 | #include "libfdt_internal.h" | |
12 | ||
f0921f50 SG |
13 | static int fdt_sw_probe_(void *fdt) |
14 | { | |
15 | if (fdt_chk_basic()) { | |
16 | if (fdt_magic(fdt) == FDT_MAGIC) | |
17 | return -FDT_ERR_BADSTATE; | |
18 | else if (fdt_magic(fdt) != FDT_SW_MAGIC) | |
19 | return -FDT_ERR_BADMAGIC; | |
20 | } | |
21 | ||
22 | return 0; | |
23 | } | |
24 | ||
25 | #define FDT_SW_PROBE(fdt) \ | |
26 | { \ | |
27 | int err; \ | |
28 | if (fdt_chk_basic() && (err = fdt_sw_probe_(fdt)) != 0) \ | |
29 | return err; \ | |
30 | } | |
31 | ||
32 | /* 'memrsv' state: Initial state after fdt_create() | |
33 | * | |
34 | * Allowed functions: | |
35 | * fdt_add_reservmap_entry() | |
36 | * fdt_finish_reservemap() [moves to 'struct' state] | |
37 | */ | |
38 | static int fdt_sw_probe_memrsv_(void *fdt) | |
39 | { | |
40 | int err = fdt_sw_probe_(fdt); | |
41 | if (err) | |
42 | return err; | |
43 | ||
44 | if (fdt_chk_extra() && fdt_off_dt_strings(fdt) != 0) | |
45 | return -FDT_ERR_BADSTATE; | |
46 | return 0; | |
47 | } | |
48 | ||
49 | #define FDT_SW_PROBE_MEMRSV(fdt) \ | |
50 | { \ | |
51 | int err; \ | |
52 | if (fdt_chk_extra() && (err = fdt_sw_probe_memrsv_(fdt)) != 0) \ | |
53 | return err; \ | |
54 | } | |
55 | ||
56 | /* 'struct' state: Enter this state after fdt_finish_reservemap() | |
57 | * | |
58 | * Allowed functions: | |
59 | * fdt_begin_node() | |
60 | * fdt_end_node() | |
61 | * fdt_property*() | |
62 | * fdt_finish() [moves to 'complete' state] | |
63 | */ | |
64 | static int fdt_sw_probe_struct_(void *fdt) | |
c0e032e0 | 65 | { |
f0921f50 SG |
66 | int err; |
67 | ||
68 | if (!fdt_chk_extra()) | |
69 | return 0; | |
70 | err = fdt_sw_probe_(fdt); | |
71 | if (err) | |
72 | return err; | |
73 | ||
74 | if (fdt_off_dt_strings(fdt) != fdt_totalsize(fdt)) | |
75 | return -FDT_ERR_BADSTATE; | |
c0e032e0 TR |
76 | return 0; |
77 | } | |
78 | ||
f0921f50 | 79 | #define FDT_SW_PROBE_STRUCT(fdt) \ |
c0e032e0 TR |
80 | { \ |
81 | int err; \ | |
f0921f50 | 82 | if (fdt_chk_extra() && (err = fdt_sw_probe_struct_(fdt)) != 0) \ |
c0e032e0 TR |
83 | return err; \ |
84 | } | |
85 | ||
f0921f50 SG |
86 | static inline uint32_t sw_flags(void *fdt) |
87 | { | |
88 | /* assert: (fdt_magic(fdt) == FDT_SW_MAGIC) */ | |
89 | return fdt_last_comp_version(fdt); | |
90 | } | |
91 | ||
92 | /* 'complete' state: Enter this state after fdt_finish() | |
93 | * | |
94 | * Allowed functions: none | |
95 | */ | |
96 | ||
db405d19 | 97 | static void *fdt_grab_space_(void *fdt, size_t len) |
c0e032e0 | 98 | { |
832bfad7 AP |
99 | unsigned int offset = fdt_size_dt_struct(fdt); |
100 | unsigned int spaceleft; | |
c0e032e0 TR |
101 | |
102 | spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt) | |
103 | - fdt_size_dt_strings(fdt); | |
104 | ||
105 | if ((offset + len < offset) || (offset + len > spaceleft)) | |
106 | return NULL; | |
107 | ||
108 | fdt_set_size_dt_struct(fdt, offset + len); | |
db405d19 | 109 | return fdt_offset_ptr_w_(fdt, offset); |
c0e032e0 TR |
110 | } |
111 | ||
f0921f50 | 112 | int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) |
c0e032e0 | 113 | { |
832bfad7 AP |
114 | const int hdrsize = FDT_ALIGN(sizeof(struct fdt_header), |
115 | sizeof(struct fdt_reserve_entry)); | |
c0e032e0 TR |
116 | void *fdt = buf; |
117 | ||
f0921f50 | 118 | if (bufsize < hdrsize) |
c0e032e0 TR |
119 | return -FDT_ERR_NOSPACE; |
120 | ||
f0921f50 SG |
121 | if (flags & ~FDT_CREATE_FLAGS_ALL) |
122 | return -FDT_ERR_BADFLAGS; | |
123 | ||
c0e032e0 TR |
124 | memset(buf, 0, bufsize); |
125 | ||
f0921f50 SG |
126 | /* |
127 | * magic and last_comp_version keep intermediate state during the fdt | |
128 | * creation process, which is replaced with the proper FDT format by | |
129 | * fdt_finish(). | |
130 | * | |
131 | * flags should be accessed with sw_flags(). | |
132 | */ | |
c0e032e0 TR |
133 | fdt_set_magic(fdt, FDT_SW_MAGIC); |
134 | fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); | |
f0921f50 SG |
135 | fdt_set_last_comp_version(fdt, flags); |
136 | ||
c0e032e0 TR |
137 | fdt_set_totalsize(fdt, bufsize); |
138 | ||
f0921f50 | 139 | fdt_set_off_mem_rsvmap(fdt, hdrsize); |
c0e032e0 | 140 | fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); |
f0921f50 | 141 | fdt_set_off_dt_strings(fdt, 0); |
c0e032e0 TR |
142 | |
143 | return 0; | |
144 | } | |
145 | ||
f0921f50 SG |
146 | int fdt_create(void *buf, int bufsize) |
147 | { | |
148 | return fdt_create_with_flags(buf, bufsize, 0); | |
149 | } | |
150 | ||
c0e032e0 TR |
151 | int fdt_resize(void *fdt, void *buf, int bufsize) |
152 | { | |
153 | size_t headsize, tailsize; | |
154 | char *oldtail, *newtail; | |
155 | ||
f0921f50 | 156 | FDT_SW_PROBE(fdt); |
c0e032e0 | 157 | |
832bfad7 AP |
158 | if (bufsize < 0) |
159 | return -FDT_ERR_NOSPACE; | |
160 | ||
89d66907 | 161 | headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); |
c0e032e0 TR |
162 | tailsize = fdt_size_dt_strings(fdt); |
163 | ||
f0921f50 SG |
164 | if (fdt_chk_extra() && (headsize + tailsize) > fdt_totalsize(fdt)) |
165 | return -FDT_ERR_INTERNAL; | |
166 | ||
832bfad7 | 167 | if ((headsize + tailsize) > (unsigned)bufsize) |
c0e032e0 TR |
168 | return -FDT_ERR_NOSPACE; |
169 | ||
170 | oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize; | |
171 | newtail = (char *)buf + bufsize - tailsize; | |
172 | ||
173 | /* Two cases to avoid clobbering data if the old and new | |
174 | * buffers partially overlap */ | |
175 | if (buf <= fdt) { | |
176 | memmove(buf, fdt, headsize); | |
177 | memmove(newtail, oldtail, tailsize); | |
178 | } else { | |
179 | memmove(newtail, oldtail, tailsize); | |
180 | memmove(buf, fdt, headsize); | |
181 | } | |
182 | ||
c0e032e0 | 183 | fdt_set_totalsize(buf, bufsize); |
f0921f50 SG |
184 | if (fdt_off_dt_strings(buf)) |
185 | fdt_set_off_dt_strings(buf, bufsize); | |
c0e032e0 TR |
186 | |
187 | return 0; | |
188 | } | |
189 | ||
190 | int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) | |
191 | { | |
192 | struct fdt_reserve_entry *re; | |
193 | int offset; | |
194 | ||
f0921f50 | 195 | FDT_SW_PROBE_MEMRSV(fdt); |
c0e032e0 TR |
196 | |
197 | offset = fdt_off_dt_struct(fdt); | |
198 | if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) | |
199 | return -FDT_ERR_NOSPACE; | |
200 | ||
201 | re = (struct fdt_reserve_entry *)((char *)fdt + offset); | |
202 | re->address = cpu_to_fdt64(addr); | |
203 | re->size = cpu_to_fdt64(size); | |
204 | ||
205 | fdt_set_off_dt_struct(fdt, offset + sizeof(*re)); | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | int fdt_finish_reservemap(void *fdt) | |
211 | { | |
f0921f50 SG |
212 | int err = fdt_add_reservemap_entry(fdt, 0, 0); |
213 | ||
214 | if (err) | |
215 | return err; | |
216 | ||
217 | fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt)); | |
218 | return 0; | |
c0e032e0 TR |
219 | } |
220 | ||
221 | int fdt_begin_node(void *fdt, const char *name) | |
222 | { | |
223 | struct fdt_node_header *nh; | |
f0921f50 | 224 | int namelen; |
c0e032e0 | 225 | |
f0921f50 | 226 | FDT_SW_PROBE_STRUCT(fdt); |
c0e032e0 | 227 | |
f0921f50 | 228 | namelen = strlen(name) + 1; |
db405d19 | 229 | nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); |
c0e032e0 TR |
230 | if (! nh) |
231 | return -FDT_ERR_NOSPACE; | |
232 | ||
233 | nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); | |
234 | memcpy(nh->name, name, namelen); | |
235 | return 0; | |
236 | } | |
237 | ||
238 | int fdt_end_node(void *fdt) | |
239 | { | |
240 | fdt32_t *en; | |
241 | ||
f0921f50 | 242 | FDT_SW_PROBE_STRUCT(fdt); |
c0e032e0 | 243 | |
db405d19 | 244 | en = fdt_grab_space_(fdt, FDT_TAGSIZE); |
c0e032e0 TR |
245 | if (! en) |
246 | return -FDT_ERR_NOSPACE; | |
247 | ||
248 | *en = cpu_to_fdt32(FDT_END_NODE); | |
249 | return 0; | |
250 | } | |
251 | ||
f0921f50 | 252 | static int fdt_add_string_(void *fdt, const char *s) |
c0e032e0 TR |
253 | { |
254 | char *strtab = (char *)fdt + fdt_totalsize(fdt); | |
832bfad7 AP |
255 | unsigned int strtabsize = fdt_size_dt_strings(fdt); |
256 | unsigned int len = strlen(s) + 1; | |
257 | unsigned int struct_top, offset; | |
c0e032e0 | 258 | |
832bfad7 | 259 | offset = strtabsize + len; |
c0e032e0 | 260 | struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); |
832bfad7 | 261 | if (fdt_totalsize(fdt) - offset < struct_top) |
c0e032e0 TR |
262 | return 0; /* no more room :( */ |
263 | ||
832bfad7 | 264 | memcpy(strtab - offset, s, len); |
c0e032e0 | 265 | fdt_set_size_dt_strings(fdt, strtabsize + len); |
832bfad7 | 266 | return -offset; |
c0e032e0 TR |
267 | } |
268 | ||
f0921f50 SG |
269 | /* Must only be used to roll back in case of error */ |
270 | static void fdt_del_last_string_(void *fdt, const char *s) | |
271 | { | |
272 | int strtabsize = fdt_size_dt_strings(fdt); | |
273 | int len = strlen(s) + 1; | |
274 | ||
275 | fdt_set_size_dt_strings(fdt, strtabsize - len); | |
276 | } | |
277 | ||
278 | static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) | |
279 | { | |
280 | char *strtab = (char *)fdt + fdt_totalsize(fdt); | |
281 | int strtabsize = fdt_size_dt_strings(fdt); | |
282 | const char *p; | |
283 | ||
284 | *allocated = 0; | |
285 | ||
286 | p = fdt_find_string_(strtab - strtabsize, strtabsize, s); | |
287 | if (p) | |
288 | return p - strtab; | |
289 | ||
290 | *allocated = 1; | |
291 | ||
292 | return fdt_add_string_(fdt, s); | |
293 | } | |
294 | ||
2d4c2259 | 295 | int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) |
c0e032e0 TR |
296 | { |
297 | struct fdt_property *prop; | |
298 | int nameoff; | |
f0921f50 | 299 | int allocated; |
c0e032e0 | 300 | |
f0921f50 | 301 | FDT_SW_PROBE_STRUCT(fdt); |
c0e032e0 | 302 | |
f0921f50 SG |
303 | /* String de-duplication can be slow, _NO_NAME_DEDUP skips it */ |
304 | if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) { | |
305 | allocated = 1; | |
306 | nameoff = fdt_add_string_(fdt, name); | |
307 | } else { | |
308 | nameoff = fdt_find_add_string_(fdt, name, &allocated); | |
309 | } | |
c0e032e0 TR |
310 | if (nameoff == 0) |
311 | return -FDT_ERR_NOSPACE; | |
312 | ||
db405d19 | 313 | prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); |
f0921f50 SG |
314 | if (! prop) { |
315 | if (allocated) | |
316 | fdt_del_last_string_(fdt, name); | |
c0e032e0 | 317 | return -FDT_ERR_NOSPACE; |
f0921f50 | 318 | } |
c0e032e0 TR |
319 | |
320 | prop->tag = cpu_to_fdt32(FDT_PROP); | |
321 | prop->nameoff = cpu_to_fdt32(nameoff); | |
322 | prop->len = cpu_to_fdt32(len); | |
2d4c2259 TR |
323 | *valp = prop->data; |
324 | return 0; | |
325 | } | |
326 | ||
327 | int fdt_property(void *fdt, const char *name, const void *val, int len) | |
328 | { | |
329 | void *ptr; | |
330 | int ret; | |
331 | ||
332 | ret = fdt_property_placeholder(fdt, name, len, &ptr); | |
333 | if (ret) | |
334 | return ret; | |
335 | memcpy(ptr, val, len); | |
c0e032e0 TR |
336 | return 0; |
337 | } | |
338 | ||
339 | int fdt_finish(void *fdt) | |
340 | { | |
341 | char *p = (char *)fdt; | |
342 | fdt32_t *end; | |
343 | int oldstroffset, newstroffset; | |
344 | uint32_t tag; | |
345 | int offset, nextoffset; | |
346 | ||
f0921f50 | 347 | FDT_SW_PROBE_STRUCT(fdt); |
c0e032e0 TR |
348 | |
349 | /* Add terminator */ | |
db405d19 | 350 | end = fdt_grab_space_(fdt, sizeof(*end)); |
c0e032e0 TR |
351 | if (! end) |
352 | return -FDT_ERR_NOSPACE; | |
353 | *end = cpu_to_fdt32(FDT_END); | |
354 | ||
355 | /* Relocate the string table */ | |
356 | oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt); | |
357 | newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); | |
358 | memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt)); | |
359 | fdt_set_off_dt_strings(fdt, newstroffset); | |
360 | ||
361 | /* Walk the structure, correcting string offsets */ | |
362 | offset = 0; | |
363 | while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) { | |
364 | if (tag == FDT_PROP) { | |
365 | struct fdt_property *prop = | |
db405d19 | 366 | fdt_offset_ptr_w_(fdt, offset); |
c0e032e0 TR |
367 | int nameoff; |
368 | ||
369 | nameoff = fdt32_to_cpu(prop->nameoff); | |
370 | nameoff += fdt_size_dt_strings(fdt); | |
371 | prop->nameoff = cpu_to_fdt32(nameoff); | |
372 | } | |
373 | offset = nextoffset; | |
374 | } | |
375 | if (nextoffset < 0) | |
376 | return nextoffset; | |
377 | ||
378 | /* Finally, adjust the header */ | |
379 | fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); | |
f0921f50 SG |
380 | |
381 | /* And fix up fields that were keeping intermediate state. */ | |
382 | fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); | |
c0e032e0 | 383 | fdt_set_magic(fdt, FDT_MAGIC); |
f0921f50 | 384 | |
c0e032e0 TR |
385 | return 0; |
386 | } |