]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Own fdt parsing implementation
authorFrancesco Lavra <francescolavra.fl@gmail.com>
Fri, 17 May 2013 11:45:22 +0000 (13:45 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Fri, 17 May 2013 11:45:22 +0000 (13:45 +0200)
grub-core/Makefile.core.def
grub-core/lib/fdt.c [new file with mode: 0644]
grub-core/loader/arm/linux.c
include/grub/fdt.h [new file with mode: 0644]

index d01667d8be4da91e28dee51ce5462ee9b53f9c20..990352d618b6a40de579744253e77816ab3da9b0 100644 (file)
@@ -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 (file)
index 0000000..57528c5
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/fdt.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+#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;
+}
index 40b5b5e0143a320f497efcd7ea41b90fc2d34f71..22450a09fe0b95f2f20c22aaa1700f37a7371987 100644 (file)
@@ -18,6 +18,7 @@
  */
 
 #include <grub/dl.h>
+#include <grub/fdt.h>
 #include <grub/file.h>
 #include <grub/loader.h>
 #include <grub/mm.h>
 #include <grub/cpu/linux.h>
 #include <grub/lib/cmdline.h>
 
-#include <libfdt.h>
-
 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 (file)
index 0000000..2ad0536
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_FDT_HEADER
+#define GRUB_FDT_HEADER        1
+
+#include <grub/types.h>
+
+#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 */