bc092b9
From fcbb723d4b1f7ad4687191562621dd0eb25f4d9e Mon Sep 17 00:00:00 2001
bc092b9
From: Vladimir Serbinenko <phcoder@gmail.com>
bc092b9
Date: Mon, 8 May 2017 21:19:59 +0200
78e1a10
Subject: [PATCH 020/216] Add support for device-tree-based drivers.
bc092b9
bc092b9
---
bc092b9
 grub-core/Makefile.core.def        |   2 +
bc092b9
 grub-core/bus/fdt.c                | 255 +++++++++++++++++++++++++++++++++++++
bc092b9
 grub-core/kern/arm/coreboot/init.c |  20 +++
ec4acbb
 grub-core/lib/fdt.c                | 152 ++++++++++++++++------
ec4acbb
 util/grub-install-common.c         |   2 +-
ec4acbb
 util/grub-mkimage.c                |  11 +-
ec4acbb
 util/mkimage.c                     |  24 +++-
bc092b9
 include/grub/fdt.h                 |  23 ++--
bc092b9
 include/grub/fdtbus.h              |  73 +++++++++++
bc092b9
 include/grub/kernel.h              |   3 +-
bc092b9
 include/grub/util/install.h        |   2 +-
ec4acbb
 conf/Makefile.common               |   4 +-
ec4acbb
 grub-core/Makefile.am              |  10 ++
ec4acbb
 13 files changed, 524 insertions(+), 57 deletions(-)
bc092b9
 create mode 100644 grub-core/bus/fdt.c
bc092b9
 create mode 100644 include/grub/fdtbus.h
