| /* |
| * Copyright (c) 2018-2023, ARM Limited and Contributors. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| /* Helper functions to offer easier navigation of Device Tree Blob */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <libfdt.h> |
| |
| #include <common/debug.h> |
| #include <common/fdt_wrappers.h> |
| #include <common/uuid.h> |
| |
| /* |
| * Read cells from a given property of the given node. Any number of 32-bit |
| * cells of the property can be read. Returns 0 on success, or a negative |
| * FDT error value otherwise. |
| */ |
| int fdt_read_uint32_array(const void *dtb, int node, const char *prop_name, |
| unsigned int cells, uint32_t *value) |
| { |
| const fdt32_t *prop; |
| int value_len; |
| |
| assert(dtb != NULL); |
| assert(prop_name != NULL); |
| assert(value != NULL); |
| assert(node >= 0); |
| |
| /* Access property and obtain its length (in bytes) */ |
| prop = fdt_getprop(dtb, node, prop_name, &value_len); |
| if (prop == NULL) { |
| VERBOSE("Couldn't find property %s in dtb\n", prop_name); |
| return -FDT_ERR_NOTFOUND; |
| } |
| |
| /* Verify that property length can fill the entire array. */ |
| if (NCELLS((unsigned int)value_len) < cells) { |
| WARN("Property length mismatch\n"); |
| return -FDT_ERR_BADVALUE; |
| } |
| |
| for (unsigned int i = 0U; i < cells; i++) { |
| value[i] = fdt32_to_cpu(prop[i]); |
| } |
| |
| return 0; |
| } |
| |
| int fdt_read_uint32(const void *dtb, int node, const char *prop_name, |
| uint32_t *value) |
| { |
| return fdt_read_uint32_array(dtb, node, prop_name, 1, value); |
| } |
| |
| uint32_t fdt_read_uint32_default(const void *dtb, int node, |
| const char *prop_name, uint32_t dflt_value) |
| { |
| uint32_t ret = dflt_value; |
| int err = fdt_read_uint32(dtb, node, prop_name, &ret); |
| |
| if (err < 0) { |
| return dflt_value; |
| } |
| |
| return ret; |
| } |
| |
| int fdt_read_uint64(const void *dtb, int node, const char *prop_name, |
| uint64_t *value) |
| { |
| uint32_t array[2] = {0, 0}; |
| int ret; |
| |
| ret = fdt_read_uint32_array(dtb, node, prop_name, 2, array); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| *value = ((uint64_t)array[0] << 32) | array[1]; |
| return 0; |
| } |
| |
| /* |
| * Read bytes from a given property of the given node. Any number of |
| * bytes of the property can be read. The fdt pointer is updated. |
| * Returns 0 on success, and -1 on error. |
| */ |
| int fdtw_read_bytes(const void *dtb, int node, const char *prop, |
| unsigned int length, void *value) |
| { |
| const void *ptr; |
| int value_len; |
| |
| assert(dtb != NULL); |
| assert(prop != NULL); |
| assert(value != NULL); |
| assert(node >= 0); |
| |
| /* Access property and obtain its length (in bytes) */ |
| ptr = fdt_getprop_namelen(dtb, node, prop, (int)strlen(prop), |
| &value_len); |
| if (ptr == NULL) { |
| WARN("Couldn't find property %s in dtb\n", prop); |
| return -1; |
| } |
| |
| /* Verify that property length is not less than number of bytes */ |
| if ((unsigned int)value_len < length) { |
| WARN("Property length mismatch\n"); |
| return -1; |
| } |
| |
| (void)memcpy(value, ptr, length); |
| |
| return 0; |
| } |
| |
| /* |
| * Read string from a given property of the given node. Up to 'size - 1' |
| * characters are read, and a NUL terminator is added. Returns 0 on success, |
| * and -1 upon error. |
| */ |
| int fdtw_read_string(const void *dtb, int node, const char *prop, |
| char *str, size_t size) |
| { |
| const char *ptr; |
| size_t len; |
| |
| assert(dtb != NULL); |
| assert(node >= 0); |
| assert(prop != NULL); |
| assert(str != NULL); |
| assert(size > 0U); |
| |
| ptr = fdt_getprop_namelen(dtb, node, prop, (int)strlen(prop), NULL); |
| if (ptr == NULL) { |
| WARN("Couldn't find property %s in dtb\n", prop); |
| return -1; |
| } |
| |
| len = strlcpy(str, ptr, size); |
| if (len >= size) { |
| WARN("String of property %s in dtb has been truncated\n", prop); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Read UUID from a given property of the given node. Returns 0 on success, |
| * and a negative value upon error. |
| */ |
| int fdtw_read_uuid(const void *dtb, int node, const char *prop, |
| unsigned int length, uint8_t *uuid) |
| { |
| /* Buffer for UUID string (plus NUL terminator) */ |
| char uuid_string[UUID_STRING_LENGTH + 1U]; |
| int err; |
| |
| assert(dtb != NULL); |
| assert(prop != NULL); |
| assert(uuid != NULL); |
| assert(node >= 0); |
| |
| if (length < UUID_BYTES_LENGTH) { |
| return -EINVAL; |
| } |
| |
| err = fdtw_read_string(dtb, node, prop, uuid_string, |
| UUID_STRING_LENGTH + 1U); |
| if (err != 0) { |
| return err; |
| } |
| |
| if (read_uuid(uuid, uuid_string) != 0) { |
| return -FDT_ERR_BADVALUE; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Write cells in place to a given property of the given node. At most 2 cells |
| * of the property are written. Returns 0 on success, and -1 upon error. |
| */ |
| int fdtw_write_inplace_cells(void *dtb, int node, const char *prop, |
| unsigned int cells, void *value) |
| { |
| int err, len; |
| |
| assert(dtb != NULL); |
| assert(prop != NULL); |
| assert(value != NULL); |
| assert(node >= 0); |
| |
| /* We expect either 1 or 2 cell property */ |
| assert(cells <= 2U); |
| |
| if (cells == 2U) |
| *(fdt64_t *)value = cpu_to_fdt64(*(uint64_t *)value); |
| else |
| *(fdt32_t *)value = cpu_to_fdt32(*(uint32_t *)value); |
| |
| len = (int)cells * 4; |
| |
| /* Set property value in place */ |
| err = fdt_setprop_inplace(dtb, node, prop, value, len); |
| if (err != 0) { |
| WARN("Modify property %s failed with error %d\n", prop, err); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Write bytes in place to a given property of the given node. |
| * Any number of bytes of the property can be written. |
| * Returns 0 on success, and < 0 on error. |
| */ |
| int fdtw_write_inplace_bytes(void *dtb, int node, const char *prop, |
| unsigned int length, const void *data) |
| { |
| const void *ptr; |
| int namelen, value_len, err; |
| |
| assert(dtb != NULL); |
| assert(prop != NULL); |
| assert(data != NULL); |
| assert(node >= 0); |
| |
| namelen = (int)strlen(prop); |
| |
| /* Access property and obtain its length in bytes */ |
| ptr = fdt_getprop_namelen(dtb, node, prop, namelen, &value_len); |
| if (ptr == NULL) { |
| WARN("Couldn't find property %s in dtb\n", prop); |
| return -1; |
| } |
| |
| /* Verify that property length is not less than number of bytes */ |
| if ((unsigned int)value_len < length) { |
| WARN("Property length mismatch\n"); |
| return -1; |
| } |
| |
| /* Set property value in place */ |
| err = fdt_setprop_inplace_namelen_partial(dtb, node, prop, |
| namelen, 0, |
| data, (int)length); |
| if (err != 0) { |
| WARN("Set property %s failed with error %d\n", prop, err); |
| } |
| |
| return err; |
| } |
| |
| static uint64_t fdt_read_prop_cells(const fdt32_t *prop, int nr_cells) |
| { |
| uint64_t reg = fdt32_to_cpu(prop[0]); |
| |
| if (nr_cells > 1) { |
| reg = (reg << 32) | fdt32_to_cpu(prop[1]); |
| } |
| |
| return reg; |
| } |
| |
| int fdt_get_reg_props_by_index(const void *dtb, int node, int index, |
| uintptr_t *base, size_t *size) |
| { |
| const fdt32_t *prop; |
| int parent, len; |
| int ac, sc; |
| int cell; |
| |
| parent = fdt_parent_offset(dtb, node); |
| if (parent < 0) { |
| return -FDT_ERR_BADOFFSET; |
| } |
| |
| ac = fdt_address_cells(dtb, parent); |
| sc = fdt_size_cells(dtb, parent); |
| |
| cell = index * (ac + sc); |
| |
| prop = fdt_getprop(dtb, node, "reg", &len); |
| if (prop == NULL) { |
| WARN("Couldn't find \"reg\" property in dtb\n"); |
| return -FDT_ERR_NOTFOUND; |
| } |
| |
| if (((cell + ac + sc) * (int)sizeof(uint32_t)) > len) { |
| return -FDT_ERR_BADVALUE; |
| } |
| |
| if (base != NULL) { |
| *base = (uintptr_t)fdt_read_prop_cells(&prop[cell], ac); |
| } |
| |
| if (size != NULL) { |
| *size = (size_t)fdt_read_prop_cells(&prop[cell + ac], sc); |
| } |
| |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * This function fills reg node info (base & size) with an index found by |
| * checking the reg-names node. |
| * Returns 0 on success and a negative FDT error code on failure. |
| ******************************************************************************/ |
| int fdt_get_reg_props_by_name(const void *dtb, int node, const char *name, |
| uintptr_t *base, size_t *size) |
| { |
| int index; |
| |
| index = fdt_stringlist_search(dtb, node, "reg-names", name); |
| if (index < 0) { |
| return index; |
| } |
| |
| return fdt_get_reg_props_by_index(dtb, node, index, base, size); |
| } |
| |
| /******************************************************************************* |
| * This function gets the stdout path node. |
| * It reads the value indicated inside the device tree. |
| * Returns node offset on success and a negative FDT error code on failure. |
| ******************************************************************************/ |
| int fdt_get_stdout_node_offset(const void *dtb) |
| { |
| int node; |
| const char *prop, *path; |
| int len; |
| |
| /* The /secure-chosen node takes precedence over the standard one. */ |
| node = fdt_path_offset(dtb, "/secure-chosen"); |
| if (node < 0) { |
| node = fdt_path_offset(dtb, "/chosen"); |
| if (node < 0) { |
| return -FDT_ERR_NOTFOUND; |
| } |
| } |
| |
| prop = fdt_getprop(dtb, node, "stdout-path", NULL); |
| if (prop == NULL) { |
| return -FDT_ERR_NOTFOUND; |
| } |
| |
| /* Determine the actual path length, as a colon terminates the path. */ |
| path = strchr(prop, ':'); |
| if (path == NULL) { |
| len = strlen(prop); |
| } else { |
| len = path - prop; |
| } |
| |
| /* Aliases cannot start with a '/', so it must be the actual path. */ |
| if (prop[0] == '/') { |
| return fdt_path_offset_namelen(dtb, prop, len); |
| } |
| |
| /* Lookup the alias, as this contains the actual path. */ |
| path = fdt_get_alias_namelen(dtb, prop, len); |
| if (path == NULL) { |
| return -FDT_ERR_NOTFOUND; |
| } |
| |
| return fdt_path_offset(dtb, path); |
| } |
| |
| |
| /******************************************************************************* |
| * Only devices which are direct children of root node use CPU address domain. |
| * All other devices use addresses that are local to the device node and cannot |
| * directly used by CPU. Device tree provides an address translation mechanism |
| * through "ranges" property which provides mappings from local address space to |
| * parent address space. Since a device could be a child of a child node to the |
| * root node, there can be more than one level of address translation needed to |
| * map the device local address space to CPU address space. |
| * fdtw_translate_address() API performs address translation of a local address |
| * to a global address with help of various helper functions. |
| ******************************************************************************/ |
| |
| static bool fdtw_xlat_hit(const fdt32_t *value, int child_addr_size, |
| int parent_addr_size, int range_size, uint64_t base_address, |
| uint64_t *translated_addr) |
| { |
| uint64_t local_address, parent_address, addr_range; |
| |
| local_address = fdt_read_prop_cells(value, child_addr_size); |
| parent_address = fdt_read_prop_cells(value + child_addr_size, |
| parent_addr_size); |
| addr_range = fdt_read_prop_cells(value + child_addr_size + |
| parent_addr_size, |
| range_size); |
| VERBOSE("DT: Address %" PRIx64 " mapped to %" PRIx64 " with range %" PRIx64 "\n", |
| local_address, parent_address, addr_range); |
| |
| /* Perform range check */ |
| if ((base_address < local_address) || |
| (base_address >= local_address + addr_range)) { |
| return false; |
| } |
| |
| /* Found hit for the addr range that needs to be translated */ |
| *translated_addr = parent_address + (base_address - local_address); |
| VERBOSE("DT: child address %" PRIx64 "mapped to %" PRIx64 " in parent bus\n", |
| local_address, parent_address); |
| return true; |
| } |
| |
| #define ILLEGAL_ADDR ULL(~0) |
| |
| static uint64_t fdtw_search_all_xlat_entries(const void *dtb, |
| const struct fdt_property *ranges_prop, |
| int local_bus, uint64_t base_address) |
| { |
| uint64_t translated_addr; |
| const fdt32_t *next_entry; |
| int parent_bus_node, nxlat_entries, length; |
| int self_addr_cells, parent_addr_cells, self_size_cells, ncells_xlat; |
| |
| /* |
| * The number of cells in one translation entry in ranges is the sum of |
| * the following values: |
| * self#address-cells + parent#address-cells + self#size-cells |
| * Ex: the iofpga ranges property has one translation entry with 4 cells |
| * They represent iofpga#addr-cells + motherboard#addr-cells + iofpga#size-cells |
| * = 1 + 2 + 1 |
| */ |
| |
| parent_bus_node = fdt_parent_offset(dtb, local_bus); |
| self_addr_cells = fdt_address_cells(dtb, local_bus); |
| self_size_cells = fdt_size_cells(dtb, local_bus); |
| parent_addr_cells = fdt_address_cells(dtb, parent_bus_node); |
| |
| /* Number of cells per translation entry i.e., mapping */ |
| ncells_xlat = self_addr_cells + parent_addr_cells + self_size_cells; |
| |
| assert(ncells_xlat > 0); |
| |
| /* |
| * Find the number of translations(mappings) specified in the current |
| * `ranges` property. Note that length represents number of bytes and |
| * is stored in big endian mode. |
| */ |
| length = fdt32_to_cpu(ranges_prop->len); |
| nxlat_entries = (length/sizeof(uint32_t))/ncells_xlat; |
| |
| assert(nxlat_entries > 0); |
| |
| next_entry = (const fdt32_t *)ranges_prop->data; |
| |
| /* Iterate over the entries in the "ranges" */ |
| for (int i = 0; i < nxlat_entries; i++) { |
| if (fdtw_xlat_hit(next_entry, self_addr_cells, |
| parent_addr_cells, self_size_cells, base_address, |
| &translated_addr)){ |
| return translated_addr; |
| } |
| next_entry = next_entry + ncells_xlat; |
| } |
| |
| INFO("DT: No translation found for address %" PRIx64 " in node %s\n", |
| base_address, fdt_get_name(dtb, local_bus, NULL)); |
| return ILLEGAL_ADDR; |
| } |
| |
| |
| /******************************************************************************* |
| * address mapping needs to be done recursively starting from current node to |
| * root node through all intermediate parent nodes. |
| * Sample device tree is shown here: |
| |
| smb@0,0 { |
| compatible = "simple-bus"; |
| |
| #address-cells = <2>; |
| #size-cells = <1>; |
| ranges = <0 0 0 0x08000000 0x04000000>, |
| <1 0 0 0x14000000 0x04000000>, |
| <2 0 0 0x18000000 0x04000000>, |
| <3 0 0 0x1c000000 0x04000000>, |
| <4 0 0 0x0c000000 0x04000000>, |
| <5 0 0 0x10000000 0x04000000>; |
| |
| motherboard { |
| arm,v2m-memory-map = "rs1"; |
| compatible = "arm,vexpress,v2m-p1", "simple-bus"; |
| #address-cells = <2>; |
| #size-cells = <1>; |
| ranges; |
| |
| iofpga@3,00000000 { |
| compatible = "arm,amba-bus", "simple-bus"; |
| #address-cells = <1>; |
| #size-cells = <1>; |
| ranges = <0 3 0 0x200000>; |
| v2m_serial1: uart@a0000 { |
| compatible = "arm,pl011", "arm,primecell"; |
| reg = <0x0a0000 0x1000>; |
| interrupts = <0 6 4>; |
| clocks = <&v2m_clk24mhz>, <&v2m_clk24mhz>; |
| clock-names = "uartclk", "apb_pclk"; |
| }; |
| }; |
| }; |
| |
| * As seen above, there are 3 levels of address translations needed. An empty |
| * `ranges` property denotes identity mapping (as seen in `motherboard` node). |
| * Each ranges property can map a set of child addresses to parent bus. Hence |
| * there can be more than 1 (translation) entry in the ranges property as seen |
| * in the `smb` node which has 6 translation entries. |
| ******************************************************************************/ |
| |
| /* Recursive implementation */ |
| uint64_t fdtw_translate_address(const void *dtb, int node, |
| uint64_t base_address) |
| { |
| int length, local_bus_node; |
| const char *node_name; |
| uint64_t global_address; |
| |
| local_bus_node = fdt_parent_offset(dtb, node); |
| node_name = fdt_get_name(dtb, local_bus_node, NULL); |
| |
| /* |
| * In the example given above, starting from the leaf node: |
| * uart@a000 represents the current node |
| * iofpga@3,00000000 represents the local bus |
| * motherboard represents the parent bus |
| */ |
| |
| /* Read the ranges property */ |
| const struct fdt_property *property = fdt_get_property(dtb, |
| local_bus_node, "ranges", &length); |
| |
| if (property == NULL) { |
| if (local_bus_node == 0) { |
| /* |
| * root node doesn't have range property as addresses |
| * are in CPU address space. |
| */ |
| return base_address; |
| } |
| INFO("DT: Couldn't find ranges property in node %s\n", |
| node_name); |
| return ILLEGAL_ADDR; |
| } else if (length == 0) { |
| /* empty ranges indicates identity map to parent bus */ |
| return fdtw_translate_address(dtb, local_bus_node, base_address); |
| } |
| |
| VERBOSE("DT: Translation lookup in node %s at offset %d\n", node_name, |
| local_bus_node); |
| global_address = fdtw_search_all_xlat_entries(dtb, property, |
| local_bus_node, base_address); |
| |
| if (global_address == ILLEGAL_ADDR) { |
| return ILLEGAL_ADDR; |
| } |
| |
| /* Translate the local device address recursively */ |
| return fdtw_translate_address(dtb, local_bus_node, global_address); |
| } |
| |
| /* |
| * For every CPU node (`/cpus/cpu@n`) in an FDT, execute a callback passing a |
| * pointer to the FDT and the offset of the CPU node. If the return value of the |
| * callback is negative, it is treated as an error and the loop is aborted. In |
| * this situation, the value of the callback is returned from the function. |
| * |
| * Returns `0` on success, or a negative integer representing an error code. |
| */ |
| int fdtw_for_each_cpu(const void *dtb, |
| int (*callback)(const void *dtb, int node, uintptr_t mpidr)) |
| { |
| int ret = 0; |
| int parent, node = 0; |
| |
| parent = fdt_path_offset(dtb, "/cpus"); |
| if (parent < 0) { |
| return parent; |
| } |
| |
| fdt_for_each_subnode(node, dtb, parent) { |
| const char *name; |
| int len; |
| |
| uintptr_t mpidr = 0U; |
| |
| name = fdt_get_name(dtb, node, &len); |
| if (strncmp(name, "cpu@", 4) != 0) { |
| continue; |
| } |
| |
| ret = fdt_get_reg_props_by_index(dtb, node, 0, &mpidr, NULL); |
| if (ret < 0) { |
| break; |
| } |
| |
| ret = callback(dtb, node, mpidr); |
| if (ret < 0) { |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * Find a given node in device tree. If not present, add it. |
| * Returns offset of node found/added on success, and < 0 on error. |
| */ |
| int fdtw_find_or_add_subnode(void *fdt, int parentoffset, const char *name) |
| { |
| int offset; |
| |
| offset = fdt_subnode_offset(fdt, parentoffset, name); |
| |
| if (offset == -FDT_ERR_NOTFOUND) { |
| offset = fdt_add_subnode(fdt, parentoffset, name); |
| } |
| |
| if (offset < 0) { |
| ERROR("%s: %s: %s\n", __func__, name, fdt_strerror(offset)); |
| } |
| |
| return offset; |
| } |