diff options
-rw-r--r-- | include/sbi_utils/irqchip/imsic.h | 50 | ||||
-rw-r--r-- | lib/utils/irqchip/imsic.c | 287 | ||||
-rw-r--r-- | lib/utils/irqchip/objects.mk | 1 |
3 files changed, 338 insertions, 0 deletions
diff --git a/include/sbi_utils/irqchip/imsic.h b/include/sbi_utils/irqchip/imsic.h new file mode 100644 index 0000000..cffcb5a --- /dev/null +++ b/include/sbi_utils/irqchip/imsic.h @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 Western Digital Corporation or its affiliates. + * Copyright (c) 2022 Ventana Micro Systems Inc. + * + * Authors: + * Anup Patel <anup.patel@wdc.com> + */ + +#ifndef __IRQCHIP_IMSIC_H__ +#define __IRQCHIP_IMSIC_H__ + +#include <sbi/sbi_types.h> + +#define IMSIC_MMIO_PAGE_SHIFT 12 +#define IMSIC_MMIO_PAGE_SZ (1UL << IMSIC_MMIO_PAGE_SHIFT) + +#define IMSIC_MAX_REGS 16 + +struct imsic_regs { + unsigned long addr; + unsigned long size; +}; + +struct imsic_data { + bool targets_mmode; + u32 guest_index_bits; + u32 hart_index_bits; + u32 group_index_bits; + u32 group_index_shift; + unsigned long num_ids; + struct imsic_regs regs[IMSIC_MAX_REGS]; +}; + +int imsic_map_hartid_to_data(u32 hartid, struct imsic_data *imsic, int file); + +struct imsic_data *imsic_get_data(u32 hartid); + +int imsic_get_target_file(u32 hartid); + +void imsic_local_irqchip_init(void); + +int imsic_warm_irqchip_init(void); + +int imsic_data_check(struct imsic_data *imsic); + +int imsic_cold_irqchip_init(struct imsic_data *imsic); + +#endif diff --git a/lib/utils/irqchip/imsic.c b/lib/utils/irqchip/imsic.c new file mode 100644 index 0000000..123d01f --- /dev/null +++ b/lib/utils/irqchip/imsic.c @@ -0,0 +1,287 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 Western Digital Corporation or its affiliates. + * Copyright (c) 2022 Ventana Micro Systems Inc. + * + * Authors: + * Anup Patel <anup.patel@wdc.com> + */ + +#include <sbi/riscv_asm.h> +#include <sbi/riscv_io.h> +#include <sbi/riscv_encoding.h> +#include <sbi/sbi_console.h> +#include <sbi/sbi_domain.h> +#include <sbi/sbi_hartmask.h> +#include <sbi/sbi_ipi.h> +#include <sbi/sbi_error.h> +#include <sbi/sbi_trap.h> +#include <sbi_utils/irqchip/imsic.h> + +#define IMSIC_MMIO_PAGE_LE 0x00 +#define IMSIC_MMIO_PAGE_BE 0x04 + +#define IMSIC_MIN_ID 63 +#define IMSIC_MAX_ID 2047 + +#define IMSIC_EIDELIVERY 0x70 + +#define IMSIC_EITHRESHOLD 0x72 + +#define IMSIC_TOPEI 0x76 +#define IMSIC_TOPEI_ID_SHIFT 16 +#define IMSIC_TOPEI_ID_MASK 0x7ff +#define IMSIC_TOPEI_PRIO_MASK 0x7ff + +#define IMSIC_EIP0 0x80 + +#define IMSIC_EIP63 0xbf + +#define IMSIC_EIE0 0xc0 + +#define IMSIC_EIE63 0xff + +#define IMSIC_DISABLE_EIDELIVERY 0 +#define IMSIC_ENABLE_EIDELIVERY 1 +#define IMSIC_DISABLE_EITHRESHOLD 1 +#define IMSIC_ENABLE_EITHRESHOLD 0 + +#define IMSIC_IPI_ID 1 + +#define imsic_csr_write(__c, __v) \ +do { \ + csr_write(CSR_MISELECT, __c); \ + csr_write(CSR_MIREG, __v); \ +} while (0) + +#define imsic_csr_read(__c) \ +({ \ + unsigned long __v; \ + csr_write(CSR_MISELECT, __c); \ + __v = csr_read(CSR_MIREG); \ + __v; \ +}) + +static struct imsic_data *imsic_hartid2data[SBI_HARTMASK_MAX_BITS]; +static int imsic_hartid2file[SBI_HARTMASK_MAX_BITS]; + +int imsic_map_hartid_to_data(u32 hartid, struct imsic_data *imsic, int file) +{ + if (!imsic || !imsic->targets_mmode || + (SBI_HARTMASK_MAX_BITS <= hartid)) + return SBI_EINVAL; + + imsic_hartid2data[hartid] = imsic; + imsic_hartid2file[hartid] = file; + return 0; +} + +struct imsic_data *imsic_get_data(u32 hartid) +{ + if (SBI_HARTMASK_MAX_BITS <= hartid) + return NULL; + return imsic_hartid2data[hartid]; +} + +int imsic_get_target_file(u32 hartid) +{ + if ((SBI_HARTMASK_MAX_BITS <= hartid) || + !imsic_hartid2data[hartid]) + return SBI_ENOENT; + return imsic_hartid2file[hartid]; +} + +static int imsic_external_irqfn(struct sbi_trap_regs *regs) +{ + ulong mirq; + + while ((mirq = csr_swap(CSR_MTOPEI, 0))) { + mirq = (mirq >> IMSIC_TOPEI_ID_SHIFT); + + switch (mirq) { + case IMSIC_IPI_ID: + sbi_ipi_process(); + break; + default: + sbi_printf("%s: unhandled IRQ%d\n", + __func__, (u32)mirq); + break; + } + } + + return 0; +} + +static void imsic_ipi_send(u32 target_hart) +{ + unsigned long reloff; + struct imsic_regs *regs; + struct imsic_data *data = imsic_hartid2data[target_hart]; + int file = imsic_hartid2file[target_hart]; + + if (!data || !data->targets_mmode) + return; + + regs = &data->regs[0]; + reloff = file * (1UL << data->guest_index_bits) * IMSIC_MMIO_PAGE_SZ; + while (regs->size && (regs->size <= reloff)) { + reloff -= regs->size; + regs++; + } + + if (regs->size && (reloff < regs->size)) + writel(IMSIC_IPI_ID, + (void *)(regs->addr + reloff + IMSIC_MMIO_PAGE_LE)); +} + +static struct sbi_ipi_device imsic_ipi_device = { + .name = "aia-imsic", + .ipi_send = imsic_ipi_send +}; + +void imsic_local_irqchip_init(void) +{ + /* + * This function is expected to be called from: + * 1) nascent_init() platform callback which is called + * very early on each HART in boot-up path and and + * HSM resume path. + * 2) irqchip_init() platform callback which is called + * in boot-up path. + */ + + /* Setup threshold to allow all enabled interrupts */ + imsic_csr_write(IMSIC_EITHRESHOLD, IMSIC_ENABLE_EITHRESHOLD); + + /* Enable interrupt delivery */ + imsic_csr_write(IMSIC_EIDELIVERY, IMSIC_ENABLE_EIDELIVERY); + + /* Enable IPI */ + csr_write(CSR_MSETEIENUM, IMSIC_IPI_ID); +} + +int imsic_warm_irqchip_init(void) +{ + unsigned long i; + struct imsic_data *imsic = imsic_hartid2data[current_hartid()]; + + /* Sanity checks */ + if (!imsic || !imsic->targets_mmode) + return SBI_EINVAL; + + /* Disable all interrupts */ + for (i = 1; i <= imsic->num_ids; i++) + csr_write(CSR_MCLREIENUM, i); + + /* Clear IPI */ + csr_write(CSR_MCLREIPNUM, IMSIC_IPI_ID); + + /* Local IMSIC initialization */ + imsic_local_irqchip_init(); + + return 0; +} + +int imsic_data_check(struct imsic_data *imsic) +{ + u32 i, tmp; + unsigned long base_addr, addr, mask; + + /* Sanity checks */ + if (!imsic || + (imsic->num_ids < IMSIC_MIN_ID) || + (IMSIC_MAX_ID < imsic->num_ids)) + return SBI_EINVAL; + + tmp = BITS_PER_LONG - IMSIC_MMIO_PAGE_SHIFT; + if (tmp < imsic->guest_index_bits) + return SBI_EINVAL; + + tmp = BITS_PER_LONG - IMSIC_MMIO_PAGE_SHIFT - + imsic->guest_index_bits; + if (tmp < imsic->hart_index_bits) + return SBI_EINVAL; + + tmp = BITS_PER_LONG - IMSIC_MMIO_PAGE_SHIFT - + imsic->guest_index_bits - imsic->hart_index_bits; + if (tmp < imsic->group_index_bits) + return SBI_EINVAL; + + tmp = IMSIC_MMIO_PAGE_SHIFT + imsic->guest_index_bits + + imsic->hart_index_bits; + if (imsic->group_index_shift < tmp) + return SBI_EINVAL; + tmp = imsic->group_index_bits + imsic->group_index_shift - 1; + if (tmp >= BITS_PER_LONG) + return SBI_EINVAL; + + /* + * Number of interrupt identities should be 1 less than + * multiple of 63 + */ + if ((imsic->num_ids & IMSIC_MIN_ID) != IMSIC_MIN_ID) + return SBI_EINVAL; + + /* We should have at least one regset */ + if (!imsic->regs[0].size) + return SBI_EINVAL; + + /* Match patter of each regset */ + base_addr = imsic->regs[0].addr; + base_addr &= ~((1UL << (imsic->guest_index_bits + + imsic->hart_index_bits + + IMSIC_MMIO_PAGE_SHIFT)) - 1); + base_addr &= ~(((1UL << imsic->group_index_bits) - 1) << + imsic->group_index_shift); + for (i = 0; i < IMSIC_MAX_REGS && imsic->regs[i].size; i++) { + mask = (1UL << imsic->guest_index_bits) * IMSIC_MMIO_PAGE_SZ; + mask -= 1UL; + if (imsic->regs[i].size & mask) + return SBI_EINVAL; + + addr = imsic->regs[i].addr; + addr &= ~((1UL << (imsic->guest_index_bits + + imsic->hart_index_bits + + IMSIC_MMIO_PAGE_SHIFT)) - 1); + addr &= ~(((1UL << imsic->group_index_bits) - 1) << + imsic->group_index_shift); + if (base_addr != addr) + return SBI_EINVAL; + } + + return 0; +} + +int imsic_cold_irqchip_init(struct imsic_data *imsic) +{ + int i, rc; + struct sbi_domain_memregion reg; + + /* Sanity checks */ + rc = imsic_data_check(imsic); + if (rc) + return rc; + + /* We only initialize M-mode IMSIC */ + if (!imsic->targets_mmode) + return SBI_EINVAL; + + /* Setup external interrupt function for IMSIC */ + sbi_trap_set_external_irqfn(imsic_external_irqfn); + + /* Add IMSIC regions to the root domain */ + for (i = 0; i < IMSIC_MAX_REGS && imsic->regs[i].size; i++) { + sbi_domain_memregion_init(imsic->regs[i].addr, + imsic->regs[i].size, + SBI_DOMAIN_MEMREGION_MMIO, ®); + rc = sbi_domain_root_add_memregion(®); + if (rc) + return rc; + } + + /* Register IPI device */ + sbi_ipi_set_device(&imsic_ipi_device); + + return 0; +} diff --git a/lib/utils/irqchip/objects.mk b/lib/utils/irqchip/objects.mk index 934f706..76a3c94 100644 --- a/lib/utils/irqchip/objects.mk +++ b/lib/utils/irqchip/objects.mk @@ -9,4 +9,5 @@ libsbiutils-objs-y += irqchip/fdt_irqchip.o libsbiutils-objs-y += irqchip/fdt_irqchip_plic.o +libsbiutils-objs-y += irqchip/imsic.o libsbiutils-objs-y += irqchip/plic.o |