bc092b9
bc092b9
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
ec4acbb
index 411dca46bf2..77d0b019eb2 100644
bc092b9
--- a/grub-core/Makefile.core.def
bc092b9
+++ b/grub-core/Makefile.core.def
bc092b9
@@ -158,6 +158,8 @@ kernel = {
bc092b9
   arm_coreboot = kern/arm/coreboot/init.c;
bc092b9
   arm_coreboot = kern/arm/coreboot/timer.c;
bc092b9
   arm_coreboot = kern/arm/coreboot/coreboot.S;
bc092b9
+  arm_coreboot = lib/fdt.c;
bc092b9
+  arm_coreboot = bus/fdt.c;
bc092b9
 
bc092b9
   terminfoinkernel = term/terminfo.c;
bc092b9
   terminfoinkernel = term/tparm.c;
bc092b9
diff --git a/grub-core/bus/fdt.c b/grub-core/bus/fdt.c
bc092b9
new file mode 100644
ec4acbb
index 00000000000..6fb077000a5
bc092b9
--- /dev/null
bc092b9
+++ b/grub-core/bus/fdt.c
bc092b9
@@ -0,0 +1,255 @@
bc092b9
+/*
bc092b9
+ *  GRUB  --  GRand Unified Bootloader
bc092b9
+ *  Copyright (C) 2016  Free Software Foundation, Inc.
bc092b9
+ *
bc092b9
+ *  GRUB is free software: you can redistribute it and/or modify
bc092b9
+ *  it under the terms of the GNU General Public License as published by
bc092b9
+ *  the Free Software Foundation, either version 3 of the License, or
bc092b9
+ *  (at your option) any later version.
bc092b9
+ *
bc092b9
+ *  GRUB is distributed in the hope that it will be useful,
bc092b9
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
bc092b9
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
bc092b9
+ *  GNU General Public License for more details.
bc092b9
+ *
bc092b9
+ *  You should have received a copy of the GNU General Public License
bc092b9
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
bc092b9
+ */
bc092b9
+
bc092b9
+#include <grub/fdtbus.h>
bc092b9
+#include <grub/fdt.h>
bc092b9
+#include <grub/term.h>
bc092b9
+
bc092b9
+static const void *dtb;
bc092b9
+static grub_size_t root_address_cells, root_size_cells;
bc092b9
+/* Pointer to this symbol signals invalid mapping.  */
bc092b9
+char grub_fdtbus_invalid_mapping[1];
bc092b9
+
bc092b9
+struct grub_fdtbus_dev
bc092b9
+{
bc092b9
+  struct grub_fdtbus_dev *next;
bc092b9
+  struct grub_fdtbus_dev *parent;
bc092b9
+  int node;
bc092b9
+  struct grub_fdtbus_driver *driver;
bc092b9
+};
bc092b9
+
bc092b9
+struct grub_fdtbus_dev *devs;
bc092b9
+struct grub_fdtbus_driver *drivers;
bc092b9
+
bc092b9
+static int
bc092b9
+is_compatible (struct grub_fdtbus_driver *driver,
bc092b9
+	       int node)
bc092b9
+{
bc092b9
+  grub_size_t compatible_size;
bc092b9
+  const char *compatible = grub_fdt_get_prop (dtb, node, "compatible",
bc092b9
+					      &compatible_size);
bc092b9
+  const char *compatible_end = compatible + compatible_size;
bc092b9
+  while (compatible < compatible_end)
bc092b9
+    {
bc092b9
+      if (grub_strcmp (driver->compatible, compatible) == 0)
bc092b9
+	return 1;
bc092b9
+      compatible += grub_strlen (compatible) + 1;
bc092b9
+    }
bc092b9
+  return 0;
bc092b9
+}
bc092b9
+
bc092b9
+static void
bc092b9
+fdtbus_scan (struct grub_fdtbus_dev *parent)
bc092b9
+{
bc092b9
+  int node;
bc092b9
+  for (node = grub_fdt_first_node (dtb, parent ? parent->node : 0); node >= 0;
bc092b9
+       node = grub_fdt_next_node (dtb, node))
bc092b9
+    {
bc092b9
+      struct grub_fdtbus_dev *dev;
bc092b9
+      struct grub_fdtbus_driver *driver;
bc092b9
+      dev = grub_zalloc (sizeof (*dev));
bc092b9
+      if (!dev)
bc092b9
+	{
bc092b9
+	  grub_print_error ();
bc092b9
+	  return;
bc092b9
+	}
bc092b9
+      dev->node = node;
bc092b9
+      dev->next = devs;
bc092b9
+      dev->parent = parent;
bc092b9
+      devs = dev;
bc092b9
+      FOR_LIST_ELEMENTS(driver, drivers)
bc092b9
+	if (!dev->driver && is_compatible (driver, node))
bc092b9
+	  {
bc092b9
+	    if (driver->attach(dev) == GRUB_ERR_NONE)
bc092b9
+	      {
bc092b9
+		dev->driver = driver;
bc092b9
+		break;
bc092b9
+	      }
bc092b9
+	    grub_print_error ();
bc092b9
+	  }
bc092b9
+      fdtbus_scan (dev);
bc092b9
+    }
bc092b9
+}
bc092b9
+
bc092b9
+void
bc092b9
+grub_fdtbus_register (struct grub_fdtbus_driver *driver)
bc092b9
+{
bc092b9
+  struct grub_fdtbus_dev *dev;
bc092b9
+  grub_list_push (GRUB_AS_LIST_P (&drivers),
bc092b9
+		  GRUB_AS_LIST (driver));
bc092b9
+  for (dev = devs; dev; dev = dev->next)
bc092b9
+    if (!dev->driver && is_compatible (driver, dev->node))
bc092b9
+      {
bc092b9
+	if (driver->attach(dev) == GRUB_ERR_NONE)
bc092b9
+	  dev->driver = driver;
bc092b9
+	grub_print_error ();
bc092b9
+      }
bc092b9
+}
bc092b9
+
bc092b9
+void
bc092b9
+grub_fdtbus_unregister (struct grub_fdtbus_driver *driver)
bc092b9
+{
bc092b9
+  grub_list_remove (GRUB_AS_LIST (driver));
bc092b9
+  struct grub_fdtbus_dev *dev;
bc092b9
+  for (dev = devs; dev; dev = dev->next)
bc092b9
+    if (dev->driver == driver)
bc092b9
+      {
bc092b9
+	if (driver->detach)
bc092b9
+	  driver->detach(dev);
bc092b9
+	dev->driver = 0;
bc092b9
+      }
bc092b9
+}
bc092b9
+
bc092b9
+void
bc092b9
+grub_fdtbus_init (const void *dtb_in, grub_size_t size)
bc092b9
+{
bc092b9
+  if (!dtb_in || grub_fdt_check_header (dtb_in, size) < 0)
bc092b9
+    grub_fatal ("invalid FDT");
bc092b9
+  dtb = dtb_in;
bc092b9
+  const grub_uint32_t *prop = grub_fdt_get_prop (dtb, 0, "#address-cells", 0);
bc092b9
+  if (prop)
bc092b9
+    root_address_cells = grub_be_to_cpu32 (*prop);
bc092b9
+  else
bc092b9
+    root_address_cells = 1;
bc092b9
+
bc092b9
+  prop = grub_fdt_get_prop (dtb, 0, "#size-cells", 0);
bc092b9
+  if (prop)
bc092b9
+    root_size_cells = grub_be_to_cpu32 (*prop);
bc092b9
+  else
bc092b9
+    root_size_cells = 1;
bc092b9
+
bc092b9
+  fdtbus_scan (0);
bc092b9
+}
bc092b9
+
bc092b9
+static int
bc092b9
+get_address_cells (const struct grub_fdtbus_dev *dev)
bc092b9
+{
bc092b9
+  const grub_uint32_t *prop;
bc092b9
+  if (!dev)
bc092b9
+    return root_address_cells;
bc092b9
+  prop = grub_fdt_get_prop (dtb, dev->node, "#address-cells", 0);
bc092b9
+  if (prop)
bc092b9
+    return grub_be_to_cpu32 (*prop);
bc092b9
+  return 1;
bc092b9
+}
bc092b9
+
bc092b9
+static int
bc092b9
+get_size_cells (const struct grub_fdtbus_dev *dev)
bc092b9
+{
bc092b9
+  const grub_uint32_t *prop;
bc092b9
+  if (!dev)
bc092b9
+    return root_size_cells;
bc092b9
+  prop = grub_fdt_get_prop (dtb, dev->node, "#size-cells", 0);
bc092b9
+  if (prop)
bc092b9
+    return grub_be_to_cpu32 (*prop);
bc092b9
+  return 1;
bc092b9
+}
bc092b9
+
bc092b9
+static grub_uint64_t
bc092b9
+get64 (const grub_uint32_t *reg, grub_size_t cells)
bc092b9
+{
bc092b9
+  grub_uint64_t val = 0;
bc092b9
+  if (cells >= 1)
bc092b9
+    val = grub_be_to_cpu32 (reg[cells - 1]);
bc092b9
+  if (cells >= 2)
bc092b9
+    val |= ((grub_uint64_t) grub_be_to_cpu32 (reg[cells - 2])) << 32;
bc092b9
+  return val;
bc092b9
+}
bc092b9
+
bc092b9
+static volatile void *
bc092b9
+translate (const struct grub_fdtbus_dev *dev, const grub_uint32_t *reg)
bc092b9
+{
bc092b9
+  volatile void *ret;
bc092b9
+  const grub_uint32_t *ranges;
bc092b9
+  grub_size_t ranges_size, cells_per_mapping;
bc092b9
+  grub_size_t parent_address_cells, child_address_cells, child_size_cells;
bc092b9
+  grub_size_t nmappings, i;
bc092b9
+  if (dev == 0)
bc092b9
+    {
bc092b9
+      grub_uint64_t val;
bc092b9
+      val = get64 (reg, root_address_cells);
bc092b9
+      if (sizeof (void *) == 4 && (val >> 32))
bc092b9
+	return grub_fdtbus_invalid_mapping;
bc092b9
+      return (void *) (grub_addr_t) val;
bc092b9
+    }
bc092b9
+  ranges = grub_fdt_get_prop (dtb, dev->node, "ranges", &ranges_size);
bc092b9
+  if (!ranges)
bc092b9
+    return grub_fdtbus_invalid_mapping;
bc092b9
+  if (ranges_size == 0)
bc092b9
+    return translate (dev->parent, reg);
bc092b9
+  parent_address_cells = get_address_cells (dev->parent);
bc092b9
+  child_address_cells = get_address_cells (dev);
bc092b9
+  child_size_cells = get_size_cells (dev);
bc092b9
+  cells_per_mapping = parent_address_cells + child_address_cells + child_size_cells;
bc092b9
+  nmappings = ranges_size / 4 / cells_per_mapping;
bc092b9
+  for (i = 0; i < nmappings; i++)
bc092b9
+    {
bc092b9
+      const grub_uint32_t *child_addr = &ranges[i * cells_per_mapping];
bc092b9
+      const grub_uint32_t *parent_addr = child_addr + child_address_cells;
bc092b9
+      grub_uint64_t child_size = get64 (parent_addr + parent_address_cells, child_size_cells);
bc092b9
+
bc092b9
+      if (child_address_cells > 2 && grub_memcmp (reg, child_addr, (child_address_cells - 2) * 4) != 0)
bc092b9
+	continue;
bc092b9
+      if (get64 (reg, child_address_cells) < get64 (child_addr, child_address_cells))
bc092b9
+	continue;
bc092b9
+
bc092b9
+      grub_uint64_t offset = get64 (reg, child_address_cells) - get64 (child_addr, child_address_cells);
bc092b9
+      if (offset >= child_size)
bc092b9
+	continue;
bc092b9
+
bc092b9
+      ret = translate (dev->parent, parent_addr);
bc092b9
+      if (grub_fdtbus_is_mapping_valid (ret))
bc092b9
+	ret = (volatile char *) ret + offset;
bc092b9
+      return ret;
bc092b9
+    }
bc092b9
+  return grub_fdtbus_invalid_mapping;
bc092b9
+}
bc092b9
+
bc092b9
+volatile void *
bc092b9
+grub_fdtbus_map_reg (const struct grub_fdtbus_dev *dev, int regno, grub_size_t *size)
bc092b9
+{
bc092b9
+  grub_size_t address_cells, size_cells;
bc092b9
+  address_cells = get_address_cells (dev->parent);
bc092b9
+  size_cells = get_size_cells (dev->parent);
bc092b9
+  const grub_uint32_t *reg = grub_fdt_get_prop (dtb, dev->node, "reg", 0);
bc092b9
+  if (size && size_cells)
bc092b9
+    *size = reg[(address_cells + size_cells) * regno + address_cells];
bc092b9
+  if (size && !size_cells)
bc092b9
+    *size = 0;
bc092b9
+  return translate (dev->parent, reg + (address_cells + size_cells) * regno);
bc092b9
+}
bc092b9
+
bc092b9
+const char *
bc092b9
+grub_fdtbus_get_name (const struct grub_fdtbus_dev *dev)
bc092b9
+{
bc092b9
+  return grub_fdt_get_nodename (dtb, dev->node);
bc092b9
+}
bc092b9
+
bc092b9
+const void *
bc092b9
+grub_fdtbus_get_prop (const struct grub_fdtbus_dev *dev,
bc092b9
+		      const char *name,
bc092b9
+		      grub_uint32_t *len)
bc092b9
+{
bc092b9
+  return grub_fdt_get_prop (dtb, dev->node, name, len);
bc092b9
+}
bc092b9
+
bc092b9
+const void *
bc092b9
+grub_fdtbus_get_fdt (void)
bc092b9
+{
bc092b9
+  return dtb;
bc092b9
+}
bc092b9
diff --git a/grub-core/kern/arm/coreboot/init.c b/grub-core/kern/arm/coreboot/init.c
ec4acbb
index 51ecaceb005..aec75c672a2 100644
bc092b9
--- a/grub-core/kern/arm/coreboot/init.c
bc092b9
+++ b/grub-core/kern/arm/coreboot/init.c
bc092b9
@@ -33,6 +33,7 @@
bc092b9
 #include <grub/symbol.h>
bc092b9
 #include <grub/video.h>
bc092b9
 #include <grub/coreboot/lbio.h>
bc092b9
+#include <grub/fdtbus.h>
bc092b9
 
bc092b9
 extern grub_uint8_t _start[];
bc092b9
 extern grub_uint8_t _end[];
bc092b9
@@ -99,6 +100,10 @@ heap_init (grub_uint64_t addr, grub_uint64_t size, grub_memory_type_t type,
bc092b9
 void
bc092b9
 grub_machine_init (void)
bc092b9
 {
bc092b9
+  struct grub_module_header *header;
bc092b9
+  void *dtb = 0;
bc092b9
+  grub_size_t dtb_size = 0;
bc092b9
+
bc092b9
   modend = grub_modules_get_end ();
bc092b9
 
bc092b9
   grub_video_coreboot_fb_early_init ();
bc092b9
@@ -112,6 +117,21 @@ grub_machine_init (void)
bc092b9
   grub_font_init ();
bc092b9
   grub_gfxterm_init ();
bc092b9
 
bc092b9
+  FOR_MODULES (header)
bc092b9
+    if (header->type == OBJ_TYPE_DTB)
bc092b9
+      {
bc092b9
+	char *dtb_orig_addr, *dtb_copy;
bc092b9
+	dtb_orig_addr = (char *) header + sizeof (struct grub_module_header);
bc092b9
+
bc092b9
+	dtb_size = header->size - sizeof (struct grub_module_header);
bc092b9
+	dtb = dtb_copy = grub_malloc (dtb_size);
bc092b9
+	grub_memmove (dtb_copy, dtb_orig_addr, dtb_size);
bc092b9
+	break;
bc092b9
+      }
bc092b9
+  if (!dtb)
bc092b9
+    grub_fatal ("No DTB found");
bc092b9
+  grub_fdtbus_init (dtb, dtb_size);
bc092b9
+
bc092b9
   grub_machine_timer_init ();
bc092b9
 }
bc092b9
 
bc092b9
diff --git a/grub-core/lib/fdt.c b/grub-core/lib/fdt.c
ec4acbb
index b5d520f2088..bdc6302448d 100644
bc092b9
--- a/grub-core/lib/fdt.c
bc092b9
+++ b/grub-core/lib/fdt.c
bc092b9
@@ -102,13 +102,13 @@ static grub_uint32_t *get_next_node (const void *fdt, char *node_name)
bc092b9
 static int get_mem_rsvmap_size (const void *fdt)
bc092b9
 {
bc092b9
   int size = 0;
bc092b9
-  grub_uint64_t *ptr = (void *) ((grub_addr_t) fdt
bc092b9
-                                 + grub_fdt_get_off_mem_rsvmap (fdt));
bc092b9
+  grub_unaligned_uint64_t *ptr = (void *) ((grub_addr_t) fdt
bc092b9
+					   + grub_fdt_get_off_mem_rsvmap (fdt));
bc092b9
 
bc092b9
   do
bc092b9
   {
bc092b9
     size += 2 * sizeof(*ptr);
bc092b9
-    if (!*ptr && !*(ptr + 1))
bc092b9
+    if (!ptr[0].val && !ptr[1].val)
bc092b9
       return size;
bc092b9
     ptr += 2;
bc092b9
   } while ((grub_addr_t) ptr <= (grub_addr_t) fdt + grub_fdt_get_totalsize (fdt)
bc092b9
@@ -229,7 +229,7 @@ static int rearrange_blocks (void *fdt, unsigned int clearance)
bc092b9
   return 0;
bc092b9
 }
bc092b9
 
bc092b9
-static grub_uint32_t *find_prop (void *fdt, unsigned int nodeoffset,
bc092b9
+static grub_uint32_t *find_prop (const void *fdt, unsigned int nodeoffset,
bc092b9
 				 const char *name)
bc092b9
 {
bc092b9
   grub_uint32_t *prop = (void *) ((grub_addr_t) fdt
bc092b9
@@ -268,9 +268,9 @@ static grub_uint32_t *find_prop (void *fdt, unsigned int nodeoffset,
bc092b9
    the size allocated for the FDT; if this function is called before the other
bc092b9
    functions in this file and returns success, the other functions are
bc092b9
    guaranteed not to access memory locations outside the allocated memory. */
bc092b9
-int grub_fdt_check_header_nosize (void *fdt)
bc092b9
+int grub_fdt_check_header_nosize (const void *fdt)
bc092b9
 {
bc092b9
-  if (((grub_addr_t) fdt & 0x7) || (grub_fdt_get_magic (fdt) != FDT_MAGIC)
bc092b9
+  if (((grub_addr_t) fdt & 0x3) || (grub_fdt_get_magic (fdt) != FDT_MAGIC)
bc092b9
       || (grub_fdt_get_version (fdt) < FDT_SUPPORTED_VERSION)
bc092b9
       || (grub_fdt_get_last_comp_version (fdt) > FDT_SUPPORTED_VERSION)
bc092b9
       || (grub_fdt_get_off_dt_struct (fdt) & 0x00000003)
bc092b9
@@ -286,7 +286,7 @@ int grub_fdt_check_header_nosize (void *fdt)
bc092b9
   return 0;
bc092b9
 }
bc092b9
 
bc092b9
-int grub_fdt_check_header (void *fdt, unsigned int size)
bc092b9
+int grub_fdt_check_header (const void *fdt, unsigned int size)
bc092b9
 {
bc092b9
   if (size < sizeof (grub_fdt_header_t)
bc092b9
       || (grub_fdt_get_totalsize (fdt) > size)
ec4acbb
@@ -295,52 +295,104 @@ int grub_fdt_check_header (void *fdt, unsigned int size)
bc092b9
   return 0;
bc092b9
 }
bc092b9
 
bc092b9
+static const grub_uint32_t *
bc092b9
+advance_token (const void *fdt, const grub_uint32_t *token, const grub_uint32_t *end, int skip_current)
ec4acbb
+{
bc092b9
+  for (; token < end; skip_current = 0)
ec4acbb
+  {
bc092b9
+    switch (grub_be_to_cpu32 (*token))
ec4acbb
+    {
ec4acbb
+      case FDT_BEGIN_NODE:
bc092b9
+	if (skip_current)
bc092b9
+	  {
bc092b9
+	    token = get_next_node (fdt, (char *) (token + 1));
bc092b9
+	    continue;
bc092b9
+	  }
bc092b9
+	char *ptr;
bc092b9
+	for (ptr = (char *) (token + 1); *ptr && ptr < (char *) end; ptr++);
bc092b9
+        if (ptr >= (char *) end)
bc092b9
+          return 0;
bc092b9
+	return token;
ec4acbb
+      case FDT_PROP:
ec4acbb
+        /* Skip property token and following data (len, nameoff and property
ec4acbb
+           value). */
ec4acbb
+        if (token >= end - 1)
bc092b9
+          return 0;
ec4acbb
+        token += prop_entry_size(grub_be_to_cpu32(*(token + 1)))
ec4acbb
+                 / sizeof(*token);
ec4acbb
+        break;
ec4acbb
+      case FDT_NOP:
ec4acbb
+        token++;
ec4acbb
+        break;
ec4acbb
+      default:
bc092b9
+        return 0;
ec4acbb
+    }
ec4acbb
+  }
bc092b9
+  return 0;
bc092b9
+}
bc092b9
+
bc092b9
+int grub_fdt_next_node (const void *fdt, unsigned int currentoffset)
bc092b9
+{
bc092b9
+  const grub_uint32_t *token = (const grub_uint32_t *) fdt + (currentoffset + grub_fdt_get_off_dt_struct (fdt)) / 4;
bc092b9
+  token = advance_token (fdt, token, (const void *) struct_end (fdt), 1);
bc092b9
+  if (!token)
bc092b9
+    return -1;
bc092b9
+  return (int) ((grub_addr_t) token - (grub_addr_t) fdt
bc092b9
+		- grub_fdt_get_off_dt_struct (fdt));
bc092b9
+}			 
bc092b9
+
bc092b9
+int grub_fdt_first_node (const void *fdt, unsigned int parentoffset)
bc092b9
+{
bc092b9
+  const grub_uint32_t *token, *end;
bc092b9
+  char *node_name;
bc092b9
+
bc092b9
+  if (parentoffset & 0x3)
bc092b9
+    return -1;
bc092b9
+  token = (const void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
bc092b9
+                    + parentoffset);
bc092b9
+  end = (const void *) struct_end (fdt);
bc092b9
+  if ((token >= end) || (grub_be_to_cpu32(*token) != FDT_BEGIN_NODE))
bc092b9
+    return -1;
bc092b9
+  SKIP_NODE_NAME(node_name, token, end);
bc092b9
+  token = advance_token (fdt, token, end, 0);
bc092b9
+  if (!token)
bc092b9
+    return -1;
bc092b9
+  return (int) ((grub_addr_t) token - (grub_addr_t) fdt
bc092b9
+		- grub_fdt_get_off_dt_struct (fdt));
bc092b9
+}			 
bc092b9
+
ec4acbb
 /* Find a direct sub-node of a given parent node. */
ec4acbb
 int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset,
ec4acbb
 			   const char *name)
ec4acbb
 {
ec4acbb
-  grub_uint32_t *token, *end;
ec4acbb
-  char *node_name;
bc092b9
+  const grub_uint32_t *token, *end;
bc092b9
+  const char *node_name;
bc092b9
+  int skip_current = 0;
ec4acbb
 
ec4acbb
   if (parentoffset & 0x3)
ec4acbb
     return -1;
ec4acbb
-  token = (void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
bc092b9
+  token = (const void *) ((grub_addr_t) fdt + grub_fdt_get_off_dt_struct(fdt)
ec4acbb
                     + parentoffset);
ec4acbb
-  end = (void *) struct_end (fdt);
bc092b9
+  end = (const void *) struct_end (fdt);
ec4acbb
   if ((token >= end) || (grub_be_to_cpu32(*token) != FDT_BEGIN_NODE))
ec4acbb
     return -1;
ec4acbb
   SKIP_NODE_NAME(node_name, token, end);
ec4acbb
-  while (token < end)
ec4acbb
-  {
ec4acbb
-    switch (grub_be_to_cpu32(*token))
ec4acbb
-    {
ec4acbb
-      case FDT_BEGIN_NODE:
ec4acbb
-        node_name = (char *) (token + 1);
ec4acbb
-        if (node_name + grub_strlen (name) >= (char *) end)
ec4acbb
-          return -1;
ec4acbb
-        if (!grub_strcmp (node_name, name))
ec4acbb
-          return (int) ((grub_addr_t) token - (grub_addr_t) fdt
ec4acbb
-                        - grub_fdt_get_off_dt_struct (fdt));
ec4acbb
-        token = get_next_node (fdt, node_name);
ec4acbb
-        if (!token)
ec4acbb
-          return -1;
ec4acbb
-        break;
ec4acbb
-      case FDT_PROP:
ec4acbb
-        /* Skip property token and following data (len, nameoff and property
ec4acbb
-           value). */
ec4acbb
-        if (token >= end - 1)
ec4acbb
-          return -1;
ec4acbb
-        token += prop_entry_size(grub_be_to_cpu32(*(token + 1)))
ec4acbb
-                 / sizeof(*token);
ec4acbb
-        break;
ec4acbb
-      case FDT_NOP:
ec4acbb
-        token++;
ec4acbb
-        break;
ec4acbb
-      default:
ec4acbb
-        return -1;
ec4acbb
-    }
bc092b9
+  while (1) {
bc092b9
+    token = advance_token (fdt, token, end, skip_current);
bc092b9
+    if (!token)
bc092b9
+      return -1;
bc092b9
+    skip_current = 1;
bc092b9
+    node_name = (const char *) token + 4;
bc092b9
+    if (grub_strcmp (node_name, name) == 0)
bc092b9
+      return (int) ((grub_addr_t) token - (grub_addr_t) fdt
bc092b9
+		    - grub_fdt_get_off_dt_struct (fdt));
ec4acbb
   }
ec4acbb
-  return -1;
bc092b9
+}
bc092b9
+
bc092b9
+const char *
bc092b9
+grub_fdt_get_nodename (const void *fdt, unsigned int nodeoffset)
bc092b9
+{
bc092b9
+  return (const char *) fdt + grub_fdt_get_off_dt_struct(fdt) + nodeoffset + 4;
bc092b9
 }
bc092b9
 
bc092b9
 int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,
bc092b9
@@ -359,6 +411,24 @@ int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,
bc092b9
   return add_subnode (fdt, parentoffset, name);
bc092b9
 }
bc092b9
 
bc092b9
+const void *
bc092b9
+grub_fdt_get_prop (const void *fdt, unsigned int nodeoffset, const char *name,
bc092b9
+		   grub_uint32_t *len)
bc092b9
+{
bc092b9
+  grub_uint32_t *prop;
bc092b9
+  if ((nodeoffset >= grub_fdt_get_size_dt_struct (fdt)) || (nodeoffset & 0x3)
bc092b9
+      || (grub_be_to_cpu32(*(grub_uint32_t *) ((grub_addr_t) fdt
bc092b9
+					       + grub_fdt_get_off_dt_struct (fdt) + nodeoffset))
bc092b9
+          != FDT_BEGIN_NODE))
bc092b9
+    return 0;
bc092b9
+  prop = find_prop (fdt, nodeoffset, name);
bc092b9
+  if (!prop)
bc092b9
+    return 0;
bc092b9
+  if (len)
bc092b9
+    *len = grub_be_to_cpu32 (*(prop + 1));
bc092b9
+  return prop + 3;
bc092b9
+}
bc092b9
+
bc092b9
 int grub_fdt_set_prop (void *fdt, unsigned int nodeoffset, const char *name,
bc092b9
 		       const void *val, grub_uint32_t len)
bc092b9
 {
ec4acbb
diff --git a/util/grub-install-common.c b/util/grub-install-common.c
ec4acbb
index 452b230daed..8539ff3484d 100644
ec4acbb
--- a/util/grub-install-common.c
ec4acbb
+++ b/util/grub-install-common.c
ec4acbb
@@ -499,7 +499,7 @@ grub_install_make_image_wrap_file (const char *dir, const char *prefix,
ec4acbb
   grub_install_generate_image (dir, prefix, fp, outname,
ec4acbb
 			       modules.entries, memdisk_path,
ec4acbb
 			       pubkeys, npubkeys, config_path, tgt,
ec4acbb
-			       note, compression);
ec4acbb
+			       note, compression, 0);
ec4acbb
   while (dc--)
ec4acbb
     grub_install_pop_module ();
ec4acbb
 }
ec4acbb
diff --git a/util/grub-mkimage.c b/util/grub-mkimage.c
ec4acbb
index aba19d21b9a..98d24cc06ea 100644
ec4acbb
--- a/util/grub-mkimage.c
ec4acbb
+++ b/util/grub-mkimage.c
ec4acbb
@@ -71,6 +71,7 @@ static struct argp_option options[] = {
ec4acbb
    N_("embed FILE as a memdisk image\n"
ec4acbb
       "Implies `-p (memdisk)/boot/grub' and overrides any prefix supplied previously,"
ec4acbb
       " but the prefix itself can be overridden by later options"), 0},
ec4acbb
+  {"dtb",  'D', N_("FILE"), 0, N_("embed FILE as a device tree (DTB)\n"), 0},
ec4acbb
    /* TRANSLATORS: "embed" is a verb (command description).  "*/
ec4acbb
   {"config",   'c', N_("FILE"), 0, N_("embed FILE as an early config"), 0},
ec4acbb
    /* TRANSLATORS: "embed" is a verb (command description).  "*/
ec4acbb
@@ -117,6 +118,7 @@ struct arguments
ec4acbb
   char *dir;
ec4acbb
   char *prefix;
ec4acbb
   char *memdisk;
ec4acbb
+  char *dtb;
ec4acbb
   char **pubkeys;
ec4acbb
   size_t npubkeys;
ec4acbb
   char *font;
ec4acbb
@@ -176,6 +178,13 @@ argp_parser (int key, char *arg, struct argp_state *state)
ec4acbb
       arguments->prefix = xstrdup ("(memdisk)/boot/grub");
ec4acbb
       break;
ec4acbb
 
ec4acbb
+    case 'D':
ec4acbb
+      if (arguments->dtb)
ec4acbb
+	free (arguments->dtb);
ec4acbb
+
ec4acbb
+      arguments->dtb = xstrdup (arg);
ec4acbb
+      break;
ec4acbb
+
ec4acbb
     case 'k':
ec4acbb
       arguments->pubkeys = xrealloc (arguments->pubkeys,
ec4acbb
 				     sizeof (arguments->pubkeys[0])
ec4acbb
@@ -300,7 +309,7 @@ main (int argc, char *argv[])
ec4acbb
 			       arguments.memdisk, arguments.pubkeys,
ec4acbb
 			       arguments.npubkeys, arguments.config,
ec4acbb
 			       arguments.image_target, arguments.note,
ec4acbb
-			       arguments.comp);
ec4acbb
+			       arguments.comp, arguments.dtb);
ec4acbb
 
ec4acbb
   grub_util_file_sync  (fp);
ec4acbb
   fclose (fp);
ec4acbb
diff --git a/util/mkimage.c b/util/mkimage.c
ec4acbb
index 6aa77ed7367..e22d82afa61 100644
ec4acbb
--- a/util/mkimage.c
ec4acbb
+++ b/util/mkimage.c
ec4acbb
@@ -777,13 +777,12 @@ grub_install_generate_image (const char *dir, const char *prefix,
ec4acbb
 			     char *memdisk_path, char **pubkey_paths,
ec4acbb
 			     size_t npubkeys, char *config_path,
ec4acbb
 			     const struct grub_install_image_target_desc *image_target,
ec4acbb
-			     int note,
ec4acbb
-			     grub_compression_t comp)
ec4acbb
+			     int note, grub_compression_t comp, const char *dtb_path)
ec4acbb
 {
ec4acbb
   char *kernel_img, *core_img;
ec4acbb
   size_t total_module_size, core_size;
ec4acbb
   size_t memdisk_size = 0, config_size = 0;
ec4acbb
-  size_t prefix_size = 0;
ec4acbb
+  size_t prefix_size = 0, dtb_size = 0;
ec4acbb
   char *kernel_path;
ec4acbb
   size_t offset;
ec4acbb
   struct grub_util_path_list *path_list, *p;
ec4acbb
@@ -828,6 +827,12 @@ grub_install_generate_image (const char *dir, const char *prefix,
ec4acbb
       total_module_size += memdisk_size + sizeof (struct grub_module_header);
ec4acbb
     }
ec4acbb
 
ec4acbb
+  if (dtb_path)
ec4acbb
+    {
ec4acbb
+      dtb_size = ALIGN_UP(grub_util_get_image_size (dtb_path), 4);
ec4acbb
+      total_module_size += dtb_size + sizeof (struct grub_module_header);
ec4acbb
+    }
ec4acbb
+
ec4acbb
   if (config_path)
ec4acbb
     {
ec4acbb
       config_size = ALIGN_ADDR (grub_util_get_image_size (config_path) + 1);
ec4acbb
@@ -950,6 +955,19 @@ grub_install_generate_image (const char *dir, const char *prefix,
ec4acbb
       offset += memdisk_size;
ec4acbb
     }
ec4acbb
 
ec4acbb
+  if (dtb_path)
ec4acbb
+    {
ec4acbb
+      struct grub_module_header *header;
ec4acbb
+
ec4acbb
+      header = (struct grub_module_header *) (kernel_img + offset);
ec4acbb
+      header->type = grub_host_to_target32 (OBJ_TYPE_DTB);
ec4acbb
+      header->size = grub_host_to_target32 (dtb_size + sizeof (*header));
ec4acbb
+      offset += sizeof (*header);
ec4acbb
+
ec4acbb
+      grub_util_load_image (dtb_path, kernel_img + offset);
ec4acbb
+      offset += dtb_size;
ec4acbb
+    }
ec4acbb
+
ec4acbb
   if (config_path)
ec4acbb
     {
ec4acbb
       struct grub_module_header *header;
bc092b9
diff --git a/include/grub/fdt.h b/include/grub/fdt.h
ec4acbb
index fdfca75bf48..75525fa317c 100644
bc092b9
--- a/include/grub/fdt.h
bc092b9
+++ b/include/grub/fdt.h
bc092b9
@@ -20,6 +20,7 @@
bc092b9
 #define GRUB_FDT_HEADER	1
bc092b9
 
bc092b9
 #include <grub/types.h>
bc092b9
+#include <grub/symbol.h>
bc092b9
 
bc092b9
 #define FDT_MAGIC 0xD00DFEED
bc092b9
 
bc092b9
@@ -95,16 +96,22 @@ struct grub_fdt_empty_tree {
bc092b9
 #define grub_fdt_set_size_dt_struct(fdt, value)	\
bc092b9
 	grub_fdt_set_header(fdt, size_dt_struct, value)
bc092b9
 
bc092b9
-int grub_fdt_create_empty_tree (void *fdt, unsigned int size);
bc092b9
-int grub_fdt_check_header (void *fdt, unsigned int size);
bc092b9
-int grub_fdt_check_header_nosize (void *fdt);
bc092b9
-int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset,
bc092b9
-			   const char *name);
bc092b9
-int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,
bc092b9
+int EXPORT_FUNC(grub_fdt_create_empty_tree) (void *fdt, unsigned int size);
bc092b9
+int EXPORT_FUNC(grub_fdt_check_header) (const void *fdt, unsigned int size);
bc092b9
+int EXPORT_FUNC(grub_fdt_check_header_nosize) (const void *fdt);
bc092b9
+int EXPORT_FUNC(grub_fdt_find_subnode) (const void *fdt, unsigned int parentoffset,
bc092b9
+					const char *name);
bc092b9
+int EXPORT_FUNC(grub_fdt_first_node) (const void *fdt, unsigned int parentoffset);
bc092b9
+int EXPORT_FUNC(grub_fdt_next_node) (const void *fdt, unsigned int currentoffset);
bc092b9
+int EXPORT_FUNC(grub_fdt_add_subnode) (void *fdt, unsigned int parentoffset,
bc092b9
 			  const char *name);
bc092b9
+const char *
bc092b9
+EXPORT_FUNC(grub_fdt_get_nodename) (const void *fdt, unsigned int nodeoffset);
bc092b9
+const void *EXPORT_FUNC(grub_fdt_get_prop) (const void *fdt, unsigned int nodeoffset, const char *name,
bc092b9
+					    grub_uint32_t *len);
bc092b9
 
bc092b9
-int grub_fdt_set_prop (void *fdt, unsigned int nodeoffset, const char *name,
bc092b9
-		      const void *val, grub_uint32_t len);
bc092b9
+int EXPORT_FUNC(grub_fdt_set_prop) (void *fdt, unsigned int nodeoffset, const char *name,
bc092b9
+				    const void *val, grub_uint32_t len);
bc092b9
 #define grub_fdt_set_prop32(fdt, nodeoffset, name, val)	\
bc092b9
 ({ \
bc092b9
   grub_uint32_t _val = grub_cpu_to_be32(val); \
bc092b9
diff --git a/include/grub/fdtbus.h b/include/grub/fdtbus.h
bc092b9
new file mode 100644
ec4acbb
index 00000000000..985837e55f6
bc092b9
--- /dev/null
bc092b9
+++ b/include/grub/fdtbus.h
bc092b9
@@ -0,0 +1,73 @@
bc092b9
+/*
bc092b9
+ *  GRUB  --  GRand Unified Bootloader
bc092b9
+ *  Copyright (C) 2016  Free Software Foundation, Inc.
bc092b9
+ *
bc092b9
+ *  GRUB is free software: you can redistribute it and/or modify
bc092b9
+ *  it under the terms of the GNU General Public License as published by
bc092b9
+ *  the Free Software Foundation, either version 3 of the License, or
bc092b9
+ *  (at your option) any later version.
bc092b9
+ *
bc092b9
+ *  GRUB is distributed in the hope that it will be useful,
bc092b9
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
bc092b9
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
bc092b9
+ *  GNU General Public License for more details.
bc092b9
+ *
bc092b9
+ *  You should have received a copy of the GNU General Public License
bc092b9
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
bc092b9
+ */
bc092b9
+
bc092b9
+#ifndef GRUB_FDTBUS_HEADER
bc092b9
+#define GRUB_FDTBUS_HEADER	1
bc092b9
+
bc092b9
+#include <grub/fdt.h>
bc092b9
+#include <grub/err.h>
bc092b9
+
bc092b9
+struct grub_fdtbus_dev;
bc092b9
+
bc092b9
+struct grub_fdtbus_driver
bc092b9
+{
bc092b9
+  struct grub_fdtbus_driver *next;
bc092b9
+  struct grub_fdtbus_driver **prev;
bc092b9
+
bc092b9
+  const char *compatible;
bc092b9
+
bc092b9
+  grub_err_t (*attach) (const struct grub_fdtbus_dev *dev);
bc092b9
+  void (*detach) (const struct grub_fdtbus_dev *dev);
bc092b9
+};
bc092b9
+
bc092b9
+extern char EXPORT_VAR(grub_fdtbus_invalid_mapping)[1];
bc092b9
+
bc092b9
+static inline int
bc092b9
+grub_fdtbus_is_mapping_valid (volatile void *m)
bc092b9
+{
bc092b9
+  return m != grub_fdtbus_invalid_mapping;
bc092b9
+}
bc092b9
+
bc092b9
+volatile void *
bc092b9
+EXPORT_FUNC(grub_fdtbus_map_reg) (const struct grub_fdtbus_dev *dev, int reg, grub_size_t *size);
bc092b9
+
bc092b9
+const void *
bc092b9
+EXPORT_FUNC(grub_fdtbus_get_fdt) (void);
bc092b9
+
bc092b9
+const char *
bc092b9
+EXPORT_FUNC(grub_fdtbus_get_name) (const struct grub_fdtbus_dev *dev);
bc092b9
+
bc092b9
+const void *
bc092b9
+EXPORT_FUNC(grub_fdtbus_get_prop) (const struct grub_fdtbus_dev *dev,
bc092b9
+		      const char *name,
bc092b9
+		      grub_uint32_t *len);
bc092b9
+
bc092b9
+void
bc092b9
+EXPORT_FUNC(grub_fdtbus_register) (struct grub_fdtbus_driver *driver);
bc092b9
+
bc092b9
+void
bc092b9
+EXPORT_FUNC(grub_fdtbus_unregister) (struct grub_fdtbus_driver *driver);
bc092b9
+
bc092b9
+/* Must be called before any register(). */
bc092b9
+/* dtb is assumed to be unfreeable and must remain
bc092b9
+   valid for lifetime of GRUB.
bc092b9
+ */
bc092b9
+void
bc092b9
+grub_fdtbus_init (const void *dtb, grub_size_t size);
bc092b9
+
bc092b9
+#endif
bc092b9
diff --git a/include/grub/kernel.h b/include/grub/kernel.h
ec4acbb
index 20ddf2da297..ecd88ca72c6 100644
bc092b9
--- a/include/grub/kernel.h
bc092b9
+++ b/include/grub/kernel.h
bc092b9
@@ -28,7 +28,8 @@ enum
bc092b9
   OBJ_TYPE_MEMDISK,
bc092b9
   OBJ_TYPE_CONFIG,
bc092b9
   OBJ_TYPE_PREFIX,
bc092b9
-  OBJ_TYPE_PUBKEY
bc092b9
+  OBJ_TYPE_PUBKEY,
bc092b9
+  OBJ_TYPE_DTB
bc092b9
 };
bc092b9
 
bc092b9
 /* The module header.  */
bc092b9
diff --git a/include/grub/util/install.h b/include/grub/util/install.h
ec4acbb
index 5ca4811cd13..6abd288c313 100644
bc092b9
--- a/include/grub/util/install.h
bc092b9
+++ b/include/grub/util/install.h
bc092b9
@@ -176,7 +176,7 @@ grub_install_generate_image (const char *dir, const char *prefix,
bc092b9
 			     char *config_path,
bc092b9
 			     const struct grub_install_image_target_desc *image_target,
bc092b9
 			     int note,
bc092b9
-			     grub_compression_t comp);
bc092b9
+			     grub_compression_t comp, const char *dtb_file);
bc092b9
 
bc092b9
 const struct grub_install_image_target_desc *
bc092b9
 grub_install_get_image_target (const char *arg);
ec4acbb
diff --git a/conf/Makefile.common b/conf/Makefile.common
ec4acbb
index 11296b550a7..311da61c6c5 100644
ec4acbb
--- a/conf/Makefile.common
ec4acbb
+++ b/conf/Makefile.common
ec4acbb
@@ -86,9 +86,11 @@ CPPFLAGS_TERMINAL_LIST += '-Dgrub_term_register_output(...)=OUTPUT_TERMINAL_LIST
ec4acbb
 CPPFLAGS_COMMAND_LIST = '-Dgrub_register_command(...)=COMMAND_LIST_MARKER(__VA_ARGS__)'
ec4acbb
 CPPFLAGS_COMMAND_LIST += '-Dgrub_register_extcmd(...)=EXTCOMMAND_LIST_MARKER(__VA_ARGS__)'
ec4acbb
 CPPFLAGS_COMMAND_LIST += '-Dgrub_register_command_p1(...)=P1COMMAND_LIST_MARKER(__VA_ARGS__)'
ec4acbb
+CPPFLAGS_FDT_LIST := '-Dgrub_fdtbus_register(...)=FDT_DRIVER_LIST_MARKER(__VA_ARGS__)'
ec4acbb
 CPPFLAGS_MARKER = $(CPPFLAGS_FS_LIST) $(CPPFLAGS_VIDEO_LIST) \
ec4acbb
 	$(CPPFLAGS_PARTTOOL_LIST) $(CPPFLAGS_PARTMAP_LIST) \
ec4acbb
-	$(CPPFLAGS_TERMINAL_LIST) $(CPPFLAGS_COMMAND_LIST)
ec4acbb
+	$(CPPFLAGS_TERMINAL_LIST) $(CPPFLAGS_COMMAND_LIST) \
ec4acbb
+	$(CPPFLAGS_FDT_LIST)
bc092b9
 
ec4acbb
 # Define these variables to calm down automake
bc092b9
 
ec4acbb
diff --git a/grub-core/Makefile.am b/grub-core/Makefile.am
ec4acbb
index bec0585549a..fc6ca305147 100644
ec4acbb
--- a/grub-core/Makefile.am
ec4acbb
+++ b/grub-core/Makefile.am
ec4acbb
@@ -368,6 +368,16 @@ terminal.lst: $(MARKER_FILES)
ec4acbb
 platform_DATA += terminal.lst
ec4acbb
 CLEANFILES += terminal.lst
bc092b9
 
ec4acbb
+fdt.lst: $(MARKER_FILES)
ec4acbb
+	(for pp in $^; do \
ec4acbb
+	  b=`basename $$pp .marker`; \
ec4acbb
+	  sed -n \
ec4acbb
+	    -e "/FDT_DRIVER_LIST_MARKER *( *\"/{s/.*( *\"\([^\"]*\)\".*/i\1: $$b/;p;}" \
ec4acbb
+	    -e "/FDT_DRIVER_LIST_MARKER *( *\"/{s/.*( *\"\([^\"]*\)\".*/o\1: $$b/;p;}" $$pp; \
ec4acbb
+	done) | sort -u > $@
ec4acbb
+platform_DATA += fdt.lst
ec4acbb
+CLEANFILES += fdt.lst
bc092b9
+
ec4acbb
 parttool.lst: $(MARKER_FILES)
ec4acbb
 	(for pp in $^; do \
ec4acbb
 	  b=`basename $$pp .marker`; \
bc092b9
-- 
ec4acbb
2.15.0
bc092b9