| // 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/sbi_hart.h> |
| #include <sbi_utils/fdt/fdt_helper.h> |
| #include <sbi_utils/irqchip/aplic.h> |
| #include <sbi_utils/irqchip/imsic.h> |
| #include <sbi_utils/irqchip/plic.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_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 void fdt_parse_isa_extensions_one_hart(const char *isa, |
| unsigned long *extensions, |
| int len) |
| { |
| size_t i; |
| |
| for (i = 0; i < SBI_HART_EXT_MAX; i++) { |
| if (fdt_stringlist_contains(isa, len, sbi_hart_ext[i].name)) |
| __set_bit(sbi_hart_ext[i].id, extensions); |
| } |
| } |
| |
| 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; |
| |
| scratch = sbi_hartid_to_scratch(hartid); |
| if (!scratch) |
| return SBI_ENOENT; |
| |
| hart_exts = sbi_scratch_offset_ptr(scratch, |
| fdt_isa_bitmap_offset); |
| |
| val = fdt_getprop(fdt, cpu_offset, "riscv,isa-extensions", &len); |
| if (val && len > 0) { |
| fdt_parse_isa_extensions_one_hart((const char *)val, |
| hart_exts, len); |
| continue; |
| } |
| |
| val = fdt_getprop(fdt, cpu_offset, "riscv,isa", &len); |
| if (!val || len <= 0) |
| return SBI_ENOENT; |
| |
| 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,delegation", &len); |
| if (!del) |
| 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; |
| } |