Patchwork [U-Boot,v3] powerpc/85xx: introduce 'fdt verify' command

login
register
mail settings
Submitter Timur Tabi
Date Nov. 17, 2010, 7:15 p.m.
Message ID <1290021332-21158-1-git-send-email-timur@freescale.com>
Download mbox | patch
Permalink /patch/71614/
State Changes Requested
Delegated to: Kumar Gala
Headers show

Comments

Timur Tabi - Nov. 17, 2010, 7:15 p.m.
Introduce the 'fdt verify' command, which verifies some of the physical
addresses in the device tree.

U-Boot uses macros to determine where devices should be located in physical
memory, and Linux uses the device tree to determine where the devices are
actually located.  However, U-Boot does not update the device tree with those
addresses when it boots the operating system.  This means that it's possible
to use a device tree with the wrong addresses, and U-Boot won't complain.
This frequently happens when U-Boot is compiled for 32-bit physical addresses,
but the device tree uses 36-bit addresses.

It's not safe or practical to have U-Boot update the addresses in the device
tree, mostly because U-Boot is not aware of all the devices.  We can, however,
spot-check a few common devices to see if the addresses match, and then display
a warning if they do not.

Signed-off-by: Timur Tabi <timur@freescale.com>
---
 arch/powerpc/cpu/mpc85xx/fdt.c |  170 ++++++++++++++++++++++++++++++++++++++++
 common/cmd_fdt.c               |   21 +++++-
 common/fdt_support.c           |  170 ++++++++++++++++++++++++++++++++++++++++
 include/fdt_support.h          |   28 +++++++
 4 files changed, 387 insertions(+), 2 deletions(-)

Patch

diff --git a/arch/powerpc/cpu/mpc85xx/fdt.c b/arch/powerpc/cpu/mpc85xx/fdt.c
index 53e0596..ede33e7 100644
--- a/arch/powerpc/cpu/mpc85xx/fdt.c
+++ b/arch/powerpc/cpu/mpc85xx/fdt.c
@@ -393,6 +393,176 @@  static void ft_fixup_qe_snum(void *blob)
 }
 #endif
 
