[U-Boot] [PATCH] powerpc/85xx: compare actual device addresses with the device tree

Timur Tabi timur at freescale.com
Wed Nov 10 00:01:49 CET 2010


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 at freescale.com>
---
 arch/powerpc/cpu/mpc85xx/fdt.c |  351 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 351 insertions(+), 0 deletions(-)

diff --git a/arch/powerpc/cpu/mpc85xx/fdt.c b/arch/powerpc/cpu/mpc85xx/fdt.c
index 53e0596..7bd9423 100644
--- a/arch/powerpc/cpu/mpc85xx/fdt.c
+++ b/arch/powerpc/cpu/mpc85xx/fdt.c
@@ -393,6 +393,354 @@ 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
+
+/*
+ * 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 unsigned int fdt_get_address_cells(void *blob, int node)
+{
+	const u32 *prop = fdt_getprop(blob, node, "#address-cells", NULL);
+
+	return prop ? *prop : 2;
+}
+
+#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.
+ */
+static 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 ((attr & PCI_CELL0_SS_MASK) == PCI_CELL0_SS_IO) {
+			if (is_io && (dt_addr == addr)) {
+				if (dt_size == size)
+					return;
+				else
+					goto wrong_size;
+			}
+			if (!is_io && (dt_addr == addr))
+				goto wrong_type;
+		} else {
+			if (!is_io && (dt_addr == addr)) {
+				if (dt_size == size)
+					return;
+				else
+					goto wrong_size;
+			}
+			if (is_io && (dt_addr == addr))
+				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);
+}
+
+/*
+ * Returns the base address of an SOC or PCI node
+ */
+static 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;
+}
+
+static int fdt_next_pci_node(void *blob, int off)
+{
+	return fdt_node_offset_by_prop_value(blob, off,
+					     "device_type", "pci", 4);
+}
+
+/*
+ * 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 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.
+ */
+static 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);
+}
+
+/*
+ * 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.
+ */
+static 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;
@@ -478,4 +826,7 @@ void ft_cpu_setup(void *blob, bd_t *bd)
 
 	fdt_fixup_qportals(blob);
 #endif
+
+	/* Now that we've done all the fixups, check the device tree */
+	fdt_verify_addresses(blob);
 }
-- 
1.7.2.3




More information about the U-Boot mailing list