2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2013 Free Software Foundation, Inc.
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/misc.h>
24 GRUB_MOD_LICENSE ("GPLv3+");
26 #define FDT_SUPPORTED_VERSION 17
28 #define FDT_BEGIN_NODE 0x00000001
29 #define FDT_END_NODE 0x00000002
30 #define FDT_PROP 0x00000003
31 #define FDT_NOP 0x00000004
32 #define FDT_END 0x00000009
34 #define struct_end(fdt) \
35 ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt) \
36 + grub_fdt_get_size_dt_struct(fdt))
38 /* Size needed by a node entry: 2 tokens (FDT_BEGIN_NODE and FDT_END_NODE), plus
39 the NULL-terminated string containing the name, plus padding if needed. */
40 #define node_entry_size(node_name) \
41 (2 * sizeof(grub_uint32_t) \
42 + ALIGN_UP (grub_strlen (name) + 1, sizeof(grub_uint32_t)))
44 /* Size needed by a property entry: 1 token (FDT_PROPERTY), plus len and nameoff
45 fields, plus the property value, plus padding if needed. */
46 #define prop_entry_size(prop_len) \
47 (3 * sizeof(grub_uint32_t) + ALIGN_UP(prop_len, sizeof(grub_uint32_t)))
49 #define SKIP_NODE_NAME(name, token, end) \
50 name = (char *) ((token) + 1); \
51 while (name < (char *) end) \
56 token = (grub_uint32_t *) ALIGN_UP((grub_addr_t) (name), sizeof(*token))
59 static grub_uint32_t *get_next_node (const void *fdt, char *node_name)
61 grub_uint32_t *end = (void *) struct_end (fdt);
64 if (node_name >= (char *) end)
68 if (node_name >= (char *) end)
71 token = (grub_uint32_t *) ALIGN_UP ((grub_addr_t) node_name, 4);
74 switch (grub_be_to_cpu32(*token))
77 token = get_next_node (fdt, (char *) (token + 1));
87 /* Skip property token and following data (len, nameoff and property
89 token += prop_entry_size(grub_be_to_cpu32(*(token + 1)))
102 static int get_mem_rsvmap_size (const void *fdt)
105 grub_unaligned_uint64_t *ptr = (void *) ((grub_addr_t) fdt
106 + grub_fdt_get_off_mem_rsvmap (fdt));
110 size += 2 * sizeof(*ptr);
111 if (!ptr[0].val && !ptr[1].val)
114 } while ((grub_addr_t) ptr <= (grub_addr_t) fdt + grub_fdt_get_totalsize (fdt)
115 - 2 * sizeof(grub_uint64_t));
119 static grub_uint32_t get_free_space (void *fdt)
121 int mem_rsvmap_size = get_mem_rsvmap_size (fdt);
123 if (mem_rsvmap_size < 0)
124 /* invalid memory reservation block */
126 return (grub_fdt_get_totalsize (fdt) - sizeof(grub_fdt_header_t)
127 - mem_rsvmap_size - grub_fdt_get_size_dt_strings (fdt)
128 - grub_fdt_get_size_dt_struct (fdt));
131 static int add_subnode (void *fdt, int parentoffset, const char *name)
133 grub_uint32_t *token = (void *) ((grub_addr_t) fdt
134 + grub_fdt_get_off_dt_struct(fdt)
136 grub_uint32_t *end = (void *) struct_end (fdt);
137 unsigned int entry_size = node_entry_size (name);
138 unsigned int struct_size = grub_fdt_get_size_dt_struct(fdt);
141 SKIP_NODE_NAME(node_name, token, end);
143 /* Insert the new subnode just after the properties of the parent node (if
149 switch (grub_be_to_cpu32(*token))
152 /* Skip len, nameoff and property value. */
153 token += prop_entry_size(grub_be_to_cpu32(*(token + 1)))
168 grub_memmove (token + entry_size / sizeof(*token), token,
169 (grub_addr_t) end - (grub_addr_t) token);
170 *token = grub_cpu_to_be32_compile_time(FDT_BEGIN_NODE);
171 token[entry_size / sizeof(*token) - 2] = 0; /* padding bytes */
172 grub_strcpy((char *) (token + 1), name);
173 token[entry_size / sizeof(*token) - 1] = grub_cpu_to_be32_compile_time(FDT_END_NODE);
174 grub_fdt_set_size_dt_struct (fdt, struct_size + entry_size);
175 return ((grub_addr_t) token - (grub_addr_t) fdt
176 - grub_fdt_get_off_dt_struct(fdt));
179 /* Rearrange FDT blocks in the canonical order: first the memory reservation
180 block (just after the FDT header), then the structure block and finally the
181 strings block. No free space is left between the first and the second block,
182 while the space between the second and the third block is given by the
183 clearance argument. */
184 static int rearrange_blocks (void *fdt, unsigned int clearance)
186 grub_uint32_t off_mem_rsvmap = ALIGN_UP(sizeof(grub_fdt_header_t), 8);
187 grub_uint32_t off_dt_struct = off_mem_rsvmap + get_mem_rsvmap_size (fdt);
188 grub_uint32_t off_dt_strings = off_dt_struct
189 + grub_fdt_get_size_dt_struct (fdt)
191 grub_uint8_t *fdt_ptr = fdt;
192 grub_uint8_t *tmp_fdt;
194 if ((grub_fdt_get_off_mem_rsvmap (fdt) == off_mem_rsvmap)
195 && (grub_fdt_get_off_dt_struct (fdt) == off_dt_struct))
197 /* No need to allocate memory for a temporary FDT, just move the strings
199 if (grub_fdt_get_off_dt_strings (fdt) != off_dt_strings)
201 grub_memmove(fdt_ptr + off_dt_strings,
202 fdt_ptr + grub_fdt_get_off_dt_strings (fdt),
203 grub_fdt_get_size_dt_strings (fdt));
204 grub_fdt_set_off_dt_strings (fdt, off_dt_strings);
208 tmp_fdt = grub_malloc (grub_fdt_get_totalsize (fdt));
211 grub_memcpy (tmp_fdt + off_mem_rsvmap,
212 fdt_ptr + grub_fdt_get_off_mem_rsvmap (fdt),
213 get_mem_rsvmap_size (fdt));
214 grub_fdt_set_off_mem_rsvmap (fdt, off_mem_rsvmap);
215 grub_memcpy (tmp_fdt + off_dt_struct,
216 fdt_ptr + grub_fdt_get_off_dt_struct (fdt),
217 grub_fdt_get_size_dt_struct (fdt));
218 grub_fdt_set_off_dt_struct (fdt, off_dt_struct);
219 grub_memcpy (tmp_fdt + off_dt_strings,
220 fdt_ptr + grub_fdt_get_off_dt_strings (fdt),
221 grub_fdt_get_size_dt_strings (fdt));
222 grub_fdt_set_off_dt_strings (fdt, off_dt_strings);
224 /* Copy reordered blocks back to fdt. */
225 grub_memcpy (fdt_ptr + off_mem_rsvmap, tmp_fdt + off_mem_rsvmap,
226 grub_fdt_get_totalsize (fdt) - off_mem_rsvmap);
232 static grub_uint32_t *find_prop (const void *fdt, unsigned int nodeoffset,
235 grub_uint32_t *prop = (void *) ((grub_addr_t) fdt
236 + grub_fdt_get_off_dt_struct (fdt)
238 grub_uint32_t *end = (void *) struct_end(fdt);
239 grub_uint32_t nameoff;
242 SKIP_NODE_NAME(node_name, prop, end);
243 while (prop < end - 2)
245 if (grub_be_to_cpu32(*prop) == FDT_PROP)
247 nameoff = grub_be_to_cpu32(*(prop + 2));
248 if ((nameoff + grub_strlen (name) < grub_fdt_get_size_dt_strings (fdt))
249 && !grub_strcmp (name, (char *) fdt +
250 grub_fdt_get_off_dt_strings (fdt) + nameoff))
252 if (prop + prop_entry_size(grub_be_to_cpu32(*(prop + 1)))
253 / sizeof (*prop) >= end)
257 prop += prop_entry_size(grub_be_to_cpu32(*(prop + 1))) / sizeof (*prop);
259 else if (grub_be_to_cpu32(*prop) == FDT_NOP)
267 /* Check the FDT header for consistency and adjust the totalsize field to match
268 the size allocated for the FDT; if this function is called before the other
269 functions in this file and returns success, the other functions are
270 guaranteed not to access memory locations outside the allocated memory. */
271 int grub_fdt_check_header_nosize (const void *fdt)
273 if (((grub_addr_t) fdt & 0x3) || (grub_fdt_get_magic (fdt) != FDT_MAGIC)
274 || (grub_fdt_get_version (fdt) < FDT_SUPPORTED_VERSION)
275 || (grub_fdt_get_last_comp_version (fdt) > FDT_SUPPORTED_VERSION)
276 || (grub_fdt_get_off_dt_struct (fdt) & 0x00000003)
277 || (grub_fdt_get_size_dt_struct (fdt) & 0x00000003)
278 || (grub_fdt_get_off_dt_struct (fdt) + grub_fdt_get_size_dt_struct (fdt)
279 > grub_fdt_get_totalsize (fdt))
280 || (grub_fdt_get_off_dt_strings (fdt) + grub_fdt_get_size_dt_strings (fdt)
281 > grub_fdt_get_totalsize (fdt))
282 || (grub_fdt_get_off_mem_rsvmap (fdt) & 0x00000007)
283 || (grub_fdt_get_off_mem_rsvmap (fdt)
284 > grub_fdt_get_totalsize (fdt) - 2 * sizeof(grub_uint64_t)))
289 int grub_fdt_check_header (const void *fdt, unsigned int size)
291 if (size < sizeof (grub_fdt_header_t)
292 || (grub_fdt_get_totalsize (fdt) > size)
293 || grub_fdt_check_header_nosize (fdt) == -1)
298 static const grub_uint32_t *
299 advance_token (const void *fdt, const grub_uint32_t *token, const grub_uint32_t *end, int skip_current)
301 for (; token < end; skip_current = 0)
303 switch (grub_be_to_cpu32 (*token))
308 token = get_next_node (fdt, (char *) (token + 1));
312 for (ptr = (char *) (token + 1); *ptr && ptr < (char *) end; ptr++);
313 if (ptr >= (char *) end)
317 /* Skip property token and following data (len, nameoff and property
319 if (token >= end - 1)
321 token += prop_entry_size(grub_be_to_cpu32(*(token + 1)))
334 int grub_fdt_next_node (const void *fdt, unsigned int currentoffset)
336 const grub_uint32_t *token = (const grub_uint32_t *) fdt + (currentoffset + grub_fdt_get_off_dt_struct (fdt)) / 4;
337 token = advance_token (fdt, token, (const void *) struct_end (fdt), 1);
340 return (int) ((grub_addr_t) token - (grub_addr_t) fdt
341 - grub_fdt_get_off_dt_struct (fdt));
344 int grub_fdt_first_node (const void *fdt, unsigned int parentoffset)
346 const grub_uint32_t *token, *end;
349 if (parentoffset & 0x3)
351 token = (const void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
353 end = (const void *) struct_end (fdt);
354 if ((token >= end) || (grub_be_to_cpu32(*token) != FDT_BEGIN_NODE))
356 SKIP_NODE_NAME(node_name, token, end);
357 token = advance_token (fdt, token, end, 0);
360 return (int) ((grub_addr_t) token - (grub_addr_t) fdt
361 - grub_fdt_get_off_dt_struct (fdt));
364 /* Find a direct sub-node of a given parent node. */
365 int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset,
368 const grub_uint32_t *token, *end;
369 const char *node_name;
370 int skip_current = 0;
372 if (parentoffset & 0x3)
374 token = (const void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
376 end = (const void *) struct_end (fdt);
377 if ((token >= end) || (grub_be_to_cpu32(*token) != FDT_BEGIN_NODE))
379 SKIP_NODE_NAME(node_name, token, end);
381 token = advance_token (fdt, token, end, skip_current);
385 node_name = (const char *) token + 4;
386 if (grub_strcmp (node_name, name) == 0)
387 return (int) ((grub_addr_t) token - (grub_addr_t) fdt
388 - grub_fdt_get_off_dt_struct (fdt));
393 grub_fdt_get_nodename (const void *fdt, unsigned int nodeoffset)
395 return (const char *) fdt + grub_fdt_get_off_dt_struct(fdt) + nodeoffset + 4;
398 int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,
401 unsigned int entry_size = node_entry_size(name);
403 if ((parentoffset & 0x3) || (get_free_space (fdt) < entry_size))
406 /* The new node entry will increase the size of the structure block: rearrange
407 blocks such that there is sufficient free space between the structure and
408 the strings block, then add the new node entry. */
409 if (rearrange_blocks (fdt, entry_size) < 0)
411 return add_subnode (fdt, parentoffset, name);
415 grub_fdt_get_prop (const void *fdt, unsigned int nodeoffset, const char *name,
419 if ((nodeoffset >= grub_fdt_get_size_dt_struct (fdt)) || (nodeoffset & 0x3)
420 || (grub_be_to_cpu32(*(grub_uint32_t *) ((grub_addr_t) fdt
421 + grub_fdt_get_off_dt_struct (fdt) + nodeoffset))
424 prop = find_prop (fdt, nodeoffset, name);
428 *len = grub_be_to_cpu32 (*(prop + 1));
432 int grub_fdt_set_prop (void *fdt, unsigned int nodeoffset, const char *name,
433 const void *val, grub_uint32_t len)
436 int prop_name_present = 0;
437 grub_uint32_t nameoff = 0;
439 if ((nodeoffset >= grub_fdt_get_size_dt_struct (fdt)) || (nodeoffset & 0x3)
440 || (grub_be_to_cpu32(*(grub_uint32_t *) ((grub_addr_t) fdt
441 + grub_fdt_get_off_dt_struct (fdt) + nodeoffset))
444 prop = find_prop (fdt, nodeoffset, name);
447 grub_uint32_t prop_len = ALIGN_UP(grub_be_to_cpu32 (*(prop + 1)),
448 sizeof(grub_uint32_t));
451 prop_name_present = 1;
452 for (i = 0; i < prop_len / sizeof(grub_uint32_t); i++)
453 *(prop + 3 + i) = grub_cpu_to_be32_compile_time (FDT_NOP);
454 if (len > ALIGN_UP(prop_len, sizeof(grub_uint32_t)))
456 /* Length of new property value is greater than the space allocated
457 for the current value: a new entry needs to be created, so save the
458 nameoff field of the current entry and replace the current entry
460 nameoff = grub_be_to_cpu32 (*(prop + 2));
461 *prop = *(prop + 1) = *(prop + 2) = grub_cpu_to_be32_compile_time (FDT_NOP);
465 if (!prop || !prop_name_present) {
466 unsigned int needed_space = 0;
469 needed_space = prop_entry_size(len);
470 if (!prop_name_present)
471 needed_space += grub_strlen (name) + 1;
472 if (needed_space > get_free_space (fdt))
474 if (rearrange_blocks (fdt, !prop ? prop_entry_size(len) : 0) < 0)
477 if (!prop_name_present) {
478 /* Append the property name at the end of the strings block. */
479 nameoff = grub_fdt_get_size_dt_strings (fdt);
480 grub_strcpy ((char *) fdt + grub_fdt_get_off_dt_strings (fdt) + nameoff,
482 grub_fdt_set_size_dt_strings (fdt, grub_fdt_get_size_dt_strings (fdt)
483 + grub_strlen (name) + 1);
486 char *node_name = (char *) ((grub_addr_t) fdt
487 + grub_fdt_get_off_dt_struct (fdt) + nodeoffset
488 + sizeof(grub_uint32_t));
490 prop = (void *) (node_name + ALIGN_UP(grub_strlen(node_name) + 1, 4));
491 grub_memmove (prop + prop_entry_size(len) / sizeof(*prop), prop,
492 struct_end(fdt) - (grub_addr_t) prop);
493 grub_fdt_set_size_dt_struct (fdt, grub_fdt_get_size_dt_struct (fdt)
494 + prop_entry_size(len));
495 *prop = grub_cpu_to_be32_compile_time (FDT_PROP);
496 *(prop + 2) = grub_cpu_to_be32 (nameoff);
498 *(prop + 1) = grub_cpu_to_be32 (len);
500 /* Insert padding bytes at the end of the value; if they are not needed, they
501 will be overwritten by the following memcpy. */
502 *(prop + prop_entry_size(len) / sizeof(grub_uint32_t) - 1) = 0;
504 grub_memcpy (prop + 3, val, len);
509 grub_fdt_create_empty_tree (void *fdt, unsigned int size)
511 struct grub_fdt_empty_tree *et;
513 if (size < GRUB_FDT_EMPTY_TREE_SZ)
516 grub_memset (fdt, 0, size);
519 et->empty_node.tree_end = grub_cpu_to_be32_compile_time (FDT_END);
520 et->empty_node.node_end = grub_cpu_to_be32_compile_time (FDT_END_NODE);
521 et->empty_node.node_start = grub_cpu_to_be32_compile_time (FDT_BEGIN_NODE);
522 ((struct grub_fdt_empty_tree *) fdt)->header.off_mem_rsvmap =
523 grub_cpu_to_be32_compile_time (ALIGN_UP (sizeof (grub_fdt_header_t), 8));
525 grub_fdt_set_off_dt_strings (fdt, sizeof (*et));
526 grub_fdt_set_off_dt_struct (fdt,
527 sizeof (et->header) + sizeof (et->empty_rsvmap));
528 grub_fdt_set_version (fdt, FDT_SUPPORTED_VERSION);
529 grub_fdt_set_last_comp_version (fdt, FDT_SUPPORTED_VERSION);
530 grub_fdt_set_size_dt_struct (fdt, sizeof (et->empty_node));
531 grub_fdt_set_totalsize (fdt, size);
532 grub_fdt_set_magic (fdt, FDT_MAGIC);