123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2013 Free Software Foundation, Inc.
- *
- * GRUB is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <grub/fdt.h>
- #include <grub/misc.h>
- #include <grub/mm.h>
- #include <grub/dl.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- #define FDT_SUPPORTED_VERSION 17
- #define FDT_BEGIN_NODE 0x00000001
- #define FDT_END_NODE 0x00000002
- #define FDT_PROP 0x00000003
- #define FDT_NOP 0x00000004
- #define FDT_END 0x00000009
- #define struct_end(fdt) \
- ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt) \
- + grub_fdt_get_size_dt_struct(fdt))
- /* Size needed by a node entry: 2 tokens (FDT_BEGIN_NODE and FDT_END_NODE), plus
- the NULL-terminated string containing the name, plus padding if needed. */
- #define node_entry_size(node_name) \
- (2 * sizeof(grub_uint32_t) \
- + ALIGN_UP (grub_strlen (name) + 1, sizeof(grub_uint32_t)))
- #define SKIP_NODE_NAME(name, token, end) \
- name = (char *) ((token) + 1); \
- while (name < (char *) end) \
- { \
- if (!*name++) \
- break; \
- } \
- token = (grub_uint32_t *) ALIGN_UP((grub_addr_t) (name), sizeof(*token))
- static grub_uint32_t *get_next_node (const void *fdt, char *node_name)
- {
- grub_uint32_t *end = (void *) struct_end (fdt);
- grub_uint32_t *token;
- if (node_name >= (char *) end)
- return NULL;
- while (*node_name++)
- {
- if (node_name >= (char *) end)
- return NULL;
- }
- token = (grub_uint32_t *) ALIGN_UP ((grub_addr_t) node_name, 4);
- while (token < end)
- {
- switch (grub_be_to_cpu32(*token))
- {
- case FDT_BEGIN_NODE:
- token = get_next_node (fdt, (char *) (token + 1));
- if (!token)
- return NULL;
- break;
- case FDT_END_NODE:
- token++;
- if (token >= end)
- return NULL;
- return token;
- case FDT_PROP:
- /* Skip property token and following data (len, nameoff and property
- value). */
- token += grub_fdt_prop_entry_size(grub_be_to_cpu32(*(token + 1)))
- / sizeof(*token);
- break;
- case FDT_NOP:
- token++;
- break;
- default:
- return NULL;
- }
- }
- return NULL;
- }
- static int get_mem_rsvmap_size (const void *fdt)
- {
- int size = 0;
- grub_unaligned_uint64_t *ptr = (void *) ((grub_addr_t) fdt
- + grub_fdt_get_off_mem_rsvmap (fdt));
- do
- {
- size += 2 * sizeof(*ptr);
- if (!ptr[0].val && !ptr[1].val)
- return size;
- ptr += 2;
- } while ((grub_addr_t) ptr <= (grub_addr_t) fdt + grub_fdt_get_totalsize (fdt)
- - 2 * sizeof(grub_uint64_t));
- return -1;
- }
- static grub_uint32_t get_free_space (void *fdt)
- {
- int mem_rsvmap_size = get_mem_rsvmap_size (fdt);
- if (mem_rsvmap_size < 0)
- /* invalid memory reservation block */
- return 0;
- return (grub_fdt_get_totalsize (fdt) - sizeof(grub_fdt_header_t)
- - mem_rsvmap_size - grub_fdt_get_size_dt_strings (fdt)
- - grub_fdt_get_size_dt_struct (fdt));
- }
- static int add_subnode (void *fdt, int parentoffset, const char *name)
- {
- grub_uint32_t *token = (void *) ((grub_addr_t) fdt
- + grub_fdt_get_off_dt_struct(fdt)
- + parentoffset);
- grub_uint32_t *end = (void *) struct_end (fdt);
- unsigned int entry_size = node_entry_size (name);
- unsigned int struct_size = grub_fdt_get_size_dt_struct(fdt);
- char *node_name;
- SKIP_NODE_NAME(node_name, token, end);
- /* Insert the new subnode just after the properties of the parent node (if
- any).*/
- while (1)
- {
- if (token >= end)
- return -1;
- switch (grub_be_to_cpu32(*token))
- {
- case FDT_PROP:
- /* Skip len, nameoff and property value. */
- token += grub_fdt_prop_entry_size(grub_be_to_cpu32(*(token + 1)))
- / sizeof(*token);
- break;
- case FDT_BEGIN_NODE:
- case FDT_END_NODE:
- goto insert;
- case FDT_NOP:
- token++;
- break;
- default:
- /* invalid token */
- return -1;
- }
- }
- insert:
- grub_memmove (token + entry_size / sizeof(*token), token,
- (grub_addr_t) end - (grub_addr_t) token);
- *token = grub_cpu_to_be32_compile_time(FDT_BEGIN_NODE);
- token[entry_size / sizeof(*token) - 2] = 0; /* padding bytes */
- grub_strcpy((char *) (token + 1), name);
- token[entry_size / sizeof(*token) - 1] = grub_cpu_to_be32_compile_time(FDT_END_NODE);
- grub_fdt_set_size_dt_struct (fdt, struct_size + entry_size);
- return ((grub_addr_t) token - (grub_addr_t) fdt
- - grub_fdt_get_off_dt_struct(fdt));
- }
- /* Rearrange FDT blocks in the canonical order: first the memory reservation
- block (just after the FDT header), then the structure block and finally the
- strings block. No free space is left between the first and the second block,
- while the space between the second and the third block is given by the
- clearance argument. */
- static int rearrange_blocks (void *fdt, unsigned int clearance)
- {
- grub_uint32_t off_mem_rsvmap = ALIGN_UP(sizeof(grub_fdt_header_t), 8);
- grub_uint32_t off_dt_struct = off_mem_rsvmap + get_mem_rsvmap_size (fdt);
- grub_uint32_t off_dt_strings = off_dt_struct
- + grub_fdt_get_size_dt_struct (fdt)
- + clearance;
- grub_uint8_t *fdt_ptr = fdt;
- grub_uint8_t *tmp_fdt;
- if ((grub_fdt_get_off_mem_rsvmap (fdt) == off_mem_rsvmap)
- && (grub_fdt_get_off_dt_struct (fdt) == off_dt_struct))
- {
- /* No need to allocate memory for a temporary FDT, just move the strings
- block if needed. */
- if (grub_fdt_get_off_dt_strings (fdt) != off_dt_strings)
- {
- grub_memmove(fdt_ptr + off_dt_strings,
- fdt_ptr + grub_fdt_get_off_dt_strings (fdt),
- grub_fdt_get_size_dt_strings (fdt));
- grub_fdt_set_off_dt_strings (fdt, off_dt_strings);
- }
- return 0;
- }
- tmp_fdt = grub_malloc (grub_fdt_get_totalsize (fdt));
- if (!tmp_fdt)
- return -1;
- grub_memcpy (tmp_fdt + off_mem_rsvmap,
- fdt_ptr + grub_fdt_get_off_mem_rsvmap (fdt),
- get_mem_rsvmap_size (fdt));
- grub_fdt_set_off_mem_rsvmap (fdt, off_mem_rsvmap);
- grub_memcpy (tmp_fdt + off_dt_struct,
- fdt_ptr + grub_fdt_get_off_dt_struct (fdt),
- grub_fdt_get_size_dt_struct (fdt));
- grub_fdt_set_off_dt_struct (fdt, off_dt_struct);
- grub_memcpy (tmp_fdt + off_dt_strings,
- fdt_ptr + grub_fdt_get_off_dt_strings (fdt),
- grub_fdt_get_size_dt_strings (fdt));
- grub_fdt_set_off_dt_strings (fdt, off_dt_strings);
- /* Copy reordered blocks back to fdt. */
- grub_memcpy (fdt_ptr + off_mem_rsvmap, tmp_fdt + off_mem_rsvmap,
- grub_fdt_get_totalsize (fdt) - off_mem_rsvmap);
- grub_free(tmp_fdt);
- return 0;
- }
- static grub_uint32_t *find_prop (const void *fdt, unsigned int nodeoffset,
- const char *name)
- {
- grub_uint32_t *prop = (void *) ((grub_addr_t) fdt
- + grub_fdt_get_off_dt_struct (fdt)
- + nodeoffset);
- grub_uint32_t *end = (void *) struct_end(fdt);
- grub_uint32_t nameoff;
- char *node_name;
- SKIP_NODE_NAME(node_name, prop, end);
- while (prop < end - 2)
- {
- if (grub_be_to_cpu32(*prop) == FDT_PROP)
- {
- nameoff = grub_be_to_cpu32(*(prop + 2));
- if ((nameoff + grub_strlen (name) < grub_fdt_get_size_dt_strings (fdt))
- && !grub_strcmp (name, (char *) fdt +
- grub_fdt_get_off_dt_strings (fdt) + nameoff))
- {
- if (prop + grub_fdt_prop_entry_size(grub_be_to_cpu32(*(prop + 1)))
- / sizeof (*prop) >= end)
- return NULL;
- return prop;
- }
- prop += grub_fdt_prop_entry_size(grub_be_to_cpu32(*(prop + 1))) / sizeof (*prop);
- }
- else if (grub_be_to_cpu32(*prop) == FDT_NOP)
- prop++;
- else
- return NULL;
- }
- return NULL;
- }
- /* Check the FDT header for consistency and adjust the totalsize field to match
- the size allocated for the FDT; if this function is called before the other
- functions in this file and returns success, the other functions are
- guaranteed not to access memory locations outside the allocated memory. */
- int grub_fdt_check_header_nosize (const void *fdt)
- {
- if (((grub_addr_t) fdt & 0x3) || (grub_fdt_get_magic (fdt) != FDT_MAGIC)
- || (grub_fdt_get_version (fdt) < FDT_SUPPORTED_VERSION)
- || (grub_fdt_get_last_comp_version (fdt) > FDT_SUPPORTED_VERSION)
- || (grub_fdt_get_off_dt_struct (fdt) & 0x00000003)
- || (grub_fdt_get_size_dt_struct (fdt) & 0x00000003)
- || (grub_fdt_get_off_dt_struct (fdt) + grub_fdt_get_size_dt_struct (fdt)
- > grub_fdt_get_totalsize (fdt))
- || (grub_fdt_get_off_dt_strings (fdt) + grub_fdt_get_size_dt_strings (fdt)
- > grub_fdt_get_totalsize (fdt))
- || (grub_fdt_get_off_mem_rsvmap (fdt) & 0x00000007)
- || (grub_fdt_get_off_mem_rsvmap (fdt)
- > grub_fdt_get_totalsize (fdt) - 2 * sizeof(grub_uint64_t)))
- return -1;
- return 0;
- }
- int grub_fdt_check_header (const void *fdt, unsigned int size)
- {
- if (size < sizeof (grub_fdt_header_t)
- || (grub_fdt_get_totalsize (fdt) > size)
- || grub_fdt_check_header_nosize (fdt) == -1)
- return -1;
- return 0;
- }
- static const grub_uint32_t *
- advance_token (const void *fdt, const grub_uint32_t *token, const grub_uint32_t *end, int skip_current)
- {
- for (; token < end; skip_current = 0)
- {
- switch (grub_be_to_cpu32 (*token))
- {
- case FDT_BEGIN_NODE:
- if (skip_current)
- {
- token = get_next_node (fdt, (char *) (token + 1));
- continue;
- }
- char *ptr;
- for (ptr = (char *) (token + 1); *ptr && ptr < (char *) end; ptr++)
- ;
- if (ptr >= (char *) end)
- return 0;
- return token;
- case FDT_PROP:
- /* Skip property token and following data (len, nameoff and property
- value). */
- if (token >= end - 1)
- return 0;
- token += grub_fdt_prop_entry_size(grub_be_to_cpu32(*(token + 1)))
- / sizeof(*token);
- break;
- case FDT_NOP:
- token++;
- break;
- default:
- return 0;
- }
- }
- return 0;
- }
- int grub_fdt_next_node (const void *fdt, unsigned int currentoffset)
- {
- const grub_uint32_t *token = (const grub_uint32_t *) fdt + (currentoffset + grub_fdt_get_off_dt_struct (fdt)) / 4;
- token = advance_token (fdt, token, (const void *) struct_end (fdt), 1);
- if (!token)
- return -1;
- return (int) ((grub_addr_t) token - (grub_addr_t) fdt
- - grub_fdt_get_off_dt_struct (fdt));
- }
- int grub_fdt_first_node (const void *fdt, unsigned int parentoffset)
- {
- const grub_uint32_t *token, *end;
- char *node_name;
- if (parentoffset & 0x3)
- return -1;
- token = (const void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
- + parentoffset);
- end = (const void *) struct_end (fdt);
- if ((token >= end) || (grub_be_to_cpu32(*token) != FDT_BEGIN_NODE))
- return -1;
- SKIP_NODE_NAME(node_name, token, end);
- token = advance_token (fdt, token, end, 0);
- if (!token)
- return -1;
- return (int) ((grub_addr_t) token - (grub_addr_t) fdt
- - grub_fdt_get_off_dt_struct (fdt));
- }
- /* Find a direct sub-node of a given parent node. */
- int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset,
- const char *name)
- {
- const grub_uint32_t *token, *end;
- const char *node_name;
- int skip_current = 0;
- if (parentoffset & 0x3)
- return -1;
- token = (const void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
- + parentoffset);
- end = (const void *) struct_end (fdt);
- if ((token >= end) || (grub_be_to_cpu32(*token) != FDT_BEGIN_NODE))
- return -1;
- SKIP_NODE_NAME(node_name, token, end);
- while (1) {
- token = advance_token (fdt, token, end, skip_current);
- if (!token)
- return -1;
- skip_current = 1;
- node_name = (const char *) token + 4;
- if (grub_strcmp (node_name, name) == 0)
- return (int) ((grub_addr_t) token - (grub_addr_t) fdt
- - grub_fdt_get_off_dt_struct (fdt));
- }
- }
- const char *
- grub_fdt_get_nodename (const void *fdt, unsigned int nodeoffset)
- {
- return (const char *) fdt + grub_fdt_get_off_dt_struct(fdt) + nodeoffset + 4;
- }
- int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,
- const char *name)
- {
- unsigned int entry_size = node_entry_size(name);
- if ((parentoffset & 0x3) || (get_free_space (fdt) < entry_size))
- return -1;
- /* The new node entry will increase the size of the structure block: rearrange
- blocks such that there is sufficient free space between the structure and
- the strings block, then add the new node entry. */
- if (rearrange_blocks (fdt, entry_size) < 0)
- return -1;
- return add_subnode (fdt, parentoffset, name);
- }
- const void *
- grub_fdt_get_prop (const void *fdt, unsigned int nodeoffset, const char *name,
- grub_uint32_t *len)
- {
- grub_uint32_t *prop;
- if ((nodeoffset >= grub_fdt_get_size_dt_struct (fdt)) || (nodeoffset & 0x3)
- || (grub_be_to_cpu32(*(grub_uint32_t *) ((grub_addr_t) fdt
- + grub_fdt_get_off_dt_struct (fdt) + nodeoffset))
- != FDT_BEGIN_NODE))
- return 0;
- prop = find_prop (fdt, nodeoffset, name);
- if (!prop)
- return 0;
- if (len)
- *len = grub_be_to_cpu32 (*(prop + 1));
- return prop + 3;
- }
- int grub_fdt_set_prop (void *fdt, unsigned int nodeoffset, const char *name,
- const void *val, grub_uint32_t len)
- {
- grub_uint32_t *prop;
- int prop_name_present = 0;
- grub_uint32_t nameoff = 0;
- if ((nodeoffset >= grub_fdt_get_size_dt_struct (fdt)) || (nodeoffset & 0x3)
- || (grub_be_to_cpu32(*(grub_uint32_t *) ((grub_addr_t) fdt
- + grub_fdt_get_off_dt_struct (fdt) + nodeoffset))
- != FDT_BEGIN_NODE))
- return -1;
- prop = find_prop (fdt, nodeoffset, name);
- if (prop)
- {
- grub_uint32_t prop_len = ALIGN_UP(grub_be_to_cpu32 (*(prop + 1)),
- sizeof(grub_uint32_t));
- grub_uint32_t i;
- prop_name_present = 1;
- for (i = 0; i < prop_len / sizeof(grub_uint32_t); i++)
- *(prop + 3 + i) = grub_cpu_to_be32_compile_time (FDT_NOP);
- if (len > ALIGN_UP(prop_len, sizeof(grub_uint32_t)))
- {
- /* Length of new property value is greater than the space allocated
- for the current value: a new entry needs to be created, so save the
- nameoff field of the current entry and replace the current entry
- with NOP tokens. */
- nameoff = grub_be_to_cpu32 (*(prop + 2));
- *prop = *(prop + 1) = *(prop + 2) = grub_cpu_to_be32_compile_time (FDT_NOP);
- prop = NULL;
- }
- }
- if (!prop || !prop_name_present) {
- unsigned int needed_space = 0;
- if (!prop)
- needed_space = grub_fdt_prop_entry_size(len);
- if (!prop_name_present)
- needed_space += grub_strlen (name) + 1;
- if (needed_space > get_free_space (fdt))
- return -1;
- if (rearrange_blocks (fdt, !prop ? grub_fdt_prop_entry_size(len) : 0) < 0)
- return -1;
- }
- if (!prop_name_present) {
- /* Append the property name at the end of the strings block. */
- nameoff = grub_fdt_get_size_dt_strings (fdt);
- grub_strcpy ((char *) fdt + grub_fdt_get_off_dt_strings (fdt) + nameoff,
- name);
- grub_fdt_set_size_dt_strings (fdt, grub_fdt_get_size_dt_strings (fdt)
- + grub_strlen (name) + 1);
- }
- if (!prop) {
- char *node_name = (char *) ((grub_addr_t) fdt
- + grub_fdt_get_off_dt_struct (fdt) + nodeoffset
- + sizeof(grub_uint32_t));
- prop = (void *) (node_name + ALIGN_UP(grub_strlen(node_name) + 1, 4));
- grub_memmove (prop + grub_fdt_prop_entry_size(len) / sizeof(*prop), prop,
- struct_end(fdt) - (grub_addr_t) prop);
- grub_fdt_set_size_dt_struct (fdt, grub_fdt_get_size_dt_struct (fdt)
- + grub_fdt_prop_entry_size(len));
- *prop = grub_cpu_to_be32_compile_time (FDT_PROP);
- *(prop + 2) = grub_cpu_to_be32 (nameoff);
- }
- *(prop + 1) = grub_cpu_to_be32 (len);
- /* Insert padding bytes at the end of the value; if they are not needed, they
- will be overwritten by the following memcpy. */
- *(prop + grub_fdt_prop_entry_size(len) / sizeof(grub_uint32_t) - 1) = 0;
- grub_memcpy (prop + 3, val, len);
- return 0;
- }
- int
- grub_fdt_create_empty_tree (void *fdt, unsigned int size)
- {
- struct grub_fdt_empty_tree *et;
- if (size < GRUB_FDT_EMPTY_TREE_SZ)
- return -1;
- grub_memset (fdt, 0, size);
- et = fdt;
- et->empty_node.tree_end = grub_cpu_to_be32_compile_time (FDT_END);
- et->empty_node.node_end = grub_cpu_to_be32_compile_time (FDT_END_NODE);
- et->empty_node.node_start = grub_cpu_to_be32_compile_time (FDT_BEGIN_NODE);
- ((struct grub_fdt_empty_tree *) fdt)->header.off_mem_rsvmap =
- grub_cpu_to_be32_compile_time (ALIGN_UP (sizeof (grub_fdt_header_t), 8));
- grub_fdt_set_off_dt_strings (fdt, sizeof (*et));
- grub_fdt_set_off_dt_struct (fdt,
- sizeof (et->header) + sizeof (et->empty_rsvmap));
- grub_fdt_set_version (fdt, FDT_SUPPORTED_VERSION);
- grub_fdt_set_last_comp_version (fdt, FDT_SUPPORTED_VERSION);
- grub_fdt_set_size_dt_struct (fdt, sizeof (et->empty_node));
- grub_fdt_set_totalsize (fdt, size);
- grub_fdt_set_magic (fdt, FDT_MAGIC);
- return 0;
- }
|