// 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 */ #include #include #include #include #include #include #include #include #include #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; }