diff options
Diffstat (limited to 'roms/opensbi/lib/utils/fdt')
-rw-r--r-- | roms/opensbi/lib/utils/fdt/fdt_domain.c | 455 | ||||
-rw-r--r-- | roms/opensbi/lib/utils/fdt/fdt_fixup.c | 265 | ||||
-rw-r--r-- | roms/opensbi/lib/utils/fdt/fdt_helper.c | 465 | ||||
-rw-r--r-- | roms/opensbi/lib/utils/fdt/objects.mk | 9 |
4 files changed, 1194 insertions, 0 deletions
diff --git a/roms/opensbi/lib/utils/fdt/fdt_domain.c b/roms/opensbi/lib/utils/fdt/fdt_domain.c new file mode 100644 index 000000000..09615e53e --- /dev/null +++ b/roms/opensbi/lib/utils/fdt/fdt_domain.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * fdt_domain.c - Flat Device Tree Domain helper routines + * + * Copyright (c) 2020 Western Digital Corporation or its affiliates. + * + * Authors: + * Anup Patel <anup.patel@wdc.com> + */ + +#include <libfdt.h> +#include <sbi/sbi_domain.h> +#include <sbi/sbi_error.h> +#include <sbi/sbi_hartmask.h> +#include <sbi/sbi_scratch.h> +#include <sbi_utils/fdt/fdt_domain.h> +#include <sbi_utils/fdt/fdt_helper.h> + +int fdt_iterate_each_domain(void *fdt, void *opaque, + int (*fn)(void *fdt, int domain_offset, + void *opaque)) +{ + int rc, doffset, poffset; + + if (!fdt || !fn) + return SBI_EINVAL; + + poffset = fdt_path_offset(fdt, "/chosen"); + if (poffset < 0) + return 0; + poffset = fdt_node_offset_by_compatible(fdt, poffset, + "opensbi,domain,config"); + if (poffset < 0) + return 0; + + fdt_for_each_subnode(doffset, fdt, poffset) { + if (fdt_node_check_compatible(fdt, doffset, + "opensbi,domain,instance")) + continue; + + rc = fn(fdt, doffset, opaque); + if (rc) + return rc; + } + + return 0; +} + +int fdt_iterate_each_memregion(void *fdt, int domain_offset, void *opaque, + int (*fn)(void *fdt, int domain_offset, + int region_offset, u32 region_access, + void *opaque)) +{ + u32 i, rcount; + int rc, len, region_offset; + const u32 *regions; + + if (!fdt || (domain_offset < 0) || !fn) + return SBI_EINVAL; + + if (fdt_node_check_compatible(fdt, domain_offset, + "opensbi,domain,instance")) + return SBI_EINVAL; + + regions = fdt_getprop(fdt, domain_offset, "regions", &len); + if (!regions) + return 0; + + rcount = (u32)len / (sizeof(u32) * 2); + for (i = 0; i < rcount; i++) { + region_offset = fdt_node_offset_by_phandle(fdt, + fdt32_to_cpu(regions[2 * i])); + if (region_offset < 0) + return region_offset; + + if (fdt_node_check_compatible(fdt, region_offset, + "opensbi,domain,memregion")) + return SBI_EINVAL; + + rc = fn(fdt, domain_offset, region_offset, + fdt32_to_cpu(regions[(2 * i) + 1]), opaque); + if (rc) + return rc; + } + + return 0; +} + +struct __fixup_find_domain_offset_info { + const char *name; + int *doffset; +}; + +static int __fixup_find_domain_offset(void *fdt, int doff, void *p) +{ + struct __fixup_find_domain_offset_info *fdo = p; + + if (!sbi_strcmp(fdo->name, fdt_get_name(fdt, doff, NULL))) + *fdo->doffset = doff; + + return 0; +} + +#define DISABLE_DEVICES_MASK (SBI_DOMAIN_MEMREGION_READABLE | \ + SBI_DOMAIN_MEMREGION_WRITEABLE | \ + SBI_DOMAIN_MEMREGION_EXECUTABLE) + +static int __fixup_count_disable_devices(void *fdt, int doff, int roff, + u32 perm, void *p) +{ + int len; + u32 *dcount = p; + + if (perm & DISABLE_DEVICES_MASK) + return 0; + + len = 0; + if (fdt_getprop(fdt, roff, "devices", &len)) + *dcount += len / sizeof(u32); + + return 0; +} + +static int __fixup_disable_devices(void *fdt, int doff, int roff, + u32 raccess, void *p) +{ + int i, len, coff; + const u32 *devices; + + if (raccess & DISABLE_DEVICES_MASK) + return 0; + + len = 0; + devices = fdt_getprop(fdt, roff, "devices", &len); + if (!devices) + return 0; + len = len / sizeof(u32); + + for (i = 0; i < len; i++) { + coff = fdt_node_offset_by_phandle(fdt, + fdt32_to_cpu(devices[i])); + if (coff < 0) + return coff; + + fdt_setprop_string(fdt, coff, "status", "disabled"); + } + + return 0; +} + +void fdt_domain_fixup(void *fdt) +{ + u32 i, dcount; + int err, poffset, doffset; + struct sbi_domain *dom = sbi_domain_thishart_ptr(); + struct __fixup_find_domain_offset_info fdo; + + /* Remove the domain assignment DT property from CPU DT nodes */ + poffset = fdt_path_offset(fdt, "/cpus"); + if (poffset < 0) + return; + fdt_for_each_subnode(doffset, fdt, poffset) { + err = fdt_parse_hart_id(fdt, doffset, &i); + if (err) + continue; + + fdt_nop_property(fdt, doffset, "opensbi-domain"); + } + + /* Skip device disable for root domain */ + if (!dom->index) + goto skip_device_disable; + + /* Find current domain DT node */ + doffset = -1; + fdo.name = dom->name; + fdo.doffset = &doffset; + fdt_iterate_each_domain(fdt, &fdo, __fixup_find_domain_offset); + if (doffset < 0) + goto skip_device_disable; + + /* Count current domain device DT nodes to be disabled */ + dcount = 0; + fdt_iterate_each_memregion(fdt, doffset, &dcount, + __fixup_count_disable_devices); + if (!dcount) + goto skip_device_disable; + + /* Expand FDT based on device DT nodes to be disabled */ + err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + dcount * 32); + if (err < 0) + return; + + /* Again find current domain DT node */ + doffset = -1; + fdo.name = dom->name; + fdo.doffset = &doffset; + fdt_iterate_each_domain(fdt, &fdo, __fixup_find_domain_offset); + if (doffset < 0) + goto skip_device_disable; + + /* Disable device DT nodes for current domain */ + fdt_iterate_each_memregion(fdt, doffset, NULL, + __fixup_disable_devices); +skip_device_disable: + + /* Remove the OpenSBI domain config DT node */ + poffset = fdt_path_offset(fdt, "/chosen"); + if (poffset < 0) + return; + poffset = fdt_node_offset_by_compatible(fdt, poffset, + "opensbi,domain,config"); + if (poffset < 0) + return; + fdt_nop_node(fdt, poffset); +} + +#define FDT_DOMAIN_MAX_COUNT 8 +#define FDT_DOMAIN_REGION_MAX_COUNT 16 + +static u32 fdt_domains_count; +static struct sbi_domain fdt_domains[FDT_DOMAIN_MAX_COUNT]; +static struct sbi_hartmask fdt_masks[FDT_DOMAIN_MAX_COUNT]; +static struct sbi_domain_memregion + fdt_regions[FDT_DOMAIN_MAX_COUNT][FDT_DOMAIN_REGION_MAX_COUNT + 2]; + +static int __fdt_parse_region(void *fdt, int domain_offset, + int region_offset, u32 region_access, + void *opaque) +{ + int len; + u32 val32; + u64 val64; + const u32 *val; + u32 *region_count = opaque; + struct sbi_domain_memregion *region; + + /* Find next region of the domain */ + if (FDT_DOMAIN_REGION_MAX_COUNT <= *region_count) + return SBI_EINVAL; + region = &fdt_regions[fdt_domains_count][*region_count]; + + /* Read "base" DT property */ + val = fdt_getprop(fdt, region_offset, "base", &len); + if (!val && len >= 8) + return SBI_EINVAL; + val64 = fdt32_to_cpu(val[0]); + val64 = (val64 << 32) | fdt32_to_cpu(val[1]); + region->base = val64; + + /* Read "order" DT property */ + val = fdt_getprop(fdt, region_offset, "order", &len); + if (!val && len >= 4) + return SBI_EINVAL; + val32 = fdt32_to_cpu(*val); + if (val32 < 3 || __riscv_xlen < val32) + return SBI_EINVAL; + region->order = val32; + + /* Read "mmio" DT property */ + region->flags = region_access & SBI_DOMAIN_MEMREGION_ACCESS_MASK; + if (fdt_get_property(fdt, region_offset, "mmio", NULL)) + region->flags |= SBI_DOMAIN_MEMREGION_MMIO; + + (*region_count)++; + + return 0; +} + +static int __fdt_parse_domain(void *fdt, int domain_offset, void *opaque) +{ + u32 val32; + u64 val64; + const u32 *val; + struct sbi_domain *dom; + struct sbi_hartmask *mask; + struct sbi_hartmask assign_mask; + int *cold_domain_offset = opaque; + struct sbi_domain_memregion *regions; + int i, err, len, cpus_offset, cpu_offset, doffset; + + /* Sanity check on maximum domains we can handle */ + if (FDT_DOMAIN_MAX_COUNT <= fdt_domains_count) + return SBI_EINVAL; + dom = &fdt_domains[fdt_domains_count]; + mask = &fdt_masks[fdt_domains_count]; + regions = &fdt_regions[fdt_domains_count][0]; + + /* Read DT node name */ + sbi_strncpy(dom->name, fdt_get_name(fdt, domain_offset, NULL), + sizeof(dom->name)); + dom->name[sizeof(dom->name) - 1] = '\0'; + + /* Setup possible HARTs mask */ + SBI_HARTMASK_INIT(mask); + dom->possible_harts = mask; + val = fdt_getprop(fdt, domain_offset, "possible-harts", &len); + len = len / sizeof(u32); + if (val && len) { + for (i = 0; i < len; i++) { + cpu_offset = fdt_node_offset_by_phandle(fdt, + fdt32_to_cpu(val[i])); + if (cpu_offset < 0) + return cpu_offset; + + err = fdt_parse_hart_id(fdt, cpu_offset, &val32); + if (err) + return err; + + sbi_hartmask_set_hart(val32, mask); + } + } + + /* Setup memregions from DT */ + val32 = 0; + sbi_memset(regions, 0, + sizeof(*regions) * (FDT_DOMAIN_REGION_MAX_COUNT + 2)); + dom->regions = regions; + err = fdt_iterate_each_memregion(fdt, domain_offset, &val32, + __fdt_parse_region); + if (err) + return err; + sbi_domain_memregion_initfw(®ions[val32]); + + /* Read "boot-hart" DT property */ + val32 = -1U; + val = fdt_getprop(fdt, domain_offset, "boot-hart", &len); + if (val && len >= 4) { + cpu_offset = fdt_node_offset_by_phandle(fdt, + fdt32_to_cpu(*val)); + if (cpu_offset >= 0) + fdt_parse_hart_id(fdt, cpu_offset, &val32); + } else { + if (domain_offset == *cold_domain_offset) + val32 = current_hartid(); + } + dom->boot_hartid = val32; + + /* Read "next-arg1" DT property */ + val64 = 0; + val = fdt_getprop(fdt, domain_offset, "next-arg1", &len); + if (val && len >= 8) { + val64 = fdt32_to_cpu(val[0]); + val64 = (val64 << 32) | fdt32_to_cpu(val[1]); + } else { + if (domain_offset == *cold_domain_offset) + val64 = sbi_scratch_thishart_ptr()->next_arg1; + } + dom->next_arg1 = val64; + + /* Read "next-addr" DT property */ + val64 = 0; + val = fdt_getprop(fdt, domain_offset, "next-addr", &len); + if (val && len >= 8) { + val64 = fdt32_to_cpu(val[0]); + val64 = (val64 << 32) | fdt32_to_cpu(val[1]); + } else { + if (domain_offset == *cold_domain_offset) + val64 = sbi_scratch_thishart_ptr()->next_addr; + } + dom->next_addr = val64; + + /* Read "next-mode" DT property */ + val32 = 0x1; + val = fdt_getprop(fdt, domain_offset, "next-mode", &len); + if (val && len >= 4) { + val32 = fdt32_to_cpu(val[0]); + if (val32 != 0x0 && val32 != 0x1) + val32 = 0x1; + } else { + if (domain_offset == *cold_domain_offset) + val32 = sbi_scratch_thishart_ptr()->next_mode; + } + dom->next_mode = val32; + + /* Read "system-reset-allowed" DT property */ + if (fdt_get_property(fdt, domain_offset, + "system-reset-allowed", NULL)) + dom->system_reset_allowed = TRUE; + else + dom->system_reset_allowed = FALSE; + + /* Find /cpus DT node */ + cpus_offset = fdt_path_offset(fdt, "/cpus"); + if (cpus_offset < 0) + return cpus_offset; + + /* HART to domain assignment mask based on CPU DT nodes */ + sbi_hartmask_clear_all(&assign_mask); + fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) { + err = fdt_parse_hart_id(fdt, cpu_offset, &val32); + if (err) + continue; + + if (SBI_HARTMASK_MAX_BITS <= val32) + continue; + + val = fdt_getprop(fdt, cpu_offset, "opensbi-domain", &len); + if (!val || len < 4) + return SBI_EINVAL; + + doffset = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*val)); + if (doffset < 0) + return doffset; + + if (doffset == domain_offset) + sbi_hartmask_set_hart(val32, &assign_mask); + } + + /* Increment domains count */ + fdt_domains_count++; + + /* Register the domain */ + return sbi_domain_register(dom, &assign_mask); +} + +int fdt_domains_populate(void *fdt) +{ + const u32 *val; + int cold_domain_offset; + u32 hartid, cold_hartid; + int err, len, cpus_offset, cpu_offset; + + /* Sanity checks */ + if (!fdt) + return SBI_EINVAL; + + /* Find /cpus DT node */ + cpus_offset = fdt_path_offset(fdt, "/cpus"); + if (cpus_offset < 0) + return cpus_offset; + + /* Find coldboot HART domain DT node offset */ + cold_domain_offset = -1; + cold_hartid = current_hartid(); + fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) { + err = fdt_parse_hart_id(fdt, cpu_offset, &hartid); + if (err) + continue; + + if (hartid != cold_hartid) + continue; + + val = fdt_getprop(fdt, cpu_offset, "opensbi-domain", &len); + if (val && len >= 4) + cold_domain_offset = fdt_node_offset_by_phandle(fdt, + fdt32_to_cpu(*val)); + + break; + } + + /* Iterate over each domain in FDT and populate details */ + return fdt_iterate_each_domain(fdt, &cold_domain_offset, + __fdt_parse_domain); +} diff --git a/roms/opensbi/lib/utils/fdt/fdt_fixup.c b/roms/opensbi/lib/utils/fdt/fdt_fixup.c new file mode 100644 index 000000000..eea450d80 --- /dev/null +++ b/roms/opensbi/lib/utils/fdt/fdt_fixup.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * fdt_fixup.c - Flat Device Tree parsing helper routines + * Implement helper routines to parse FDT nodes on top of + * libfdt for OpenSBI usage + * + * Copyright (C) 2020 Bin Meng <bmeng.cn@gmail.com> + */ + +#include <libfdt.h> +#include <sbi/sbi_console.h> +#include <sbi/sbi_domain.h> +#include <sbi/sbi_math.h> +#include <sbi/sbi_hart.h> +#include <sbi/sbi_scratch.h> +#include <sbi/sbi_string.h> +#include <sbi_utils/fdt/fdt_fixup.h> +#include <sbi_utils/fdt/fdt_helper.h> + +void fdt_cpu_fixup(void *fdt) +{ + struct sbi_domain *dom = sbi_domain_thishart_ptr(); + int err, cpu_offset, cpus_offset, len; + const char *mmu_type; + u32 hartid; + + err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 32); + if (err < 0) + return; + + cpus_offset = fdt_path_offset(fdt, "/cpus"); + if (cpus_offset < 0) + return; + + fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) { + err = fdt_parse_hart_id(fdt, cpu_offset, &hartid); + if (err) + continue; + + /* + * Disable a HART DT node if one of the following is true: + * 1. The HART is not assigned to the current domain + * 2. MMU is not available for the HART + */ + + mmu_type = fdt_getprop(fdt, cpu_offset, "mmu-type", &len); + if (!sbi_domain_is_assigned_hart(dom, hartid) || + !mmu_type || !len) + fdt_setprop_string(fdt, cpu_offset, "status", + "disabled"); + } +} + +void fdt_plic_fixup(void *fdt, const char *compat) +{ + u32 *cells; + int i, cells_count; + int plic_off; + + plic_off = fdt_node_offset_by_compatible(fdt, 0, compat); + if (plic_off < 0) + return; + + cells = (u32 *)fdt_getprop(fdt, plic_off, + "interrupts-extended", &cells_count); + if (!cells) + return; + + cells_count = cells_count / sizeof(u32); + if (!cells_count) + return; + + for (i = 0; i < (cells_count / 2); i++) { + if (fdt32_to_cpu(cells[2 * i + 1]) == IRQ_M_EXT) + cells[2 * i + 1] = cpu_to_fdt32(0xffffffff); + } +} + +static int fdt_resv_memory_update_node(void *fdt, unsigned long addr, + unsigned long size, int index, + int parent, bool no_map) +{ + int na = fdt_address_cells(fdt, 0); + int ns = fdt_size_cells(fdt, 0); + fdt32_t addr_high, addr_low; + fdt32_t size_high, size_low; + int subnode, err; + fdt32_t reg[4]; + fdt32_t *val; + char name[32]; + + addr_high = (u64)addr >> 32; + addr_low = addr; + size_high = (u64)size >> 32; + size_low = size; + + if (na > 1 && addr_high) + sbi_snprintf(name, sizeof(name), + "mmode_resv%d@%x,%x", index, + addr_high, addr_low); + else + sbi_snprintf(name, sizeof(name), + "mmode_resv%d@%x", index, + addr_low); + + subnode = fdt_add_subnode(fdt, parent, name); + if (subnode < 0) + return subnode; + + if (no_map) { + /* + * Tell operating system not to create a virtual + * mapping of the region as part of its standard + * mapping of system memory. + */ + err = fdt_setprop_empty(fdt, subnode, "no-map"); + if (err < 0) + return err; + } + + /* encode the <reg> property value */ + val = reg; + if (na > 1) + *val++ = cpu_to_fdt32(addr_high); + *val++ = cpu_to_fdt32(addr_low); + if (ns > 1) + *val++ = cpu_to_fdt32(size_high); + *val++ = cpu_to_fdt32(size_low); + + err = fdt_setprop(fdt, subnode, "reg", reg, + (na + ns) * sizeof(fdt32_t)); + if (err < 0) + return err; + + return 0; +} + +/** + * We use PMP to protect OpenSBI firmware to safe-guard it from buggy S-mode + * software, see pmp_init() in lib/sbi/sbi_hart.c. The protected memory region + * information needs to be conveyed to S-mode software (e.g.: operating system) + * via some well-known method. + * + * With device tree, this can be done by inserting a child node of the reserved + * memory node which is used to specify one or more regions of reserved memory. + * + * For the reserved memory node bindings, see Linux kernel documentation at + * Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt + * + * Some additional memory spaces may be protected by platform codes via PMP as + * well, and corresponding child nodes will be inserted. + */ +int fdt_reserved_memory_fixup(void *fdt) +{ + struct sbi_domain_memregion *reg; + struct sbi_domain *dom = sbi_domain_thishart_ptr(); + struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); + unsigned long addr, size; + int err, parent, i; + int na = fdt_address_cells(fdt, 0); + int ns = fdt_size_cells(fdt, 0); + + /* + * Expand the device tree to accommodate new node + * by the following estimated size: + * + * Each PMP memory region entry occupies 64 bytes. + * With 16 PMP memory regions we need 64 * 16 = 1024 bytes. + */ + err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 1024); + if (err < 0) + return err; + + /* try to locate the reserved memory node */ + parent = fdt_path_offset(fdt, "/reserved-memory"); + if (parent < 0) { + /* if such node does not exist, create one */ + parent = fdt_add_subnode(fdt, 0, "reserved-memory"); + if (parent < 0) + return parent; + + /* + * reserved-memory node has 3 required properties: + * - #address-cells: the same value as the root node + * - #size-cells: the same value as the root node + * - ranges: should be empty + */ + + err = fdt_setprop_empty(fdt, parent, "ranges"); + if (err < 0) + return err; + + err = fdt_setprop_u32(fdt, parent, "#size-cells", ns); + if (err < 0) + return err; + + err = fdt_setprop_u32(fdt, parent, "#address-cells", na); + if (err < 0) + return err; + } + + /* + * We assume the given device tree does not contain any memory region + * child node protected by PMP. Normally PMP programming happens at + * M-mode firmware. The memory space used by OpenSBI is protected. + * Some additional memory spaces may be protected by domain memory + * regions. + * + * With above assumption, we create child nodes directly. + */ + + i = 0; + sbi_domain_for_each_memregion(dom, reg) { + /* Ignore MMIO or READABLE or WRITABLE or EXECUTABLE regions */ + if (reg->flags & SBI_DOMAIN_MEMREGION_MMIO) + continue; + if (reg->flags & SBI_DOMAIN_MEMREGION_READABLE) + continue; + if (reg->flags & SBI_DOMAIN_MEMREGION_WRITEABLE) + continue; + if (reg->flags & SBI_DOMAIN_MEMREGION_EXECUTABLE) + continue; + + addr = reg->base; + size = 1UL << reg->order; + fdt_resv_memory_update_node(fdt, addr, size, i, parent, + (sbi_hart_pmp_count(scratch)) ? false : true); + i++; + } + + return 0; +} + +int fdt_reserved_memory_nomap_fixup(void *fdt) +{ + int parent, subnode; + int err; + + /* Locate the reserved memory node */ + parent = fdt_path_offset(fdt, "/reserved-memory"); + if (parent < 0) + return parent; + + fdt_for_each_subnode(subnode, fdt, parent) { + /* + * Tell operating system not to create a virtual + * mapping of the region as part of its standard + * mapping of system memory. + */ + err = fdt_setprop_empty(fdt, subnode, "no-map"); + if (err < 0) + return err; + } + + return 0; +} + +void fdt_fixups(void *fdt) +{ + fdt_plic_fixup(fdt, "riscv,plic0"); + + fdt_reserved_memory_fixup(fdt); +} + + diff --git a/roms/opensbi/lib/utils/fdt/fdt_helper.c b/roms/opensbi/lib/utils/fdt/fdt_helper.c new file mode 100644 index 000000000..bf19ff92d --- /dev/null +++ b/roms/opensbi/lib/utils/fdt/fdt_helper.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * fdt_helper.c - Flat Device Tree manipulation helper routines + * Implement helper routines on top of libfdt for OpenSBI usage + * + * Copyright (C) 2020 Bin Meng <bmeng.cn@gmail.com> + */ + +#include <libfdt.h> +#include <sbi/riscv_asm.h> +#include <sbi/sbi_console.h> +#include <sbi/sbi_hartmask.h> +#include <sbi/sbi_platform.h> +#include <sbi/sbi_scratch.h> +#include <sbi_utils/fdt/fdt_helper.h> +#include <sbi_utils/irqchip/plic.h> +#include <sbi_utils/sys/clint.h> + +#define DEFAULT_UART_FREQ 0 +#define DEFAULT_UART_BAUD 115200 +#define DEFAULT_UART_REG_SHIFT 0 +#define DEFAULT_UART_REG_IO_WIDTH 1 + +#define DEFAULT_SIFIVE_UART_FREQ 0 +#define DEFAULT_SIFIVE_UART_BAUD 115200 +#define DEFAULT_SIFIVE_UART_REG_SHIFT 0 +#define DEFAULT_SIFIVE_UART_REG_IO_WIDTH 4 + +#define DEFAULT_SHAKTI_UART_FREQ 50000000 +#define DEFAULT_SHAKTI_UART_BAUD 115200 + +const struct fdt_match *fdt_match_node(void *fdt, int nodeoff, + const struct fdt_match *match_table) +{ + int ret; + + if (!fdt || nodeoff < 0 || !match_table) + return NULL; + + while (match_table->compatible) { + ret = fdt_node_check_compatible(fdt, nodeoff, + match_table->compatible); + if (!ret) + return match_table; + match_table++; + } + + return NULL; +} + +int fdt_find_match(void *fdt, int startoff, + const struct fdt_match *match_table, + const struct fdt_match **out_match) +{ + int nodeoff; + + if (!fdt || !match_table) + return SBI_ENODEV; + + while (match_table->compatible) { + nodeoff = fdt_node_offset_by_compatible(fdt, startoff, + match_table->compatible); + if (nodeoff >= 0) { + if (out_match) + *out_match = match_table; + return nodeoff; + } + match_table++; + } + + return SBI_ENODEV; +} + +static int fdt_translate_address(void *fdt, uint64_t reg, int parent, + unsigned long *addr) +{ + int i, rlen; + int cell_addr, cell_size; + const fdt32_t *ranges; + uint64_t offset = 0, caddr = 0, paddr = 0, rsize = 0; + + cell_addr = fdt_address_cells(fdt, parent); + if (cell_addr < 1) + return SBI_ENODEV; + + cell_size = fdt_size_cells(fdt, parent); + if (cell_size < 0) + return SBI_ENODEV; + + ranges = fdt_getprop(fdt, parent, "ranges", &rlen); + if (ranges && rlen > 0) { + for (i = 0; i < cell_addr; i++) + caddr = (caddr << 32) | fdt32_to_cpu(*ranges++); + for (i = 0; i < cell_addr; i++) + paddr = (paddr << 32) | fdt32_to_cpu(*ranges++); + for (i = 0; i < cell_size; i++) + rsize = (rsize << 32) | fdt32_to_cpu(*ranges++); + if (reg < caddr || caddr >= (reg + rsize )) { + sbi_printf("invalid address translation\n"); + return SBI_ENODEV; + } + offset = reg - caddr; + *addr = paddr + offset; + } else { + /* No translation required */ + *addr = reg; + } + + return 0; +} + +int fdt_get_node_addr_size(void *fdt, int node, unsigned long *addr, + unsigned long *size) +{ + int parent, len, i, rc; + int cell_addr, cell_size; + const fdt32_t *prop_addr, *prop_size; + uint64_t temp = 0; + + parent = fdt_parent_offset(fdt, node); + if (parent < 0) + return parent; + cell_addr = fdt_address_cells(fdt, parent); + if (cell_addr < 1) + return SBI_ENODEV; + + cell_size = fdt_size_cells(fdt, parent); + if (cell_size < 0) + return SBI_ENODEV; + + prop_addr = fdt_getprop(fdt, node, "reg", &len); + if (!prop_addr) + return SBI_ENODEV; + prop_size = prop_addr + cell_addr; + + if (addr) { + for (i = 0; i < cell_addr; i++) + temp = (temp << 32) | fdt32_to_cpu(*prop_addr++); + do { + if (parent < 0) + break; + rc = fdt_translate_address(fdt, temp, parent, addr); + if (rc) + break; + parent = fdt_parent_offset(fdt, parent); + temp = *addr; + } while (1); + } + temp = 0; + + if (size) { + for (i = 0; i < cell_size; i++) + temp = (temp << 32) | fdt32_to_cpu(*prop_size++); + *size = temp; + } + + return 0; +} + +int fdt_parse_hart_id(void *fdt, int cpu_offset, u32 *hartid) +{ + int len; + const void *prop; + const fdt32_t *val; + + if (!fdt || cpu_offset < 0) + return SBI_EINVAL; + + prop = fdt_getprop(fdt, cpu_offset, "device_type", &len); + if (!prop || !len) + return SBI_EINVAL; + if (strncmp (prop, "cpu", strlen ("cpu"))) + return SBI_EINVAL; + + val = fdt_getprop(fdt, cpu_offset, "reg", &len); + if (!val || len < sizeof(fdt32_t)) + return SBI_EINVAL; + + if (len > sizeof(fdt32_t)) + val++; + + if (hartid) + *hartid = fdt32_to_cpu(*val); + + return 0; +} + +int fdt_parse_max_hart_id(void *fdt, u32 *max_hartid) +{ + u32 hartid; + int err, cpu_offset, cpus_offset; + + if (!fdt) + return SBI_EINVAL; + if (!max_hartid) + return 0; + + *max_hartid = 0; + + cpus_offset = fdt_path_offset(fdt, "/cpus"); + if (cpus_offset < 0) + return cpus_offset; + + fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) { + err = fdt_parse_hart_id(fdt, cpu_offset, &hartid); + if (err) + continue; + + if (hartid > *max_hartid) + *max_hartid = hartid; + } + + return 0; +} + +int fdt_parse_shakti_uart_node(void *fdt, int nodeoffset, + struct platform_uart_data *uart) +{ + int len, rc; + const fdt32_t *val; + unsigned long reg_addr, reg_size; + + if (nodeoffset < 0 || !uart || !fdt) + return SBI_ENODEV; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, ®_addr, ®_size); + if (rc < 0 || !reg_addr || !reg_size) + return SBI_ENODEV; + uart->addr = reg_addr; + + /** + * UART address is mandaotry. clock-frequency and current-speed + * may not be present. Don't return error. + */ + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "clock-frequency", &len); + if (len > 0 && val) + uart->freq = fdt32_to_cpu(*val); + else + uart->freq = DEFAULT_SHAKTI_UART_FREQ; + + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len); + if (len > 0 && val) + uart->baud = fdt32_to_cpu(*val); + else + uart->baud = DEFAULT_SHAKTI_UART_BAUD; + + return 0; +} + +int fdt_parse_sifive_uart_node(void *fdt, int nodeoffset, + struct platform_uart_data *uart) +{ + int len, rc; + const fdt32_t *val; + unsigned long reg_addr, reg_size; + + if (nodeoffset < 0 || !uart || !fdt) + return SBI_ENODEV; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, ®_addr, ®_size); + if (rc < 0 || !reg_addr || !reg_size) + return SBI_ENODEV; + uart->addr = reg_addr; + + /** + * UART address is mandaotry. clock-frequency and current-speed + * may not be present. Don't return error. + */ + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "clock-frequency", &len); + if (len > 0 && val) + uart->freq = fdt32_to_cpu(*val); + else + uart->freq = DEFAULT_SIFIVE_UART_FREQ; + + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len); + if (len > 0 && val) + uart->baud = fdt32_to_cpu(*val); + else + uart->baud = DEFAULT_SIFIVE_UART_BAUD; + + /* For SiFive UART, the reg-shift and reg-io-width are fixed .*/ + uart->reg_shift = DEFAULT_SIFIVE_UART_REG_SHIFT; + uart->reg_io_width = DEFAULT_SIFIVE_UART_REG_IO_WIDTH; + + return 0; +} + +int fdt_parse_uart8250_node(void *fdt, int nodeoffset, + struct platform_uart_data *uart) +{ + int len, rc; + const fdt32_t *val; + unsigned long reg_addr, reg_size; + + if (nodeoffset < 0 || !uart || !fdt) + return SBI_ENODEV; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, ®_addr, ®_size); + if (rc < 0 || !reg_addr || !reg_size) + return SBI_ENODEV; + uart->addr = reg_addr; + + /** + * UART address is mandaotry. clock-frequency and current-speed + * may not be present. Don't return error. + */ + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "clock-frequency", &len); + if (len > 0 && val) + uart->freq = fdt32_to_cpu(*val); + else + uart->freq = DEFAULT_UART_FREQ; + + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len); + if (len > 0 && val) + uart->baud = fdt32_to_cpu(*val); + else + uart->baud = DEFAULT_UART_BAUD; + + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "reg-shift", &len); + if (len > 0 && val) + uart->reg_shift = fdt32_to_cpu(*val); + else + uart->reg_shift = DEFAULT_UART_REG_SHIFT; + + val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "reg-io-width", &len); + if (len > 0 && val) + uart->reg_io_width = fdt32_to_cpu(*val); + else + uart->reg_io_width = DEFAULT_UART_REG_IO_WIDTH; + + return 0; +} + +int fdt_parse_uart8250(void *fdt, struct platform_uart_data *uart, + const char *compatible) +{ + int nodeoffset; + + if (!compatible || !uart || !fdt) + return SBI_ENODEV; + + nodeoffset = fdt_node_offset_by_compatible(fdt, -1, compatible); + if (nodeoffset < 0) + return nodeoffset; + + return fdt_parse_uart8250_node(fdt, nodeoffset, uart); +} + +int fdt_parse_plic_node(void *fdt, int nodeoffset, struct plic_data *plic) +{ + int len, rc; + const fdt32_t *val; + unsigned long reg_addr, reg_size; + + if (nodeoffset < 0 || !plic || !fdt) + return SBI_ENODEV; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, ®_addr, ®_size); + if (rc < 0 || !reg_addr || !reg_size) + return SBI_ENODEV; + plic->addr = reg_addr; + + val = fdt_getprop(fdt, nodeoffset, "riscv,ndev", &len); + if (len > 0) + plic->num_src = fdt32_to_cpu(*val); + + return 0; +} + +int fdt_parse_plic(void *fdt, struct plic_data *plic, const char *compat) +{ + int nodeoffset; + + if (!compat || !plic || !fdt) + return SBI_ENODEV; + + nodeoffset = fdt_node_offset_by_compatible(fdt, -1, compat); + if (nodeoffset < 0) + return nodeoffset; + + return fdt_parse_plic_node(fdt, nodeoffset, plic); +} + +int fdt_parse_clint_node(void *fdt, int nodeoffset, bool for_timer, + struct clint_data *clint) +{ + const fdt32_t *val; + unsigned long reg_addr, reg_size; + int i, rc, count, cpu_offset, cpu_intc_offset; + u32 phandle, hwirq, hartid, first_hartid, last_hartid; + u32 match_hwirq = (for_timer) ? IRQ_M_TIMER : IRQ_M_SOFT; + + if (nodeoffset < 0 || !clint || !fdt) + return SBI_ENODEV; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, ®_addr, ®_size); + if (rc < 0 || !reg_addr || !reg_size) + return SBI_ENODEV; + clint->addr = reg_addr; + + val = fdt_getprop(fdt, nodeoffset, "interrupts-extended", &count); + if (!val || count < sizeof(fdt32_t)) + return SBI_EINVAL; + count = count / sizeof(fdt32_t); + + first_hartid = -1U; + last_hartid = 0; + clint->hart_count = 0; + for (i = 0; i < count; i += 2) { + phandle = fdt32_to_cpu(val[i]); + hwirq = fdt32_to_cpu(val[i + 1]); + + cpu_intc_offset = fdt_node_offset_by_phandle(fdt, phandle); + if (cpu_intc_offset < 0) + continue; + + cpu_offset = fdt_parent_offset(fdt, cpu_intc_offset); + if (cpu_intc_offset < 0) + continue; + + rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid); + if (rc) + continue; + + if (SBI_HARTMASK_MAX_BITS <= hartid) + continue; + + if (match_hwirq == hwirq) { + if (hartid < first_hartid) + first_hartid = hartid; + if (hartid > last_hartid) + last_hartid = hartid; + clint->hart_count++; + } + } + + if ((last_hartid < first_hartid) || first_hartid == -1U) + return SBI_ENODEV; + + clint->first_hartid = first_hartid; + count = last_hartid - first_hartid + 1; + if (clint->hart_count < count) + clint->hart_count = count; + + /* TODO: We should figure-out CLINT has_64bit_mmio from DT node */ + clint->has_64bit_mmio = TRUE; + + return 0; +} + +int fdt_parse_compat_addr(void *fdt, unsigned long *addr, + const char *compatible) +{ + int nodeoffset, rc; + + nodeoffset = fdt_node_offset_by_compatible(fdt, -1, compatible); + if (nodeoffset < 0) + return nodeoffset; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, addr, NULL); + if (rc < 0 || !addr) + return SBI_ENODEV; + + return 0; +} diff --git a/roms/opensbi/lib/utils/fdt/objects.mk b/roms/opensbi/lib/utils/fdt/objects.mk new file mode 100644 index 000000000..d9f1eae19 --- /dev/null +++ b/roms/opensbi/lib/utils/fdt/objects.mk @@ -0,0 +1,9 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2020 Bin Meng <bmeng.cn@gmail.com> +# + +libsbiutils-objs-y += fdt/fdt_domain.o +libsbiutils-objs-y += fdt/fdt_helper.o +libsbiutils-objs-y += fdt/fdt_fixup.o |