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 static int fdt_sw_probe_(void *fdt) 14 { 15 if (fdt_magic(fdt) == FDT_MAGIC) 16 return -FDT_ERR_BADSTATE; 17 else if (fdt_magic(fdt) != FDT_SW_MAGIC) 18 return -FDT_ERR_BADMAGIC; 19 return 0; 20 } 21 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) \ 73 { \ 74 int err; \ 75 if ((err = fdt_sw_probe_struct_(fdt)) != 0) \ 76 return err; \ 77 } 78 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 85 /* 'complete' state: Enter this state after fdt_finish() 86 * 87 * Allowed functions: none 88 */ 89 90 static void *fdt_grab_space_(void *fdt, size_t len) 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); 102 return fdt_offset_ptr_w_(fdt, offset); 103 } 104 105 int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) 106 { 107 const size_t hdrsize = FDT_ALIGN(sizeof(struct fdt_header), 108 sizeof(struct fdt_reserve_entry)); 109 void *fdt = buf; 110 111 if (bufsize < hdrsize) 112 return -FDT_ERR_NOSPACE; 113 114 if (flags & ~FDT_CREATE_FLAGS_ALL) 115 return -FDT_ERR_BADFLAGS; 116 117 memset(buf, 0, bufsize); 118 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 */ 126 fdt_set_magic(fdt, FDT_SW_MAGIC); 127 fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); 128 fdt_set_last_comp_version(fdt, flags); 129 130 fdt_set_totalsize(fdt, bufsize); 131 132 fdt_set_off_mem_rsvmap(fdt, hdrsize); 133 fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); 134 fdt_set_off_dt_strings(fdt, 0); 135 136 return 0; 137 } 138 139 int fdt_create(void *buf, int bufsize) 140 { 141 return fdt_create_with_flags(buf, bufsize, 0); 142 } 143 144 int fdt_resize(void *fdt, void *buf, int bufsize) 145 { 146 size_t headsize, tailsize; 147 char *oldtail, *newtail; 148 149 FDT_SW_PROBE(fdt); 150 151 headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); 152 tailsize = fdt_size_dt_strings(fdt); 153 154 if ((headsize + tailsize) > fdt_totalsize(fdt)) 155 return -FDT_ERR_INTERNAL; 156 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 173 fdt_set_totalsize(buf, bufsize); 174 if (fdt_off_dt_strings(buf)) 175 fdt_set_off_dt_strings(buf, bufsize); 176 177 return 0; 178 } 179 180 int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) 181 { 182 struct fdt_reserve_entry *re; 183 int offset; 184 185 FDT_SW_PROBE_MEMRSV(fdt); 186 187 offset = fdt_off_dt_struct(fdt); 188 if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) 189 return -FDT_ERR_NOSPACE; 190 191 re = (struct fdt_reserve_entry *)((char *)fdt + offset); 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 { 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; 209 } 210 211 int fdt_begin_node(void *fdt, const char *name) 212 { 213 struct fdt_node_header *nh; 214 int namelen; 215 216 FDT_SW_PROBE_STRUCT(fdt); 217 218 namelen = strlen(name) + 1; 219 nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); 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 { 230 fdt32_t *en; 231 232 FDT_SW_PROBE_STRUCT(fdt); 233 234 en = fdt_grab_space_(fdt, FDT_TAGSIZE); 235 if (! en) 236 return -FDT_ERR_NOSPACE; 237 238 *en = cpu_to_fdt32(FDT_END_NODE); 239 return 0; 240 } 241 242 static int fdt_add_string_(void *fdt, const char *s) 243 { 244 char *strtab = (char *)fdt + fdt_totalsize(fdt); 245 int strtabsize = fdt_size_dt_strings(fdt); 246 int len = strlen(s) + 1; 247 int struct_top, offset; 248 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 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 285 int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) 286 { 287 struct fdt_property *prop; 288 int nameoff; 289 int allocated; 290 291 FDT_SW_PROBE_STRUCT(fdt); 292 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 } 300 if (nameoff == 0) 301 return -FDT_ERR_NOSPACE; 302 303 prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); 304 if (! prop) { 305 if (allocated) 306 fdt_del_last_string_(fdt, name); 307 return -FDT_ERR_NOSPACE; 308 } 309 310 prop->tag = cpu_to_fdt32(FDT_PROP); 311 prop->nameoff = cpu_to_fdt32(nameoff); 312 prop->len = cpu_to_fdt32(len); 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); 326 return 0; 327 } 328 329 int fdt_finish(void *fdt) 330 { 331 char *p = (char *)fdt; 332 fdt32_t *end; 333 int oldstroffset, newstroffset; 334 uint32_t tag; 335 int offset, nextoffset; 336 337 FDT_SW_PROBE_STRUCT(fdt); 338 339 /* Add terminator */ 340 end = fdt_grab_space_(fdt, sizeof(*end)); 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 = 356 fdt_offset_ptr_w_(fdt, offset); 357 int nameoff; 358 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 } 365 if (nextoffset < 0) 366 return nextoffset; 367 368 /* Finally, adjust the header */ 369 fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); 370 371 /* And fix up fields that were keeping intermediate state. */ 372 fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); 373 fdt_set_magic(fdt, FDT_MAGIC); 374 375 return 0; 376 } 377