+/*
+ * For some CCSR devices, we only have the virtual address, not the physical
+ * address.  This is because we map CCSR as a whole, so we typically don't need
+ * a macro for the physical address of any device within CCSR.  In this case,
+ * we calculate the physical address of that device using it's the difference
+ * between the virtual address of the device and the virtual address of the
+ * beginning of CCSR.
+ */
+#define CCSR_VIRT_TO_PHYS(x) \
+	(CONFIG_SYS_CCSRBAR_PHYS + ((x) - CONFIG_SYS_CCSRBAR))
+
+#ifdef CONFIG_PCI
+
+/*
+ * Verify the addresses for all of the PCI controllers
+ *
+ * PCI is complicated because there is no correlation between the numbering
+ * of the controllers by U-Boot and the numbering the device tree.  So we need
+ * to search all of the aliases until we find a patch
+ */
+static void fdt_verify_pci_aliases(void *blob)
+{
+	int off;
+
+	for (off = fdt_next_pci_node(blob, -1); off != -FDT_ERR_NOTFOUND;
+	     off = fdt_next_pci_node(blob, off)) {
+		const u32 *reg;
+		u64 addr;
+
+		reg = fdt_getprop(blob, off, "reg", NULL);
+		if (!reg || !*reg)
+			continue;
+
+		addr = fdt_translate_address(blob, off, reg);
+		if (!addr) {
+			/* We can't determine the base address, so skip it */
+			continue;
+		}
+
+#ifdef CONFIG_SYS_PCI1_MEM_PHYS
+		if (addr == CCSR_VIRT_TO_PHYS(CONFIG_SYS_PCI1_ADDR)) {
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCI1_MEM_PHYS,
+				CONFIG_SYS_PCI1_MEM_SIZE, 0);
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCI1_IO_PHYS,
+				CONFIG_SYS_PCI1_IO_SIZE, 1);
+			continue;
+		}
+#endif
+#ifdef CONFIG_SYS_PCI2_MEM_PHYS
+		if (addr == CCSR_VIRT_TO_PHYS(CONFIG_SYS_PCI2_ADDR)) {
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCI2_MEM_PHYS,
+				CONFIG_SYS_PCI2_MEM_SIZE, 0);
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCI2_IO_PHYS,
+				CONFIG_SYS_PCI2_IO_SIZE, 1);
+			continue;
+		}
+#endif
+#ifdef CONFIG_SYS_PCIE1_MEM_PHYS
+		if (addr == CCSR_VIRT_TO_PHYS(CONFIG_SYS_PCIE1_ADDR)) {
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE1_MEM_PHYS,
+				CONFIG_SYS_PCIE1_MEM_SIZE, 0);
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE1_IO_PHYS,
+				CONFIG_SYS_PCIE1_IO_SIZE, 1);
+			continue;
+		}
+#endif
+#ifdef CONFIG_SYS_PCIE2_MEM_PHYS
+		if (addr == CCSR_VIRT_TO_PHYS(CONFIG_SYS_PCIE2_ADDR)) {
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE2_MEM_PHYS,
+				CONFIG_SYS_PCIE2_MEM_SIZE, 0);
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE2_IO_PHYS,
+				CONFIG_SYS_PCIE2_IO_SIZE, 1);
+			continue;
+		}
+#endif
+#ifdef CONFIG_SYS_PCIE3_MEM_PHYS
+		if (addr == CCSR_VIRT_TO_PHYS(CONFIG_SYS_PCIE3_ADDR)) {
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE3_MEM_PHYS,
+				CONFIG_SYS_PCIE3_MEM_SIZE, 0);
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE3_IO_PHYS,
+				CONFIG_SYS_PCIE3_IO_SIZE, 1);
+			continue;
+		}
+#endif
+#ifdef CONFIG_SYS_PCIE4_MEM_PHYS
+		if (addr == CCSR_VIRT_TO_PHYS(CONFIG_SYS_PCIE4_ADDR)) {
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE4_MEM_PHYS,
+				CONFIG_SYS_PCIE4_MEM_SIZE, 0);
+			fdt_check_pci_addresses(blob, off,
+				CONFIG_SYS_PCIE4_IO_PHYS,
+				CONFIG_SYS_PCIE4_IO_SIZE, 1);
+			continue;
+		}
+#endif
+
+		printf("Warning: node %s is set at address %llx,\n"
+		       "but U-Boot has not configured any PCI(e) device at "
+		       "that address.\n", fdt_get_name(blob, off, NULL), addr);
+	}
+}
+
+#endif /* #ifdef CONFIG_PCI */
+
+/*
+ * Verify the device addresses in the device tree
+ *
+ * This function compares several CONFIG_xxx macros that contain physical
+ * addresses with the corresponding nodes in the device tree, to see if
+ * the physical addresses are all correct.  For example, if
+ * CONFIG_SYS_NS16550_COM1 is defined, then it contains the virtual address
+ * of the first UART.  We convert this to a physical address and compare
+ * that with the physical address of the first ns16550-compatible node
+ * in the device tree.  If they don't match, then we display a warning.
+ */
+void fdt_verify_addresses(void *blob)
+{
+	uint64_t ccsr = 0;
+	int aliases;
+	int off;
+
+	/* First check the CCSR base address */
+	off = fdt_node_offset_by_prop_value(blob, -1, "device_type", "soc", 4);
+	if (off > 0)
+		ccsr = fdt_get_base_address(blob, off);
+
+	if (!ccsr) {
+		printf("Warning: could not determine base CCSR address in "
+		       "device tree\n");
+		return;
+	}
+
+	if (ccsr != CONFIG_SYS_CCSRBAR_PHYS) {
+		printf("Warning: U-Boot configured CCSR at address %llx, "
+		       "but the device tree has it at %llx\n",
+		       (uint64_t) CONFIG_SYS_CCSRBAR_PHYS, ccsr);
+	}
+
+	/*
+	 * Get the 'aliases' node.  If there isn't one, then there's nothing
+	 * left to do.
+	 */
+	aliases = fdt_path_offset(blob, "/aliases");
+	if (aliases > 0) {
+#ifdef CONFIG_SYS_NS16550_COM1
+		fdt_verify_alias_address(blob, aliases, "serial0",
+			CCSR_VIRT_TO_PHYS(CONFIG_SYS_NS16550_COM1));
+#endif
+
+#ifdef CONFIG_SYS_NS16550_COM2
+		fdt_verify_alias_address(blob, aliases, "serial1",
+			CCSR_VIRT_TO_PHYS(CONFIG_SYS_NS16550_COM2));
+#endif
+	}
+
+#ifdef CONFIG_PCI
+	fdt_verify_pci_aliases(blob);
+#endif
+}
+
 void ft_cpu_setup(void *blob, bd_t *bd)
 {
 	int off;
diff --git a/common/cmd_fdt.c b/common/cmd_fdt.c
index 3d0c2b7..65acaad 100644
--- a/common/cmd_fdt.c
+++ b/common/cmd_fdt.c
@@ -46,6 +46,20 @@  static int fdt_parse_prop(char *const*newval, int count, char *data, int *len);
 static int fdt_print(const char *pathp, char *prop, int depth);
 
 /*
+ * Verify the physical addresses in the device tree
+ *
+ * This CPU- or board-specific function compares the physical addresses of
+ * various devices in the device tree with the physical addresses as defined
+ * in the board header file.
+ */
+void __fdt_verify_addresses(void *blob)
+{
+}
+
+void fdt_verify_addresses(void *blob)
+	__attribute__((weak, alias("__fdt_verify_addresses")));
+
+/*
  * The working_fdt points to our working flattened device tree.
  */
 struct fdt_header *working_fdt;
@@ -435,8 +449,10 @@  int do_fdt (cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
 	/* resize the fdt */
 	else if (strncmp(argv[1], "re", 2) == 0) {
 		fdt_resize(working_fdt);
-	}
-	else {
+	} else if (argv[1][0] == 'v') {
+		/* verify the addresses in the fdt */
+		fdt_verify_addresses(working_fdt);
+	} else {
 		/* Unrecognized command */
 		return cmd_usage(cmdtp);
 	}
@@ -824,6 +840,7 @@  U_BOOT_CMD(
 	"fdt rsvmem delete <index>           - Delete a mem reserves\n"
 	"fdt chosen [<start> <end>]          - Add/update the /chosen branch in the tree\n"
 	"                                        <start>/<end> - initrd start/end addr\n"
+	"fdt verify                          - Verify the addresses in the device tree\n"
 	"NOTE: Dereference aliases by omiting the leading '/', "
 		"e.g. fdt print ethernet0."
 );
diff --git a/common/fdt_support.c b/common/fdt_support.c
index 5829afd..441bd3d 100644
--- a/common/fdt_support.c
+++ b/common/fdt_support.c
@@ -1223,3 +1223,173 @@  err_size:
 	return ret;
 }
 #endif
+
+/*
+ * Verify the physical address of device tree node for a given alias
+ *
+ * This function locates the device tree node of a given alias, and then
+ * verifies that the physical address of that device matches the given
+ * parameter.  It displays a message if there is a mismatch.
+ */
+void fdt_verify_alias_address(void *blob, int anode, const char *alias,
+			      phys_addr_t addr)
+{
+	const char *path;
+	const u32 *reg;
+	int node, len;
+	u64 dt_addr;
+
+	path = fdt_getprop(blob, anode, alias, NULL);
+	if (!path)
+		return;
+
+	node = fdt_path_offset(blob, path);
+	if (node < 0)
+		return;
+
+	reg = fdt_getprop(blob, node, "reg", &len);
+	if (!reg)
+		return;
+
+	dt_addr = fdt_translate_address(blob, node, reg);
+	if (addr != dt_addr) {
+		printf("Warning: U-Boot configured device %s at address %llx,\n"
+		       "but the device tree has it address %llx.\n",
+		       alias, (u64)addr, dt_addr);
+	}
+}
+
+/*
+ * Returns the base address of an SOC or PCI node
+ */
+u64 fdt_get_base_address(void *blob, int node)
+{
+	int size;
+	u32 naddr;
+	const u32 *prop;
+
+	prop = fdt_getprop(blob, node, "#address-cells", &size);
+	if (prop && size == 4)
+		naddr = *prop;
+	else
+		naddr = 2;
+
+	prop = fdt_getprop(blob, node, "ranges", &size);
+
+	return prop ? fdt_translate_address(blob, node, prop + naddr) : 0;
+}
+
+#ifdef CONFIG_PCI
+
+#define PCI_CELL0_SS_MASK	0x3000000
+#define PCI_CELL0_SS_IO		0x1000000
+
+/*
+ * Verify the memory and I/O addresses in a PCI node
+ *
+ * This function scans the 'ranges' property of a PCI node and looks for a
+ * match to the given parameters.  If there's an error or a mismatch, a message
+ * is displayed.
+ */
+void fdt_check_pci_addresses(void *blob, int node, phys_addr_t addr,
+			     phys_addr_t size, int is_io)
+{
+	unsigned int address_cells, parent_address_cells, size_cells;
+	u64 dt_addr, dt_size;
+	u32 attr;	/* The first cell is the PCI attributes */
+	const u32 *ranges;
+	const u32 *prop;
+	unsigned int i, count;
+	int len, row_size;
+
+	/* Get the address and size cells that we need */
+	address_cells = fdt_get_address_cells(blob, node);
+	parent_address_cells =
+		fdt_get_address_cells(blob, fdt_parent_offset(blob, node));
+
+	prop = fdt_getprop(blob, node, "#size-cells", NULL);
+	size_cells = prop ? *prop : 1;
+
+	ranges = fdt_getprop(blob, node, "ranges", &len);
+	if (!ranges) {
+		printf("Warning: node %s is missing 'ranges' property\n",
+		       fdt_get_name(blob, node, NULL));
+		return;
+	}
+
+	/* Make sure the 'ranges' property is the right size*/
+	row_size = 4 * (address_cells + parent_address_cells + size_cells);
+	if (len % row_size) {
+		printf("Warning: 'ranges' property of node %s is malformed\n",
+		       fdt_get_name(blob, node, NULL));
+		return;
+	}
+
+	count = len / row_size;
+
+	for (i = 0; i < count; i++) {
+		/* Parse one line of the 'ranges' property */
+		attr = *ranges;
+		if (parent_address_cells == 1) {
+			dt_addr = be32_to_cpup(ranges + address_cells);
+		} else {
+			/* parent_address_cells == 2 */
+			dt_addr = be64_to_cpup(ranges + address_cells);
+		}
+		if (size_cells == 1) {
+			dt_size = be32_to_cpup(ranges + address_cells +
+					       parent_address_cells);
+		} else {
+			/* size_cells == 2 */
+			dt_size = be64_to_cpup(ranges + address_cells +
+					      parent_address_cells);
+		}
+
+		/*
+		 * Check for matches.  If the address matches but is the wrong
+		 * type or wrong size, then return an error.
+		 */
+		if (dt_addr == addr) {
+			if ((attr & PCI_CELL0_SS_MASK) == PCI_CELL0_SS_IO) {
+				if (is_io) {
+					if (dt_size == size)
+						return;
+					else
+						goto wrong_size;
+				} else {
+					goto wrong_type;
+				}
+			} else {
+				if (!is_io) {
+					if (dt_size == size)
+						return;
+					else
+						goto wrong_size;
+				} else {
+					goto wrong_type;
+				}
+			}
+		}
+		ranges += address_cells + parent_address_cells + size_cells;
+	}
+
+	printf("Warning: node %s has a missing or incorrect %s region at\n"
+	       "address=%llx size=%llx\n",
+	       fdt_get_name(blob, node, NULL), is_io ? "an I/O" : "a memory",
+	       (u64)addr, (u64)size);
+	return;
+
+wrong_type:
+	printf("Warning: node %s has 'ranges' property for address %llx and\n"
+	       "size %llx, but of the wrong type\n",
+	       fdt_get_name(blob, node, NULL), (u64)dt_addr, (u64)dt_size);
+	return;
+
+wrong_size:
+	printf("Warning: node %s has 'ranges' property for address %llx but\n"
+	       "the wrong size (%llx, but U-Boot has %llx)\n",
+	       fdt_get_name(blob, node, NULL),
+	       (u64)dt_addr, (u64)dt_size, (u64)size);
+}
+
+#endif /* #ifdef CONFIG_PCI */
diff --git a/include/fdt_support.h b/include/fdt_support.h
index ce6817b..7ef73d5 100644
--- a/include/fdt_support.h
+++ b/include/fdt_support.h
@@ -90,5 +90,33 @@  int fdt_node_offset_by_compat_reg(void *blob, const char *compat,
 int fdt_alloc_phandle(void *blob);
 int fdt_add_edid(void *blob, const char *compat, unsigned char *buf);
 
+void fdt_verify_alias_address(void *blob, int anode, const char *alias,
+			      phys_addr_t addr);
+void fdt_check_pci_addresses(void *blob, int node, phys_addr_t addr,
+			     phys_addr_t size, int is_io);
+u64 fdt_get_base_address(void *blob, int node);
+
+/*
+ * Returns the next PCI node in the device tree
+ */
+static inline int fdt_next_pci_node(void *blob, int off)
+{
+	return fdt_node_offset_by_prop_value(blob, off,
+					     "device_type", "pci", 4);
+}
+
+/*
+ * Returns the value of the address cells for the given node
+ *
+ * If a given node does not have an #address-cells property, then the default
+ * value of '2' is returned.
+ */
+static inline unsigned int fdt_get_address_cells(void *blob, int node)
+{
+	const u32 *prop = fdt_getprop(blob, node, "#address-cells", NULL);
+
+	return prop ? *prop : 2;
+}
+
 #endif /* ifdef CONFIG_OF_LIBFDT */
 #endif /* ifndef __FDT_SUPPORT_H */