| 1 | |
| 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 (!can_assume(VALID_INPUT)) {
|
| 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 ((err = fdt_sw_probe_(fdt)) != 0) \
|
| 29 | return err; \
|
| 30 | }
|
| 31 |
|
| 32 | |
| 33 | *
|
| 34 | * Allowed functions:
|
| 35 | * fdt_add_reservemap_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 (!can_assume(VALID_INPUT) && 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 ((err = fdt_sw_probe_memrsv_(fdt)) != 0) \
|
| 53 | return err; \
|
| 54 | }
|
| 55 |
|
| 56 | |
| 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)
|
| 65 | {
|
| 66 | int err = fdt_sw_probe_(fdt);
|
| 67 | if (err)
|
| 68 | return err;
|
| 69 |
|
| 70 | if (!can_assume(VALID_INPUT) &&
|
| 71 | fdt_off_dt_strings(fdt) != fdt_totalsize(fdt))
|
| 72 | return -FDT_ERR_BADSTATE;
|
| 73 | return 0;
|
| 74 | }
|
| 75 |
|
| 76 | #define FDT_SW_PROBE_STRUCT(fdt) \
|
| 77 | { \
|
| 78 | int err; \
|
| 79 | if ((err = fdt_sw_probe_struct_(fdt)) != 0) \
|
| 80 | return err; \
|
| 81 | }
|
| 82 |
|
| 83 | static inline uint32_t sw_flags(void *fdt)
|
| 84 | {
|
| 85 |
|
| 86 | return fdt_last_comp_version(fdt);
|
| 87 | }
|
| 88 |
|
| 89 | |
| 90 | *
|
| 91 | * Allowed functions: none
|
| 92 |
|
| 93 |
|
| 94 | static void *fdt_grab_space_(void *fdt, size_t len)
|
| 95 | {
|
| 96 | unsigned int offset = fdt_size_dt_struct(fdt);
|
| 97 | unsigned int spaceleft;
|
| 98 |
|
| 99 | spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt)
|
| 100 | - fdt_size_dt_strings(fdt);
|
| 101 |
|
| 102 | if ((offset + len < offset) || (offset + len > spaceleft))
|
| 103 | return NULL;
|
| 104 |
|
| 105 | fdt_set_size_dt_struct(fdt, offset + len);
|
| 106 | return fdt_offset_ptr_w_(fdt, offset);
|
| 107 | }
|
| 108 |
|
| 109 | int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags)
|
| 110 | {
|
| 111 | const int hdrsize = FDT_ALIGN(sizeof(struct fdt_header),
|
| 112 | sizeof(struct fdt_reserve_entry));
|
| 113 | void *fdt = buf;
|
| 114 |
|
| 115 | if (bufsize < hdrsize)
|
| 116 | return -FDT_ERR_NOSPACE;
|
| 117 |
|
| 118 | if (flags & ~FDT_CREATE_FLAGS_ALL)
|
| 119 | return -FDT_ERR_BADFLAGS;
|
| 120 |
|
| 121 | memset(buf, 0, bufsize);
|
| 122 |
|
| 123 | |
| 124 | * magic and last_comp_version keep intermediate state during the fdt
|
| 125 | * creation process, which is replaced with the proper FDT format by
|
| 126 | * fdt_finish().
|
| 127 | *
|
| 128 | * flags should be accessed with sw_flags().
|
| 129 |
|
| 130 | fdt_set_magic(fdt, FDT_SW_MAGIC);
|
| 131 | fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION);
|
| 132 | fdt_set_last_comp_version(fdt, flags);
|
| 133 |
|
| 134 | fdt_set_totalsize(fdt, bufsize);
|
| 135 |
|
| 136 | fdt_set_off_mem_rsvmap(fdt, hdrsize);
|
| 137 | fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt));
|
| 138 | fdt_set_off_dt_strings(fdt, 0);
|
| 139 |
|
| 140 | return 0;
|
| 141 | }
|
| 142 |
|
| 143 | int fdt_create(void *buf, int bufsize)
|
| 144 | {
|
| 145 | return fdt_create_with_flags(buf, bufsize, 0);
|
| 146 | }
|
| 147 |
|
| 148 | int fdt_resize(void *fdt, void *buf, int bufsize)
|
| 149 | {
|
| 150 | size_t headsize, tailsize;
|
| 151 | char *oldtail, *newtail;
|
| 152 |
|
| 153 | FDT_SW_PROBE(fdt);
|
| 154 |
|
| 155 | if (bufsize < 0)
|
| 156 | return -FDT_ERR_NOSPACE;
|
| 157 |
|
| 158 | headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt);
|
| 159 | tailsize = fdt_size_dt_strings(fdt);
|
| 160 |
|
| 161 | if (!can_assume(VALID_DTB) &&
|
| 162 | headsize + tailsize > fdt_totalsize(fdt))
|
| 163 | return -FDT_ERR_INTERNAL;
|
| 164 |
|
| 165 | if ((headsize + tailsize) > (unsigned)bufsize)
|
| 166 | return -FDT_ERR_NOSPACE;
|
| 167 |
|
| 168 | oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize;
|
| 169 | newtail = (char *)buf + bufsize - tailsize;
|
| 170 |
|
| 171 | |
| 172 | * buffers partially overlap
|
| 173 | if (buf <= fdt) {
|
| 174 | memmove(buf, fdt, headsize);
|
| 175 | memmove(newtail, oldtail, tailsize);
|
| 176 | } else {
|
| 177 | memmove(newtail, oldtail, tailsize);
|
| 178 | memmove(buf, fdt, headsize);
|
| 179 | }
|
| 180 |
|
| 181 | fdt_set_totalsize(buf, bufsize);
|
| 182 | if (fdt_off_dt_strings(buf))
|
| 183 | fdt_set_off_dt_strings(buf, bufsize);
|
| 184 |
|
| 185 | return 0;
|
| 186 | }
|
| 187 |
|
| 188 | int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size)
|
| 189 | {
|
| 190 | struct fdt_reserve_entry *re;
|
| 191 | int offset;
|
| 192 |
|
| 193 | FDT_SW_PROBE_MEMRSV(fdt);
|
| 194 |
|
| 195 | offset = fdt_off_dt_struct(fdt);
|
| 196 | if ((offset + sizeof(*re)) > fdt_totalsize(fdt))
|
| 197 | return -FDT_ERR_NOSPACE;
|
| 198 |
|
| 199 | re = (struct fdt_reserve_entry *)((char *)fdt + offset);
|
| 200 | re->address = cpu_to_fdt64(addr);
|
| 201 | re->size = cpu_to_fdt64(size);
|
| 202 |
|
| 203 | fdt_set_off_dt_struct(fdt, offset + sizeof(*re));
|
| 204 |
|
| 205 | return 0;
|
| 206 | }
|
| 207 |
|
| 208 | int fdt_finish_reservemap(void *fdt)
|
| 209 | {
|
| 210 | int err = fdt_add_reservemap_entry(fdt, 0, 0);
|
| 211 |
|
| 212 | if (err)
|
| 213 | return err;
|
| 214 |
|
| 215 | fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt));
|
| 216 | return 0;
|
| 217 | }
|
| 218 |
|
| 219 | int fdt_begin_node(void *fdt, const char *name)
|
| 220 | {
|
| 221 | struct fdt_node_header *nh;
|
| 222 | int namelen;
|
| 223 |
|
| 224 | FDT_SW_PROBE_STRUCT(fdt);
|
| 225 |
|
| 226 | namelen = strlen(name) + 1;
|
| 227 | nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen));
|
| 228 | if (! nh)
|
| 229 | return -FDT_ERR_NOSPACE;
|
| 230 |
|
| 231 | nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE);
|
| 232 | memcpy(nh->name, name, namelen);
|
| 233 | return 0;
|
| 234 | }
|
| 235 |
|
| 236 | int fdt_end_node(void *fdt)
|
| 237 | {
|
| 238 | fdt32_t *en;
|
| 239 |
|
| 240 | FDT_SW_PROBE_STRUCT(fdt);
|
| 241 |
|
| 242 | en = fdt_grab_space_(fdt, FDT_TAGSIZE);
|
| 243 | if (! en)
|
| 244 | return -FDT_ERR_NOSPACE;
|
| 245 |
|
| 246 | *en = cpu_to_fdt32(FDT_END_NODE);
|
| 247 | return 0;
|
| 248 | }
|
| 249 |
|
| 250 | static int fdt_add_string_(void *fdt, const char *s)
|
| 251 | {
|
| 252 | char *strtab = (char *)fdt + fdt_totalsize(fdt);
|
| 253 | unsigned int strtabsize = fdt_size_dt_strings(fdt);
|
| 254 | unsigned int len = strlen(s) + 1;
|
| 255 | unsigned int struct_top, offset;
|
| 256 |
|
| 257 | offset = strtabsize + len;
|
| 258 | struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt);
|
| 259 | if (fdt_totalsize(fdt) - offset < struct_top)
|
| 260 | return 0;
|
| 261 |
|
| 262 | memcpy(strtab - offset, s, len);
|
| 263 | fdt_set_size_dt_strings(fdt, strtabsize + len);
|
| 264 | return -offset;
|
| 265 | }
|
| 266 |
|
| 267 |
|
| 268 | static void fdt_del_last_string_(void *fdt, const char *s)
|
| 269 | {
|
| 270 | int strtabsize = fdt_size_dt_strings(fdt);
|
| 271 | int len = strlen(s) + 1;
|
| 272 |
|
| 273 | fdt_set_size_dt_strings(fdt, strtabsize - len);
|
| 274 | }
|
| 275 |
|
| 276 | static int fdt_find_add_string_(void *fdt, const char *s, int *allocated)
|
| 277 | {
|
| 278 | char *strtab = (char *)fdt + fdt_totalsize(fdt);
|
| 279 | int strtabsize = fdt_size_dt_strings(fdt);
|
| 280 | const char *p;
|
| 281 |
|
| 282 | *allocated = 0;
|
| 283 |
|
| 284 | p = fdt_find_string_(strtab - strtabsize, strtabsize, s);
|
| 285 | if (p)
|
| 286 | return p - strtab;
|
| 287 |
|
| 288 | *allocated = 1;
|
| 289 |
|
| 290 | return fdt_add_string_(fdt, s);
|
| 291 | }
|
| 292 |
|
| 293 | int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp)
|
| 294 | {
|
| 295 | struct fdt_property *prop;
|
| 296 | int nameoff;
|
| 297 | int allocated;
|
| 298 |
|
| 299 | FDT_SW_PROBE_STRUCT(fdt);
|
| 300 |
|
| 301 |
|
| 302 | if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) {
|
| 303 | allocated = 1;
|
| 304 | nameoff = fdt_add_string_(fdt, name);
|
| 305 | } else {
|
| 306 | nameoff = fdt_find_add_string_(fdt, name, &allocated);
|
| 307 | }
|
| 308 | if (nameoff == 0)
|
| 309 | return -FDT_ERR_NOSPACE;
|
| 310 |
|
| 311 | prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len));
|
| 312 | if (! prop) {
|
| 313 | if (allocated)
|
| 314 | fdt_del_last_string_(fdt, name);
|
| 315 | return -FDT_ERR_NOSPACE;
|
| 316 | }
|
| 317 |
|
| 318 | prop->tag = cpu_to_fdt32(FDT_PROP);
|
| 319 | prop->nameoff = cpu_to_fdt32(nameoff);
|
| 320 | prop->len = cpu_to_fdt32(len);
|
| 321 | *valp = prop->data;
|
| 322 | return 0;
|
| 323 | }
|
| 324 |
|
| 325 | int fdt_property(void *fdt, const char *name, const void *val, int len)
|
| 326 | {
|
| 327 | void *ptr;
|
| 328 | int ret;
|
| 329 |
|
| 330 | ret = fdt_property_placeholder(fdt, name, len, &ptr);
|
| 331 | if (ret)
|
| 332 | return ret;
|
| 333 | memcpy(ptr, val, len);
|
| 334 | return 0;
|
| 335 | }
|
| 336 |
|
| 337 | int fdt_finish(void *fdt)
|
| 338 | {
|
| 339 | char *p = (char *)fdt;
|
| 340 | fdt32_t *end;
|
| 341 | int oldstroffset, newstroffset;
|
| 342 | uint32_t tag;
|
| 343 | int offset, nextoffset;
|
| 344 |
|
| 345 | FDT_SW_PROBE_STRUCT(fdt);
|
| 346 |
|
| 347 |
|
| 348 | end = fdt_grab_space_(fdt, sizeof(*end));
|
| 349 | if (! end)
|
| 350 | return -FDT_ERR_NOSPACE;
|
| 351 | *end = cpu_to_fdt32(FDT_END);
|
| 352 |
|
| 353 |
|
| 354 | oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt);
|
| 355 | newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt);
|
| 356 | memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt));
|
| 357 | fdt_set_off_dt_strings(fdt, newstroffset);
|
| 358 |
|
| 359 |
|
| 360 | offset = 0;
|
| 361 | while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) {
|
| 362 | if (tag == FDT_PROP) {
|
| 363 | struct fdt_property *prop =
|
| 364 | fdt_offset_ptr_w_(fdt, offset);
|
| 365 | int nameoff;
|
| 366 |
|
| 367 | nameoff = fdt32_to_cpu(prop->nameoff);
|
| 368 | nameoff += fdt_size_dt_strings(fdt);
|
| 369 | prop->nameoff = cpu_to_fdt32(nameoff);
|
| 370 | }
|
| 371 | offset = nextoffset;
|
| 372 | }
|
| 373 | if (nextoffset < 0)
|
| 374 | return nextoffset;
|
| 375 |
|
| 376 |
|
| 377 | fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt));
|
| 378 |
|
| 379 |
|
| 380 | fdt_set_last_comp_version(fdt, FDT_LAST_COMPATIBLE_VERSION);
|
| 381 | fdt_set_magic(fdt, FDT_MAGIC);
|
| 382 |
|
| 383 | return 0;
|
| 384 | }
|