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