From c6a8472baf067d8f89b043de69c790d0af3eb68f Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 17 May 2013 13:45:22 +0200 Subject: [PATCH] Own fdt parsing implementation --- grub-core/Makefile.core.def | 3 +- grub-core/lib/fdt.c | 389 +++++++++++++++++++++++++++++++++++ grub-core/loader/arm/linux.c | 200 +++++++++--------- include/grub/fdt.h | 99 +++++++++ 4 files changed, 587 insertions(+), 104 deletions(-) create mode 100644 grub-core/lib/fdt.c create mode 100644 include/grub/fdt.h diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index d01667d8b..990352d61 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -1536,11 +1536,10 @@ module = { sparc64_ieee1275 = loader/sparc64/ieee1275/linux.c; ia64_efi = loader/ia64/efi/linux.c; arm = loader/arm/linux.c; + arm = lib/fdt.c; common = loader/linux.c; common = lib/cmdline.c; enable = noemu; - - fdt_cppflags = '$(CPPFLAGS_LIBFDT)'; }; module = { diff --git a/grub-core/lib/fdt.c b/grub-core/lib/fdt.c new file mode 100644 index 000000000..57528c58f --- /dev/null +++ b/grub-core/lib/fdt.c @@ -0,0 +1,389 @@ +/* + * 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 . + */ + +#include +#include +#include + +#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))) + +/* Size needed by a property entry: 1 token (FDT_PROPERTY), plus len and nameoff + fields, plus the property value, plus padding if needed. */ +#define prop_entry_size(prop_len) \ + (3 * sizeof(grub_uint32_t) + ALIGN_UP(prop_len, sizeof(grub_uint32_t))) + +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 += 3 + grub_be_to_cpu32 (*(token + 1)); + break; + case FDT_NOP: + token++; + break; + default: + return NULL; + } + } + return NULL; +} + +static int get_mem_rsvmap_size (const void *fdt) +{ + int size = 0; + grub_uint64_t *ptr = (void *) ((grub_addr_t) fdt + + grub_fdt_get_off_mem_rsvmap (fdt)); + + do + { + size += 2 * sizeof(*ptr); + if (!*ptr && !*(ptr + 1)) + 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 *begin = (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); + grub_uint32_t *token = begin; + + /* 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 and nameoff. */ + token += 2; + break; + case FDT_BEGIN_NODE: + case FDT_END_NODE: + goto insert; + case FDT_NOP: + break; + default: + /* invalid token */ + return -1; + } + token++; + } +insert: + grub_memmove (token + entry_size, token, + (grub_addr_t) end - (grub_addr_t) token); + *token = grub_cpu_to_be32(FDT_BEGIN_NODE); + token[entry_size / sizeof(*token) - 2] = 0; /* padding bytes */ + grub_strcpy((char *) (token + 1), name); + token += entry_size / sizeof(*token) - 1; + *token = grub_cpu_to_be32(FDT_END_NODE); + 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)); + 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. */ + 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 (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 nameoff; + + do + { + 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)) + return prop; + prop += prop_entry_size(grub_be_to_cpu32(*prop + 1)) / sizeof (*prop); + } + else if (grub_be_to_cpu32(*prop) != FDT_NOP) + return NULL; + prop++; + } while ((grub_addr_t) prop < ((grub_addr_t) fdt + + grub_fdt_get_off_dt_struct (fdt) + + grub_fdt_get_size_dt_struct (fdt))); + 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 (void *fdt, unsigned int size) +{ + if (((grub_addr_t) fdt & 0x7) || (grub_fdt_get_magic (fdt) != FDT_MAGIC) + || (grub_fdt_get_totalsize (fdt) > size) + || (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; +} + +/* Find a direct sub-node of a given parent node. */ +int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset, + const char *name) +{ + grub_uint32_t *token, *end; + char *node_name; + + if (parentoffset & 0x3) + return -1; + token = (void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt) + + parentoffset); + end = (void *) struct_end (fdt); + while (token < end) + { + switch (grub_be_to_cpu32(*token)) + { + case FDT_BEGIN_NODE: + node_name = (char *) (token + 1); + if (node_name + grub_strlen (name) >= (char *) end) + return -1; + if (!grub_strcmp (node_name, name)) + return (int) ((grub_addr_t) token + + ALIGN_UP(grub_strlen (name) + 1, 4) + - grub_fdt_get_off_dt_struct (fdt)); + token = get_next_node (fdt, node_name); + if (!token) + return -1; + break; + case FDT_END_NODE: + return -1; + case FDT_PROP: + /* Skip property token and following data (len, nameoff and property + value). */ + token += 3 + grub_be_to_cpu32 (*(token + 1)); + break; + case FDT_NOP: + token++; + break; + default: + return -1; + } + } + return -1; +} + +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); +} + +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)) + 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 (FDT_NOP); + if (len > prop_len) + { + /* 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 (FDT_NOP); + prop = NULL; + } + } + if (!prop || !prop_name_present) { + unsigned int needed_space = 0; + + if (!prop) + needed_space = 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 ? 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) { + prop = (void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct (fdt) + + nodeoffset); + grub_memmove (prop + prop_entry_size(len), prop, + grub_fdt_get_size_dt_struct (fdt) - nodeoffset); + *prop = grub_cpu_to_be32 (FDT_PROP); + *(prop + 1) = grub_cpu_to_be32 (len); + *(prop + 2) = grub_cpu_to_be32 (nameoff); + + /* Insert padding bytes at the end of the value; if they are not needed, + they will be overwritten by the follozing memcpy. */ + *(prop + prop_entry_size(len) / sizeof(grub_uint32_t) - 1) = 0; + + grub_memcpy (prop + 3, val, len); + } + return 0; +} diff --git a/grub-core/loader/arm/linux.c b/grub-core/loader/arm/linux.c index 40b5b5e01..22450a09f 100644 --- a/grub-core/loader/arm/linux.c +++ b/grub-core/loader/arm/linux.c @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -27,23 +28,29 @@ #include #include -#include - GRUB_MOD_LICENSE ("GPLv3+"); static grub_dl_t my_mod; static grub_addr_t initrd_start; -static grub_size_t initrd_end; +static grub_addr_t initrd_end; static grub_addr_t linux_addr; static grub_size_t linux_size; static char *linux_args; -static grub_addr_t firmware_boot_data; -static grub_addr_t boot_data; static grub_uint32_t machine_type; +static void *fdt_addr; + +#define LINUX_ZIMAGE_OFFSET 0x24 +#define LINUX_ZIMAGE_MAGIC 0x016f2818 + +#define ARM_FDT_MACHINE_TYPE 0xFFFFFFFF + +#define LINUX_PHYS_OFFSET (0x00008000) +#define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000) +#define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000) /* * linux_prepare_fdt(): @@ -58,19 +65,20 @@ linux_prepare_fdt (void) int tmp_size; void *tmp_fdt; - tmp_size = fdt_totalsize ((void *) boot_data) + FDT_ADDITIONAL_ENTRIES_SIZE; + tmp_size = grub_fdt_get_totalsize (fdt_addr) + 0x100 + grub_strlen (linux_args); tmp_fdt = grub_malloc (tmp_size); if (!tmp_fdt) - return GRUB_ERR_OUT_OF_MEMORY; + return grub_errno; - fdt_open_into ((void *) boot_data, tmp_fdt, tmp_size); + grub_memcpy (tmp_fdt, fdt_addr, grub_fdt_get_totalsize (fdt_addr)); + grub_fdt_set_totalsize (tmp_fdt, tmp_size); /* Find or create '/chosen' node */ - node = fdt_subnode_offset (tmp_fdt, 0, "chosen"); + node = grub_fdt_find_subnode (tmp_fdt, 0, "chosen"); if (node < 0) { grub_printf ("No 'chosen' node in FDT - creating.\n"); - node = fdt_add_subnode (tmp_fdt, 0, "chosen"); + node = grub_fdt_add_subnode (tmp_fdt, 0, "chosen"); if (node < 0) goto failure; } @@ -78,8 +86,8 @@ linux_prepare_fdt (void) grub_printf ("linux_args: '%s'\n", linux_args); /* Generate and set command line */ - retval = fdt_setprop (tmp_fdt, node, "bootargs", linux_args, - grub_strlen (linux_args) + 1); + retval = grub_fdt_set_prop (tmp_fdt, node, "bootargs", linux_args, + grub_strlen (linux_args) + 1); if (retval) goto failure; @@ -89,26 +97,22 @@ linux_prepare_fdt (void) * We're using physical addresses, so even if we have LPAE, we're * restricted to a 32-bit address space. */ - grub_uint32_t fdt_initrd_start = cpu_to_fdt32 (initrd_start); - grub_uint32_t fdt_initrd_end = cpu_to_fdt32 (initrd_end); - grub_dprintf ("loader", "Initrd @ 0x%08x-0x%08x\n", initrd_start, initrd_end); - retval = fdt_setprop (tmp_fdt, node, "linux,initrd-start", - &fdt_initrd_start, sizeof (fdt_initrd_start)); + retval = grub_fdt_set_prop32 (tmp_fdt, node, "linux,initrd-start", + initrd_start); if (retval) goto failure; - retval = fdt_setprop (tmp_fdt, node, "linux,initrd-end", - &fdt_initrd_end, sizeof (fdt_initrd_end)); + retval = grub_fdt_set_prop32 (tmp_fdt, node, "linux,initrd-end", + initrd_end); if (retval) goto failure; } /* Copy updated FDT to its launch location */ - fdt_move (tmp_fdt, (void *) boot_data, fdt_totalsize (tmp_fdt)); + grub_memcpy (fdt_addr, tmp_fdt, tmp_size); grub_free (tmp_fdt); - fdt_pack ((void *) boot_data); grub_dprintf ("loader", "FDT updated for Linux boot\n"); @@ -116,52 +120,35 @@ linux_prepare_fdt (void) failure: grub_free (tmp_fdt); - return GRUB_ERR_BAD_ARGUMENT; + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unable to prepare FDT"); } static grub_err_t linux_boot (void) { kernel_entry_t linuxmain; - grub_err_t err = GRUB_ERR_NONE; + grub_err_t err; + + if (!fdt_addr && machine_type == ARM_FDT_MACHINE_TYPE) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + N_("device tree must be supplied")); grub_arch_sync_caches ((void *) linux_addr, linux_size); grub_dprintf ("loader", "Kernel at: 0x%x\n", linux_addr); - if (!boot_data) - { - if (firmware_boot_data) - { - grub_printf ("Using firmware-supplied boot data @ 0x%08x\n", - firmware_boot_data); - boot_data = firmware_boot_data; - } - else - { - return GRUB_ERR_FILE_NOT_FOUND; - } - } - - grub_dprintf ("loader", "Boot data at: 0x%x\n", boot_data); - - if (fdt32_to_cpu (*(grub_uint32_t *) (boot_data)) == FDT_MAGIC) - { - grub_dprintf ("loader", "FDT @ 0x%08x\n", (grub_addr_t) boot_data); - if (linux_prepare_fdt () != GRUB_ERR_NONE) - { - grub_dprintf ("loader", "linux_prepare_fdt() failed\n"); - return GRUB_ERR_FILE_NOT_FOUND; - } - } + err = linux_prepare_fdt (); + if (err) + return err; + grub_dprintf ("loader", "FDT @ 0x%p\n", fdt_addr); grub_dprintf ("loader", "Jumping to Linux...\n"); /* Boot the kernel. * Arguments to kernel: * r0 - 0 - * r1 - machine type (possibly passed from firmware) - * r2 - address of DTB or ATAG list + * r1 - machine type + * r2 - address of DTB */ linuxmain = (kernel_entry_t) linux_addr; @@ -171,7 +158,7 @@ linux_boot (void) return err; #endif - linuxmain (0, machine_type, (void *) boot_data); + linuxmain (0, machine_type, fdt_addr); return err; } @@ -180,21 +167,18 @@ linux_boot (void) * Only support zImage, so no relocations necessary */ static grub_err_t -linux_load (const char *filename) +linux_load (const char *filename, grub_file_t file) { - grub_file_t file; int size; - file = grub_file_open (filename); - if (!file) - return GRUB_ERR_FILE_NOT_FOUND; - size = grub_file_size (file); if (size == 0) - return GRUB_ERR_FILE_READ_ERROR; + return grub_error (GRUB_ERR_BAD_OS, "empty kernel"); #ifdef GRUB_MACHINE_EFI linux_addr = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_PHYS_OFFSET, size); + if (!linux_addr) + return grub_errno; #else linux_addr = LINUX_ADDRESS; #endif @@ -203,8 +187,10 @@ linux_load (const char *filename) if (grub_file_read (file, (void *) linux_addr, size) != size) { - grub_printf ("Kernel read failed!\n"); - return GRUB_ERR_FILE_READ_ERROR; + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + return grub_errno; } if (*(grub_uint32_t *) (linux_addr + LINUX_ZIMAGE_OFFSET) @@ -235,7 +221,8 @@ static grub_err_t grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { - int size, retval; + int size; + grub_err_t err; grub_file_t file; grub_dl_ref (my_mod); @@ -246,17 +233,20 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), if (!file) goto fail; - retval = linux_load (argv[0]); + err = linux_load (argv[0], file); grub_file_close (file); - if (retval != GRUB_ERR_NONE) + if (err) goto fail; - grub_loader_set (linux_boot, linux_unload, 1); + grub_loader_set (linux_boot, linux_unload, 0); size = grub_loader_cmdline_size (argc, argv); linux_args = grub_malloc (size + sizeof (LINUX_IMAGE)); if (!linux_args) - goto fail; + { + grub_loader_unset(); + goto fail; + } /* Create kernel command line. */ grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE)); @@ -288,16 +278,31 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), if (size == 0) goto fail; + if (initrd_start) + grub_free ((void *) initrd_start); #ifdef GRUB_MACHINE_EFI initrd_start = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_INITRD_PHYS_OFFSET, size); + + if (!initrd_start) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("memory allocation failed")); + goto fail; + } #else initrd_start = LINUX_INITRD_ADDRESS; #endif + grub_dprintf ("loader", "Loading initrd to 0x%08x\n", (grub_addr_t) initrd_start); if (grub_file_read (file, (void *) initrd_start, size) != size) - goto fail; + { + initrd_start = 0; + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } initrd_end = initrd_start + size; @@ -309,28 +314,15 @@ fail: return grub_errno; } -static void * +static grub_err_t load_dtb (grub_file_t dtb, int size) { - void *fdt; + if ((grub_file_read (dtb, fdt_addr, size) != size) + || (grub_fdt_check_header (fdt_addr, size) != 0)) + return grub_error (GRUB_ERR_BAD_OS, N_("invalid device tree")); - fdt = grub_malloc (size); - if (!fdt) - return NULL; - - if (grub_file_read (dtb, fdt, size) != size) - { - grub_free (fdt); - return NULL; - } - - if (fdt_check_header (fdt) != 0) - { - grub_free (fdt); - return NULL; - } - - return fdt; + grub_fdt_set_totalsize (fdt_addr, size); + return GRUB_ERR_NONE; } static grub_err_t @@ -338,7 +330,6 @@ grub_cmd_devicetree (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_file_t dtb; - void *blob; int size; if (argc != 1) @@ -346,25 +337,34 @@ grub_cmd_devicetree (grub_command_t cmd __attribute__ ((unused)), dtb = grub_file_open (argv[0]); if (!dtb) - return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("failed to open file")); + goto out; size = grub_file_size (dtb); if (size == 0) - goto out; - - blob = load_dtb (dtb, size); - if (!blob) - return GRUB_ERR_FILE_NOT_FOUND; + { + grub_error (GRUB_ERR_BAD_OS, "empty file"); + goto out; + } #ifdef GRUB_MACHINE_EFI - boot_data = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_FDT_PHYS_OFFSET, size); + fdt_addr = grub_efi_allocate_loader_memory (LINUX_FDT_PHYS_OFFSET, size); + if (!fdt_addr) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("memory allocation failed")); + goto out; + } #else - boot_data = LINUX_FDT_ADDRESS; + fdt_addr = (void *) LINUX_FDT_ADDRESS; #endif + grub_dprintf ("loader", "Loading device tree to 0x%08x\n", - (grub_addr_t) boot_data); - fdt_move (blob, (void *) boot_data, fdt_totalsize (blob)); - grub_free (blob); + (grub_addr_t) fdt_addr); + load_dtb (dtb, size); + if (grub_errno != GRUB_ERR_NONE) + { + fdt_addr = NULL; + goto out; + } /* * We've successfully loaded an FDT, so any machine type passed @@ -372,8 +372,6 @@ grub_cmd_devicetree (grub_command_t cmd __attribute__ ((unused)), */ machine_type = ARM_FDT_MACHINE_TYPE; - return GRUB_ERR_NONE; - out: grub_file_close (dtb); @@ -391,9 +389,7 @@ GRUB_MOD_INIT (linux) cmd_devicetree = grub_register_command ("devicetree", grub_cmd_devicetree, 0, N_("Load DTB file.")); my_mod = mod; - firmware_boot_data = firmware_get_boot_data (); - - boot_data = (grub_addr_t) NULL; + fdt_addr = (void *) firmware_get_boot_data (); machine_type = firmware_get_machine_type (); } diff --git a/include/grub/fdt.h b/include/grub/fdt.h new file mode 100644 index 000000000..2ad0536b6 --- /dev/null +++ b/include/grub/fdt.h @@ -0,0 +1,99 @@ +/* + * 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 . + */ + +#ifndef GRUB_FDT_HEADER +#define GRUB_FDT_HEADER 1 + +#include + +#define FDT_MAGIC 0xD00DFEED + +typedef struct { + grub_uint32_t magic; + grub_uint32_t totalsize; + grub_uint32_t off_dt_struct; + grub_uint32_t off_dt_strings; + grub_uint32_t off_mem_rsvmap; + grub_uint32_t version; + grub_uint32_t last_comp_version; + grub_uint32_t boot_cpuid_phys; + grub_uint32_t size_dt_strings; + grub_uint32_t size_dt_struct; +} grub_fdt_header_t; + +#define grub_fdt_get_header(fdt, field) \ + grub_be_to_cpu32(((const grub_fdt_header_t *)(fdt))->field) +#define grub_fdt_set_header(fdt, field, value) \ + ((grub_fdt_header_t *)(fdt))->field = grub_cpu_to_be32(value) + +#define grub_fdt_get_magic(fdt) \ + grub_fdt_get_header(fdt, magic) +#define grub_fdt_set_magic(fdt, value) \ + grub_fdt_set_header(fdt, magic, value) +#define grub_fdt_get_totalsize(fdt) \ + grub_fdt_get_header(fdt, totalsize) +#define grub_fdt_set_totalsize(fdt, value) \ + grub_fdt_set_header(fdt, totalsize, value) +#define grub_fdt_get_off_dt_struct(fdt) \ + grub_fdt_get_header(fdt, off_dt_struct) +#define grub_fdt_set_off_dt_struct(fdt, value) \ + grub_fdt_set_header(fdt, off_dt_struct, value) +#define grub_fdt_get_off_dt_strings(fdt) \ + grub_fdt_get_header(fdt, off_dt_strings) +#define grub_fdt_set_off_dt_strings(fdt, value) \ + grub_fdt_set_header(fdt, off_dt_strings, value) +#define grub_fdt_get_off_mem_rsvmap(fdt) \ + grub_fdt_get_header(fdt, off_mem_rsvmap) +#define grub_fdt_set_off_mem_rsvmap(fdt, value) \ + grub_fdt_set_header(fdt, off_mem_rsvmap, value) +#define grub_fdt_get_version(fdt) \ + grub_fdt_get_header(fdt, version) +#define grub_fdt_set_version(fdt, value) \ + grub_fdt_set_header(fdt, version, value) +#define grub_fdt_get_last_comp_version(fdt) \ + grub_fdt_get_header(fdt, last_comp_version) +#define grub_fdt_set_last_comp_version(fdt, value) \ + grub_fdt_set_header(fdt, last_comp_version, value) +#define grub_fdt_get_boot_cpuid_phys(fdt) \ + grub_fdt_get_header(fdt, boot_cpuid_phys) +#define grub_fdt_set_boot_cpuid_phys(fdt, value) \ + grub_fdt_set_header(fdt, boot_cpuid_phys, value) +#define grub_fdt_get_size_dt_strings(fdt) \ + grub_fdt_get_header(fdt, size_dt_strings) +#define grub_fdt_set_size_dt_strings(fdt, value) \ + grub_fdt_set_header(fdt, size_dt_strings, value) +#define grub_fdt_get_size_dt_struct(fdt) \ + grub_fdt_get_header(fdt, size_dt_struct) +#define grub_fdt_set_size_dt_struct(fdt, value) \ + grub_fdt_set_header(fdt, size_dt_struct, value) + +int grub_fdt_check_header (void *fdt, unsigned int size); +int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset, + const char *name); +int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset, + const char *name); + +int grub_fdt_set_prop (void *fdt, unsigned int nodeoffset, const char *name, + const void *val, grub_uint32_t len); +#define grub_fdt_set_prop32(fdt, nodeoffset, name, val) \ +({ \ + grub_uint32_t _val = grub_cpu_to_be32(val); \ + grub_fdt_set_prop ((fdt), (nodeoffset), (name), &_val, 4); \ +}) + +#endif /* ! GRUB_FDT_HEADER */