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

Timur Tabi
2010-11-09T23:01:49Z

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 | 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@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