18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * FDT Address translation based on u-boot fdt_support.c which in turn was 48c2ecf20Sopenharmony_ci * based on the kernel unflattened DT address translation code. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * (C) Copyright 2007 78c2ecf20Sopenharmony_ci * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright 2010-2011 Freescale Semiconductor, Inc. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "OF: fdt: " fmt 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/libfdt.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/of_fdt.h> 188c2ecf20Sopenharmony_ci#include <linux/sizes.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/* Max address size we deal with */ 218c2ecf20Sopenharmony_ci#define OF_MAX_ADDR_CELLS 4 228c2ecf20Sopenharmony_ci#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \ 238c2ecf20Sopenharmony_ci (ns) > 0) 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* Debug utility */ 268c2ecf20Sopenharmony_ci#ifdef DEBUG 278c2ecf20Sopenharmony_cistatic void __init of_dump_addr(const char *s, const __be32 *addr, int na) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci pr_debug("%s", s); 308c2ecf20Sopenharmony_ci while(na--) 318c2ecf20Sopenharmony_ci pr_cont(" %08x", *(addr++)); 328c2ecf20Sopenharmony_ci pr_cont("\n"); 338c2ecf20Sopenharmony_ci} 348c2ecf20Sopenharmony_ci#else 358c2ecf20Sopenharmony_cistatic void __init of_dump_addr(const char *s, const __be32 *addr, int na) { } 368c2ecf20Sopenharmony_ci#endif 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* Callbacks for bus specific translators */ 398c2ecf20Sopenharmony_cistruct of_bus { 408c2ecf20Sopenharmony_ci void (*count_cells)(const void *blob, int parentoffset, 418c2ecf20Sopenharmony_ci int *addrc, int *sizec); 428c2ecf20Sopenharmony_ci u64 (*map)(__be32 *addr, const __be32 *range, 438c2ecf20Sopenharmony_ci int na, int ns, int pna); 448c2ecf20Sopenharmony_ci int (*translate)(__be32 *addr, u64 offset, int na); 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/* Default translator (generic bus) */ 488c2ecf20Sopenharmony_cistatic void __init fdt_bus_default_count_cells(const void *blob, int parentoffset, 498c2ecf20Sopenharmony_ci int *addrc, int *sizec) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci const __be32 *prop; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci if (addrc) { 548c2ecf20Sopenharmony_ci prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL); 558c2ecf20Sopenharmony_ci if (prop) 568c2ecf20Sopenharmony_ci *addrc = be32_to_cpup(prop); 578c2ecf20Sopenharmony_ci else 588c2ecf20Sopenharmony_ci *addrc = dt_root_addr_cells; 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci if (sizec) { 628c2ecf20Sopenharmony_ci prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL); 638c2ecf20Sopenharmony_ci if (prop) 648c2ecf20Sopenharmony_ci *sizec = be32_to_cpup(prop); 658c2ecf20Sopenharmony_ci else 668c2ecf20Sopenharmony_ci *sizec = dt_root_size_cells; 678c2ecf20Sopenharmony_ci } 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic u64 __init fdt_bus_default_map(__be32 *addr, const __be32 *range, 718c2ecf20Sopenharmony_ci int na, int ns, int pna) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci u64 cp, s, da; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci cp = of_read_number(range, na); 768c2ecf20Sopenharmony_ci s = of_read_number(range + na + pna, ns); 778c2ecf20Sopenharmony_ci da = of_read_number(addr, na); 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci pr_debug("default map, cp=%llx, s=%llx, da=%llx\n", 808c2ecf20Sopenharmony_ci cp, s, da); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci if (da < cp || da >= (cp + s)) 838c2ecf20Sopenharmony_ci return OF_BAD_ADDR; 848c2ecf20Sopenharmony_ci return da - cp; 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int __init fdt_bus_default_translate(__be32 *addr, u64 offset, int na) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci u64 a = of_read_number(addr, na); 908c2ecf20Sopenharmony_ci memset(addr, 0, na * 4); 918c2ecf20Sopenharmony_ci a += offset; 928c2ecf20Sopenharmony_ci if (na > 1) 938c2ecf20Sopenharmony_ci addr[na - 2] = cpu_to_fdt32(a >> 32); 948c2ecf20Sopenharmony_ci addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci return 0; 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci/* Array of bus specific translators */ 1008c2ecf20Sopenharmony_cistatic const struct of_bus of_busses[] __initconst = { 1018c2ecf20Sopenharmony_ci /* Default */ 1028c2ecf20Sopenharmony_ci { 1038c2ecf20Sopenharmony_ci .count_cells = fdt_bus_default_count_cells, 1048c2ecf20Sopenharmony_ci .map = fdt_bus_default_map, 1058c2ecf20Sopenharmony_ci .translate = fdt_bus_default_translate, 1068c2ecf20Sopenharmony_ci }, 1078c2ecf20Sopenharmony_ci}; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic int __init fdt_translate_one(const void *blob, int parent, 1108c2ecf20Sopenharmony_ci const struct of_bus *bus, 1118c2ecf20Sopenharmony_ci const struct of_bus *pbus, __be32 *addr, 1128c2ecf20Sopenharmony_ci int na, int ns, int pna, const char *rprop) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci const __be32 *ranges; 1158c2ecf20Sopenharmony_ci int rlen; 1168c2ecf20Sopenharmony_ci int rone; 1178c2ecf20Sopenharmony_ci u64 offset = OF_BAD_ADDR; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci ranges = fdt_getprop(blob, parent, rprop, &rlen); 1208c2ecf20Sopenharmony_ci if (!ranges) 1218c2ecf20Sopenharmony_ci return 1; 1228c2ecf20Sopenharmony_ci if (rlen == 0) { 1238c2ecf20Sopenharmony_ci offset = of_read_number(addr, na); 1248c2ecf20Sopenharmony_ci memset(addr, 0, pna * 4); 1258c2ecf20Sopenharmony_ci pr_debug("empty ranges, 1:1 translation\n"); 1268c2ecf20Sopenharmony_ci goto finish; 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci pr_debug("walking ranges...\n"); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* Now walk through the ranges */ 1328c2ecf20Sopenharmony_ci rlen /= 4; 1338c2ecf20Sopenharmony_ci rone = na + pna + ns; 1348c2ecf20Sopenharmony_ci for (; rlen >= rone; rlen -= rone, ranges += rone) { 1358c2ecf20Sopenharmony_ci offset = bus->map(addr, ranges, na, ns, pna); 1368c2ecf20Sopenharmony_ci if (offset != OF_BAD_ADDR) 1378c2ecf20Sopenharmony_ci break; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci if (offset == OF_BAD_ADDR) { 1408c2ecf20Sopenharmony_ci pr_debug("not found !\n"); 1418c2ecf20Sopenharmony_ci return 1; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci memcpy(addr, ranges + na, 4 * pna); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci finish: 1468c2ecf20Sopenharmony_ci of_dump_addr("parent translation for:", addr, pna); 1478c2ecf20Sopenharmony_ci pr_debug("with offset: %llx\n", offset); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci /* Translate it into parent bus space */ 1508c2ecf20Sopenharmony_ci return pbus->translate(addr, offset, pna); 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci/* 1548c2ecf20Sopenharmony_ci * Translate an address from the device-tree into a CPU physical address, 1558c2ecf20Sopenharmony_ci * this walks up the tree and applies the various bus mappings on the 1568c2ecf20Sopenharmony_ci * way. 1578c2ecf20Sopenharmony_ci * 1588c2ecf20Sopenharmony_ci * Note: We consider that crossing any level with #size-cells == 0 to mean 1598c2ecf20Sopenharmony_ci * that translation is impossible (that is we are not dealing with a value 1608c2ecf20Sopenharmony_ci * that can be mapped to a cpu physical address). This is not really specified 1618c2ecf20Sopenharmony_ci * that way, but this is traditionally the way IBM at least do things 1628c2ecf20Sopenharmony_ci */ 1638c2ecf20Sopenharmony_cistatic u64 __init fdt_translate_address(const void *blob, int node_offset) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci int parent, len; 1668c2ecf20Sopenharmony_ci const struct of_bus *bus, *pbus; 1678c2ecf20Sopenharmony_ci const __be32 *reg; 1688c2ecf20Sopenharmony_ci __be32 addr[OF_MAX_ADDR_CELLS]; 1698c2ecf20Sopenharmony_ci int na, ns, pna, pns; 1708c2ecf20Sopenharmony_ci u64 result = OF_BAD_ADDR; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci pr_debug("** translation for device %s **\n", 1738c2ecf20Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci reg = fdt_getprop(blob, node_offset, "reg", &len); 1768c2ecf20Sopenharmony_ci if (!reg) { 1778c2ecf20Sopenharmony_ci pr_err("warning: device tree node '%s' has no address.\n", 1788c2ecf20Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 1798c2ecf20Sopenharmony_ci goto bail; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* Get parent & match bus type */ 1838c2ecf20Sopenharmony_ci parent = fdt_parent_offset(blob, node_offset); 1848c2ecf20Sopenharmony_ci if (parent < 0) 1858c2ecf20Sopenharmony_ci goto bail; 1868c2ecf20Sopenharmony_ci bus = &of_busses[0]; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci /* Cound address cells & copy address locally */ 1898c2ecf20Sopenharmony_ci bus->count_cells(blob, parent, &na, &ns); 1908c2ecf20Sopenharmony_ci if (!OF_CHECK_COUNTS(na, ns)) { 1918c2ecf20Sopenharmony_ci pr_err("Bad cell count for %s\n", 1928c2ecf20Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 1938c2ecf20Sopenharmony_ci goto bail; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci memcpy(addr, reg, na * 4); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci pr_debug("bus (na=%d, ns=%d) on %s\n", 1988c2ecf20Sopenharmony_ci na, ns, fdt_get_name(blob, parent, NULL)); 1998c2ecf20Sopenharmony_ci of_dump_addr("translating address:", addr, na); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci /* Translate */ 2028c2ecf20Sopenharmony_ci for (;;) { 2038c2ecf20Sopenharmony_ci /* Switch to parent bus */ 2048c2ecf20Sopenharmony_ci node_offset = parent; 2058c2ecf20Sopenharmony_ci parent = fdt_parent_offset(blob, node_offset); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* If root, we have finished */ 2088c2ecf20Sopenharmony_ci if (parent < 0) { 2098c2ecf20Sopenharmony_ci pr_debug("reached root node\n"); 2108c2ecf20Sopenharmony_ci result = of_read_number(addr, na); 2118c2ecf20Sopenharmony_ci break; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci /* Get new parent bus and counts */ 2158c2ecf20Sopenharmony_ci pbus = &of_busses[0]; 2168c2ecf20Sopenharmony_ci pbus->count_cells(blob, parent, &pna, &pns); 2178c2ecf20Sopenharmony_ci if (!OF_CHECK_COUNTS(pna, pns)) { 2188c2ecf20Sopenharmony_ci pr_err("Bad cell count for %s\n", 2198c2ecf20Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 2208c2ecf20Sopenharmony_ci break; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci pr_debug("parent bus (na=%d, ns=%d) on %s\n", 2248c2ecf20Sopenharmony_ci pna, pns, fdt_get_name(blob, parent, NULL)); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* Apply bus translation */ 2278c2ecf20Sopenharmony_ci if (fdt_translate_one(blob, node_offset, bus, pbus, 2288c2ecf20Sopenharmony_ci addr, na, ns, pna, "ranges")) 2298c2ecf20Sopenharmony_ci break; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci /* Complete the move up one level */ 2328c2ecf20Sopenharmony_ci na = pna; 2338c2ecf20Sopenharmony_ci ns = pns; 2348c2ecf20Sopenharmony_ci bus = pbus; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci of_dump_addr("one level translation:", addr, na); 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci bail: 2398c2ecf20Sopenharmony_ci return result; 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci/** 2438c2ecf20Sopenharmony_ci * of_flat_dt_translate_address - translate DT addr into CPU phys addr 2448c2ecf20Sopenharmony_ci * @node: node in the flat blob 2458c2ecf20Sopenharmony_ci */ 2468c2ecf20Sopenharmony_ciu64 __init of_flat_dt_translate_address(unsigned long node) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci return fdt_translate_address(initial_boot_params, node); 2498c2ecf20Sopenharmony_ci} 250