// 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 #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_UART_REG_OFFSET 0 #define DEFAULT_RENESAS_SCIF_FREQ 100000000 #define DEFAULT_RENESAS_SCIF_BAUD 115200 #define DEFAULT_SIFIVE_UART_FREQ 0 #define DEFAULT_SIFIVE_UART_BAUD 115200 #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; } int fdt_parse_phandle_with_args(void *fdt, int nodeoff, const char *prop, const char *cells_prop, int index, struct fdt_phandle_args *out_args) { u32 i, pcells; int len, pnodeoff; const fdt32_t *list, *list_end, *val; if (!fdt || (nodeoff < 0) || !prop || !cells_prop || !out_args) return SBI_EINVAL; list = fdt_getprop(fdt, nodeoff, prop, &len); if (!list) return SBI_ENOENT; list_end = list + (len / sizeof(*list)); while (list < list_end) { pnodeoff = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*list)); if (pnodeoff < 0) return pnodeoff; list++; val = fdt_getprop(fdt, pnodeoff, cells_prop, &len); if (!val) return SBI_ENOENT; pcells = fdt32_to_cpu(*val); if (FDT_MAX_PHANDLE_ARGS < pcells) return SBI_EINVAL; if (list + pcells > list_end) return SBI_ENOENT; if (index > 0) { list += pcells; index--; } else { out_args->node_offset = pnodeoff; out_args->args_count = pcells; for (i = 0; i < pcells; i++) out_args->args[i] = fdt32_to_cpu(list[i]); return 0; } } return SBI_ENOENT; } static int fdt_translate_address(void *fdt, uint64_t reg, int parent, uint64_t *addr) { int i, rlen; int cell_addr, cell_size; const fdt32_t *ranges; uint64_t offset, 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, int index, uint64_t *addr, uint64_t *size) { int parent, len, i, rc; int cell_addr, cell_size; const fdt32_t *prop_addr, *prop_size; uint64_t temp = 0; if (!fdt || node < 0 || index < 0) return SBI_EINVAL; 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; if ((len / sizeof(u32)) <= (index * (cell_addr + cell_size))) return SBI_EINVAL; prop_addr = prop_addr + (index * (cell_addr + cell_size)); 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_get_node_addr_size_by_name(void *fdt, int node, const char *name, uint64_t *addr, uint64_t *size) { int i, j, count; const char *val; const char *regname; if (!fdt || node < 0 || !name) return SBI_EINVAL; val = fdt_getprop(fdt, node, "reg-names", &count); if (!val) return SBI_ENODEV; for (i = 0, j = 0; i < count; i++, j++) { regname = val + i; if (strcmp(name, regname) == 0) return fdt_get_node_addr_size(fdt, node, j, addr, size); i += strlen(regname); } return SBI_ENODEV; } bool fdt_node_is_enabled(void *fdt, int nodeoff) { int len; const void *prop; prop = fdt_getprop(fdt, nodeoff, "status", &len); if (!prop) return true; if (!strncmp(prop, "okay", strlen("okay"))) return true; if (!strncmp(prop, "ok", strlen("ok"))) return true; return false; } 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_enabled_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 (!fdt_node_is_enabled(fdt, cpu_offset)) continue; if (hartid > *max_hartid) *max_hartid = hartid; } return 0; } int fdt_parse_timebase_frequency(void *fdt, unsigned long *freq) { const fdt32_t *val; int len, cpus_offset; if (!fdt || !freq) return SBI_EINVAL; cpus_offset = fdt_path_offset(fdt, "/cpus"); if (cpus_offset < 0) return cpus_offset; val = fdt_getprop(fdt, cpus_offset, "timebase-frequency", &len); if (len > 0 && val) *freq = fdt32_to_cpu(*val); else return SBI_ENOENT; return 0; } #define RISCV_ISA_EXT_NAME_LEN_MAX 32 static unsigned long fdt_isa_bitmap_offset; static int fdt_parse_isa_one_hart(const char *isa, unsigned long *extensions) { size_t i, j, isa_len; char mstr[RISCV_ISA_EXT_NAME_LEN_MAX]; i = 0; isa_len = strlen(isa); if (isa[i] == 'r' || isa[i] == 'R') i++; else return SBI_EINVAL; if (isa[i] == 'v' || isa[i] == 'V') i++; else return SBI_EINVAL; if (isa[i] == '3' || isa[i+1] == '2') i += 2; else if (isa[i] == '6' || isa[i+1] == '4') i += 2; else return SBI_EINVAL; /* Skip base ISA extensions */ for (; i < isa_len; i++) { if (isa[i] == '_') break; } while (i < isa_len) { if (isa[i] != '_') { i++; continue; } /* Skip the '_' character */ i++; /* Extract the multi-letter extension name */ j = 0; while ((i < isa_len) && (isa[i] != '_') && (j < (sizeof(mstr) - 1))) mstr[j++] = isa[i++]; mstr[j] = '\0'; /* Skip empty multi-letter extension name */ if (!j) continue; #define set_multi_letter_ext(name, bit) \ if (!strcmp(mstr, name)) { \ __set_bit(bit, extensions); \ continue; \ } for (j = 0; j < SBI_HART_EXT_MAX; j++) { set_multi_letter_ext(sbi_hart_ext[j].name, sbi_hart_ext[j].id); } #undef set_multi_letter_ext } return 0; } static int fdt_parse_isa_all_harts(void *fdt) { u32 hartid; const fdt32_t *val; unsigned long *hart_exts; struct sbi_scratch *scratch; int err, cpu_offset, cpus_offset, len; if (!fdt || !fdt_isa_bitmap_offset) return SBI_EINVAL; 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 (!fdt_node_is_enabled(fdt, cpu_offset)) continue; val = fdt_getprop(fdt, cpu_offset, "riscv,isa", &len); if (!val || len <= 0) return SBI_ENOENT; scratch = sbi_hartid_to_scratch(hartid); if (!scratch) return SBI_ENOENT; hart_exts = sbi_scratch_offset_ptr(scratch, fdt_isa_bitmap_offset); err = fdt_parse_isa_one_hart((const char *)val, hart_exts); if (err) return err; } return 0; } int fdt_parse_isa_extensions(void *fdt, unsigned int hartid, unsigned long *extensions) { int rc, i; unsigned long *hart_exts; struct sbi_scratch *scratch; if (!fdt_isa_bitmap_offset) { fdt_isa_bitmap_offset = sbi_scratch_alloc_offset( sizeof(*hart_exts) * BITS_TO_LONGS(SBI_HART_EXT_MAX)); if (!fdt_isa_bitmap_offset) return SBI_ENOMEM; rc = fdt_parse_isa_all_harts(fdt); if (rc) return rc; } scratch = sbi_hartid_to_scratch(hartid); if (!scratch) return SBI_ENOENT; hart_exts = sbi_scratch_offset_ptr(scratch, fdt_isa_bitmap_offset); for (i = 0; i < BITS_TO_LONGS(SBI_HART_EXT_MAX); i++) extensions[i] |= hart_exts[i]; return 0; } static int fdt_parse_uart_node_common(void *fdt, int nodeoffset, struct platform_uart_data *uart, unsigned long default_freq, unsigned long default_baud) { int len, rc; const fdt32_t *val; uint64_t reg_addr, reg_size; if (nodeoffset < 0 || !uart || !fdt) return SBI_ENODEV; rc = fdt_get_node_addr_size(fdt, nodeoffset, 0, ®_addr, ®_size); if (rc < 0 || !reg_addr || !reg_size) return SBI_ENODEV; uart->addr = reg_addr; /** * UART address is mandatory. 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_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_baud; return 0; } int fdt_parse_gaisler_uart_node(void *fdt, int nodeoffset, struct platform_uart_data *uart) { return fdt_parse_uart_node_common(fdt, nodeoffset, uart, DEFAULT_UART_FREQ, DEFAULT_UART_BAUD); } int fdt_parse_renesas_scif_node(void *fdt, int nodeoffset, struct platform_uart_data *uart) { return fdt_parse_uart_node_common(fdt, nodeoffset, uart, DEFAULT_RENESAS_SCIF_FREQ, DEFAULT_RENESAS_SCIF_BAUD); } int fdt_parse_shakti_uart_node(void *fdt, int nodeoffset, struct platform_uart_data *uart) { return fdt_parse_uart_node_common(fdt, nodeoffset, uart, DEFAULT_SHAKTI_UART_FREQ, DEFAULT_SHAKTI_UART_BAUD); } int fdt_parse_sifive_uart_node(void *fdt, int nodeoffset, struct platform_uart_data *uart) { return fdt_parse_uart_node_common(fdt, nodeoffset, uart, DEFAULT_SIFIVE_UART_FREQ, DEFAULT_SIFIVE_UART_BAUD); } int fdt_parse_uart_node(void *fdt, int nodeoffset, struct platform_uart_data *uart) { int len, rc; const fdt32_t *val; rc = fdt_parse_uart_node_common(fdt, nodeoffset, uart, DEFAULT_UART_FREQ, DEFAULT_UART_BAUD); if (rc) return rc; 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; val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "reg-offset", &len); if (len > 0 && val) uart->reg_offset = fdt32_to_cpu(*val); else uart->reg_offset = DEFAULT_UART_REG_OFFSET; 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_uart_node(fdt, nodeoffset, uart); } int fdt_parse_xlnx_uartlite_node(void *fdt, int nodeoffset, struct platform_uart_data *uart) { return fdt_parse_uart_node_common(fdt, nodeoffset, uart, 0, 0); } int fdt_parse_aplic_node(void *fdt, int nodeoff, struct aplic_data *aplic) { bool child_found; const fdt32_t *val; const fdt32_t *del; struct imsic_data imsic; int i, j, d, dcnt, len, noff, rc; uint64_t reg_addr, reg_size; struct aplic_delegate_data *deleg; if (nodeoff < 0 || !aplic || !fdt) return SBI_ENODEV; memset(aplic, 0, sizeof(*aplic)); rc = fdt_get_node_addr_size(fdt, nodeoff, 0, ®_addr, ®_size); if (rc < 0 || !reg_addr || !reg_size) return SBI_ENODEV; aplic->addr = reg_addr; aplic->size = reg_size; val = fdt_getprop(fdt, nodeoff, "riscv,num-sources", &len); if (len > 0) aplic->num_source = fdt32_to_cpu(*val); val = fdt_getprop(fdt, nodeoff, "interrupts-extended", &len); if (val && len > sizeof(fdt32_t)) { len = len / sizeof(fdt32_t); for (i = 0; i < len; i += 2) { if (fdt32_to_cpu(val[i + 1]) == IRQ_M_EXT) { aplic->targets_mmode = true; break; } } aplic->num_idc = len / 2; goto aplic_msi_parent_done; } val = fdt_getprop(fdt, nodeoff, "msi-parent", &len); if (val && len >= sizeof(fdt32_t)) { noff = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*val)); if (noff < 0) return noff; rc = fdt_parse_imsic_node(fdt, noff, &imsic); if (rc) return rc; rc = imsic_data_check(&imsic); if (rc) return rc; aplic->targets_mmode = imsic.targets_mmode; if (imsic.targets_mmode) { aplic->has_msicfg_mmode = true; aplic->msicfg_mmode.lhxs = imsic.guest_index_bits; aplic->msicfg_mmode.lhxw = imsic.hart_index_bits; aplic->msicfg_mmode.hhxw = imsic.group_index_bits; aplic->msicfg_mmode.hhxs = imsic.group_index_shift; if (aplic->msicfg_mmode.hhxs < (2 * IMSIC_MMIO_PAGE_SHIFT)) return SBI_EINVAL; aplic->msicfg_mmode.hhxs -= 24; aplic->msicfg_mmode.base_addr = imsic.regs[0].addr; } else { goto aplic_msi_parent_done; } val = fdt_getprop(fdt, nodeoff, "riscv,children", &len); if (!val || len < sizeof(fdt32_t)) goto aplic_msi_parent_done; noff = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*val)); if (noff < 0) return noff; val = fdt_getprop(fdt, noff, "msi-parent", &len); if (!val || len < sizeof(fdt32_t)) goto aplic_msi_parent_done; noff = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*val)); if (noff < 0) return noff; rc = fdt_parse_imsic_node(fdt, noff, &imsic); if (rc) return rc; rc = imsic_data_check(&imsic); if (rc) return rc; if (!imsic.targets_mmode) { aplic->has_msicfg_smode = true; aplic->msicfg_smode.lhxs = imsic.guest_index_bits; aplic->msicfg_smode.lhxw = imsic.hart_index_bits; aplic->msicfg_smode.hhxw = imsic.group_index_bits; aplic->msicfg_smode.hhxs = imsic.group_index_shift; if (aplic->msicfg_smode.hhxs < (2 * IMSIC_MMIO_PAGE_SHIFT)) return SBI_EINVAL; aplic->msicfg_smode.hhxs -= 24; aplic->msicfg_smode.base_addr = imsic.regs[0].addr; } } aplic_msi_parent_done: for (d = 0; d < APLIC_MAX_DELEGATE; d++) { deleg = &aplic->delegate[d]; deleg->first_irq = 0; deleg->last_irq = 0; deleg->child_index = 0; } del = fdt_getprop(fdt, nodeoff, "riscv,delegate", &len); if (!del || len < (3 * sizeof(fdt32_t))) goto skip_delegate_parse; d = 0; dcnt = len / sizeof(fdt32_t); for (i = 0; i < dcnt; i += 3) { if (d >= APLIC_MAX_DELEGATE) break; deleg = &aplic->delegate[d]; deleg->first_irq = fdt32_to_cpu(del[i + 1]); deleg->last_irq = fdt32_to_cpu(del[i + 2]); deleg->child_index = 0; child_found = false; val = fdt_getprop(fdt, nodeoff, "riscv,children", &len); if (!val || len < sizeof(fdt32_t)) { deleg->first_irq = 0; deleg->last_irq = 0; deleg->child_index = 0; continue; } len = len / sizeof(fdt32_t); for (j = 0; j < len; j++) { if (del[i] != val[j]) continue; deleg->child_index = j; child_found = true; break; } if (child_found) { d++; } else { deleg->first_irq = 0; deleg->last_irq = 0; deleg->child_index = 0; } } skip_delegate_parse: return 0; } bool fdt_check_imsic_mlevel(void *fdt) { const fdt32_t *val; int i, len, noff = 0; if (!fdt) return false; while ((noff = fdt_node_offset_by_compatible(fdt, noff, "riscv,imsics")) >= 0) { val = fdt_getprop(fdt, noff, "interrupts-extended", &len); if (val && len > sizeof(fdt32_t)) { len = len / sizeof(fdt32_t); for (i = 0; i < len; i += 2) { if (fdt32_to_cpu(val[i + 1]) == IRQ_M_EXT) return true; } } } return false; } int fdt_parse_imsic_node(void *fdt, int nodeoff, struct imsic_data *imsic) { const fdt32_t *val; struct imsic_regs *regs; uint64_t reg_addr, reg_size; int i, rc, len, nr_parent_irqs; if (nodeoff < 0 || !imsic || !fdt) return SBI_ENODEV; imsic->targets_mmode = false; val = fdt_getprop(fdt, nodeoff, "interrupts-extended", &len); if (val && len > sizeof(fdt32_t)) { len = len / sizeof(fdt32_t); nr_parent_irqs = len / 2; for (i = 0; i < len; i += 2) { if (fdt32_to_cpu(val[i + 1]) == IRQ_M_EXT) { imsic->targets_mmode = true; break; } } } else return SBI_EINVAL; val = fdt_getprop(fdt, nodeoff, "riscv,guest-index-bits", &len); if (val && len > 0) imsic->guest_index_bits = fdt32_to_cpu(*val); else imsic->guest_index_bits = 0; val = fdt_getprop(fdt, nodeoff, "riscv,hart-index-bits", &len); if (val && len > 0) { imsic->hart_index_bits = fdt32_to_cpu(*val); } else { imsic->hart_index_bits = sbi_fls(nr_parent_irqs); if ((1UL << imsic->hart_index_bits) < nr_parent_irqs) imsic->hart_index_bits++; } val = fdt_getprop(fdt, nodeoff, "riscv,group-index-bits", &len); if (val && len > 0) imsic->group_index_bits = fdt32_to_cpu(*val); else imsic->group_index_bits = 0; val = fdt_getprop(fdt, nodeoff, "riscv,group-index-shift", &len); if (val && len > 0) imsic->group_index_shift = fdt32_to_cpu(*val); else imsic->group_index_shift = 2 * IMSIC_MMIO_PAGE_SHIFT; val = fdt_getprop(fdt, nodeoff, "riscv,num-ids", &len); if (val && len > 0) imsic->num_ids = fdt32_to_cpu(*val); else return SBI_EINVAL; for (i = 0; i < IMSIC_MAX_REGS; i++) { regs = &imsic->regs[i]; regs->addr = 0; regs->size = 0; } for (i = 0; i < (IMSIC_MAX_REGS - 1); i++) { regs = &imsic->regs[i]; rc = fdt_get_node_addr_size(fdt, nodeoff, i, ®_addr, ®_size); if (rc < 0 || !reg_addr || !reg_size) break; regs->addr = reg_addr; regs->size = reg_size; } if (!imsic->regs[0].size) return SBI_EINVAL; return 0; } int fdt_parse_plic_node(void *fdt, int nodeoffset, struct plic_data *plic) { int len, rc; const fdt32_t *val; uint64_t reg_addr, reg_size; if (nodeoffset < 0 || !plic || !fdt) return SBI_ENODEV; rc = fdt_get_node_addr_size(fdt, nodeoffset, 0, ®_addr, ®_size); if (rc < 0 || !reg_addr || !reg_size) return SBI_ENODEV; plic->addr = reg_addr; plic->size = reg_size; 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); } static int fdt_get_aclint_addr_size_by_name(void *fdt, int nodeoffset, unsigned long *out_addr1, unsigned long *out_size1, unsigned long *out_addr2, unsigned long *out_size2) { int rc; uint64_t reg_addr, reg_size; rc = fdt_get_node_addr_size_by_name(fdt, nodeoffset, "mtime", ®_addr, ®_size); if (rc < 0 || !reg_size) reg_addr = reg_size = 0; *out_addr1 = reg_addr; *out_size1 = reg_size; rc = fdt_get_node_addr_size_by_name(fdt, nodeoffset, "mtimecmp", ®_addr, ®_size); if (rc < 0 || !reg_size) return SBI_ENODEV; *out_addr2 = reg_addr; *out_size2 = reg_size; return 0; } static int fdt_get_aclint_addr_size(void *fdt, int nodeoffset, unsigned long *out_addr1, unsigned long *out_size1, unsigned long *out_addr2, unsigned long *out_size2) { int rc; uint64_t reg_addr, reg_size; rc = fdt_get_node_addr_size(fdt, nodeoffset, 0, ®_addr, ®_size); if (rc < 0 || !reg_size) return SBI_ENODEV; *out_addr1 = reg_addr; *out_size1 = reg_size; rc = fdt_get_node_addr_size(fdt, nodeoffset, 1, ®_addr, ®_size); if (rc < 0 || !reg_size) reg_addr = reg_size = 0; if (out_addr2) *out_addr2 = reg_addr; if (out_size2) *out_size2 = reg_size; return 0; } int fdt_parse_aclint_node(void *fdt, int nodeoffset, bool for_timer, bool allow_regname, unsigned long *out_addr1, unsigned long *out_size1, unsigned long *out_addr2, unsigned long *out_size2, u32 *out_first_hartid, u32 *out_hart_count) { const fdt32_t *val; int i, rc, count, cpu_offset, cpu_intc_offset; u32 phandle, hwirq, hartid, first_hartid, last_hartid, hart_count; u32 match_hwirq = (for_timer) ? IRQ_M_TIMER : IRQ_M_SOFT; if (nodeoffset < 0 || !fdt || !out_addr1 || !out_size1 || !out_first_hartid || !out_hart_count) return SBI_EINVAL; if (for_timer && allow_regname && out_addr2 && out_size2 && fdt_getprop(fdt, nodeoffset, "reg-names", NULL)) rc = fdt_get_aclint_addr_size_by_name(fdt, nodeoffset, out_addr1, out_size1, out_addr2, out_size2); else rc = fdt_get_aclint_addr_size(fdt, nodeoffset, out_addr1, out_size1, out_addr2, out_size2); if (rc) return rc; *out_first_hartid = 0; *out_hart_count = 0; val = fdt_getprop(fdt, nodeoffset, "interrupts-extended", &count); if (!val || count < sizeof(fdt32_t)) return 0; count = count / sizeof(fdt32_t); first_hartid = -1U; hart_count = last_hartid = 0; for (i = 0; i < (count / 2); i++) { phandle = fdt32_to_cpu(val[2 * i]); hwirq = fdt32_to_cpu(val[(2 * 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_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; hart_count++; } } if ((last_hartid >= first_hartid) && first_hartid != -1U) { *out_first_hartid = first_hartid; count = last_hartid - first_hartid + 1; *out_hart_count = (hart_count < count) ? hart_count : count; } return 0; } int fdt_parse_plmt_node(void *fdt, int nodeoffset, unsigned long *plmt_base, unsigned long *plmt_size, u32 *hart_count) { const fdt32_t *val; int rc, i, count; uint64_t reg_addr, reg_size; u32 phandle, hwirq, hartid, hcount; if (nodeoffset < 0 || !fdt || !plmt_base || !hart_count || !plmt_size) return SBI_EINVAL; rc = fdt_get_node_addr_size(fdt, nodeoffset, 0, ®_addr, ®_size); if (rc < 0) return SBI_ENODEV; *plmt_base = reg_addr; *plmt_size = reg_size; val = fdt_getprop(fdt, nodeoffset, "interrupts-extended", &count); if (!val || count < sizeof(fdt32_t)) return 0; count = count / sizeof(fdt32_t); hcount = 0; for (i = 0; i < (count / 2); i++) { int cpu_offset, cpu_intc_offset; phandle = fdt32_to_cpu(val[2 * i]); hwirq = fdt32_to_cpu(val[2 * 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_offset < 0) continue; rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid); if (rc) continue; if (SBI_HARTMASK_MAX_BITS <= hartid) continue; if (hwirq == IRQ_M_TIMER) hcount++; } *hart_count = hcount; return 0; } int fdt_parse_plicsw_node(void *fdt, int nodeoffset, unsigned long *plicsw_base, unsigned long *size, u32 *hart_count) { const fdt32_t *val; int rc, i, count; uint64_t reg_addr, reg_size; u32 phandle, hwirq, hartid, hcount; if (nodeoffset < 0 || !fdt || !plicsw_base || !hart_count || !size) return SBI_EINVAL; rc = fdt_get_node_addr_size(fdt, nodeoffset, 0, ®_addr, ®_size); if (rc < 0) return SBI_ENODEV; *plicsw_base = reg_addr; *size = reg_size; val = fdt_getprop(fdt, nodeoffset, "interrupts-extended", &count); if (!val || count < sizeof(fdt32_t)) return 0; count = count / sizeof(fdt32_t); hcount = 0; for (i = 0; i < (count / 2); i++) { int cpu_offset, cpu_intc_offset; phandle = fdt32_to_cpu(val[2 * i]); hwirq = fdt32_to_cpu(val[2 * 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_offset < 0) continue; rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid); if (rc) continue; if (SBI_HARTMASK_MAX_BITS <= hartid) continue; if (hwirq == IRQ_M_SOFT) hcount++; } *hart_count = hcount; return 0; } int fdt_parse_compat_addr(void *fdt, uint64_t *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, 0, addr, NULL); if (rc < 0 || !addr) return SBI_ENODEV; return 0; }