This patch provides an implementation of Nuclei ECLIC Device. Nuclei processor core have been equipped with an Enhanced Core Local Interrupt Controller (ECLIC), which is optimized based on the RISC-V standard CLIC, to manage all interrupt sources.
https://doc.nucleisys.com/nuclei_spec/isa/eclic.html --- hw/intc/Kconfig | 3 + hw/intc/meson.build | 1 + hw/intc/nuclei_eclic.c | 437 +++++++++++++++++++++++++++++++++ include/hw/intc/nuclei_eclic.h | 115 +++++++++ 4 files changed, 556 insertions(+) create mode 100644 hw/intc/nuclei_eclic.c create mode 100644 include/hw/intc/nuclei_eclic.h diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index f4694088a4..eab30f6ffd 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -73,3 +73,6 @@ config GOLDFISH_PIC config M68K_IRQC bool + +config NUCLEI_ECLIC + bool diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 1c299039f6..7577ba69d2 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c')) specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c')) specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true: files('sifive_clint.c')) specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c')) +specific_ss.add(when: 'CONFIG_NUCLEI_ECLIC', if_true: files('nuclei_eclic.c')) specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c')) specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'], if_true: files('xics_kvm.c')) diff --git a/hw/intc/nuclei_eclic.c b/hw/intc/nuclei_eclic.c new file mode 100644 index 0000000000..52de83cb1d --- /dev/null +++ b/hw/intc/nuclei_eclic.c @@ -0,0 +1,437 @@ +/* + * NUCLEI ECLIC(Enhanced Core Local Interrupt Controller) + * + * Copyright (c) 2020 Gao ZhiYuan <alaph...@gmail.com> + * Copyright (c) 2020-2021 PLCT Lab.All rights reserved. + * + * This provides a parameterizable interrupt controller based on NucLei's ECLIC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/error-report.h" +#include "hw/sysbus.h" +#include "hw/pci/msi.h" +#include "hw/boards.h" +#include "hw/qdev-properties.h" +#include "target/riscv/cpu.h" +#include "sysemu/sysemu.h" +#include "hw/intc/nuclei_eclic.h" +#include "qapi/error.h" + +#define RISCV_DEBUG_ECLIC 0 + +static void riscv_cpu_eclic_interrupt(RISCVCPU *cpu, int exccode) +{ + CPURISCVState *env = &cpu->env; + bool locked = false; + + env->exccode = exccode; + + if (!qemu_mutex_iothread_locked()) { + locked = true; + qemu_mutex_lock_iothread(); + } + + if (exccode != -1) { + env->irq_pending = true; + cpu_interrupt(CPU(cpu), CPU_INTERRUPT_ECLIC); + } else { + env->irq_pending = false; + cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_ECLIC); + } + + if (locked) { + qemu_mutex_unlock_iothread(); + } +} + +static int level_compare(NucLeiECLICState *eclic, + ECLICPendingInterrupt *irq1, + ECLICPendingInterrupt *irq2) +{ + if (irq1->level == irq2->level) { + if (irq1->prio == irq2->prio) { + if (irq1->irq >= irq2->irq) { + return 0; + } else { + return 1; + } + } else if (irq1->prio > irq2->level) { + return 0; + } else { + return 1; + } + } else if (irq1->level > irq2->level) { + return 0; + } else { + return 1; + } +} + +static void nuclei_eclic_next_interrupt(void *eclic_ptr) +{ + RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(0)); + CPURISCVState *env = &cpu->env; + NucLeiECLICState *eclic = (NucLeiECLICState *)eclic_ptr; + ECLICPendingInterrupt *active; + target_ulong mil; + int shv; + + QLIST_FOREACH(active, &eclic->pending_list, next) + { + if (active->enable) { + mil = get_field(env->mintstatus, MINTSTATUS_MIL); + if (active->level >= eclic->mth && active->level > mil) { + shv = eclic->clicintattr[active->irq] & 0x1; + eclic->active_count++; + riscv_cpu_eclic_interrupt(cpu, + (active->irq & 0xFFF) | (shv << 12) | (active->level << 13)); + return; + } + } + } +} + +static void nuclei_eclic_update_intmth(NucLeiECLICState *eclic, + int irq, int mth) +{ + eclic->mth = mth; + nuclei_eclic_next_interrupt(eclic); +} + +static void update_eclic_int_info(NucLeiECLICState *eclic, int irq) +{ + int level_width = (eclic->cliccfg >> 1) & 0xF; + if (level_width > CLICINTCTLBITS) { + level_width = CLICINTCTLBITS; + } + int prio_width = CLICINTCTLBITS - level_width; + + if (level_width == 0) { + eclic->clicintlist[irq].level = 255; + } else { + eclic->clicintlist[irq].level = (( + (eclic->clicintctl[irq] >> (8 - level_width)) & + ~((char)0x80 >> (8 - level_width))) + << (8 - level_width)) | + (0xff >> level_width); + } + + if (prio_width == 0) { + eclic->clicintlist[irq].prio = 0; + } else { + eclic->clicintlist[irq].prio = + (eclic->clicintctl[irq] >> (8 - level_width)) & + ~(0x80 >> (8 - prio_width)); + } + + eclic->clicintlist[irq].enable = eclic->clicintie[irq] & 0x1; + eclic->clicintlist[irq].trigger = (eclic->clicintattr[irq] >> 1) & 0x3; +} + +static void eclic_remove_pending_list(NucLeiECLICState *eclic, int irq) +{ + QLIST_REMOVE(&eclic->clicintlist[irq], next); +} + +static void eclic_insert_pending_list(NucLeiECLICState *eclic, int irq) +{ + ECLICPendingInterrupt *node; + if (QLIST_EMPTY(&eclic->pending_list)) { + QLIST_INSERT_HEAD(&eclic->pending_list, &eclic->clicintlist[irq], next); + } else { + QLIST_FOREACH(node, &eclic->pending_list, next) + { + if (level_compare(eclic, node, &eclic->clicintlist[irq])) { + QLIST_INSERT_BEFORE(node, &eclic->clicintlist[irq], next); + break; + } else if (node->next.le_next == NULL) { + QLIST_INSERT_AFTER(node, &eclic->clicintlist[irq], next); + break; + } + } + } +} + +static void nuclei_eclic_update_intip(NucLeiECLICState *eclic, int irq, int new_intip) +{ + + int old_intip = eclic->clicintlist[irq].sig; + int trigger = (eclic->clicintattr[irq] >> 1) & 0x3; + if (((trigger == 0) && new_intip) || + ((trigger == 1) && !old_intip && new_intip) || + ((trigger == 3) && old_intip && !new_intip)) { + eclic->clicintip[irq] = 1; + eclic->clicintlist[irq].sig = new_intip; + eclic_insert_pending_list(eclic, irq); + } else { + if (eclic->clicintip[irq]) { + eclic_remove_pending_list(eclic, irq); + } + eclic->clicintip[irq] = 0; + eclic->clicintlist[irq].sig = new_intip; + } + + nuclei_eclic_next_interrupt(eclic); +} + +static void nuclei_eclic_update_intie(NucLeiECLICState *eclic, + int irq, int new_intie) +{ + eclic->clicintie[irq] = new_intie; + update_eclic_int_info(eclic, irq); + nuclei_eclic_next_interrupt(eclic); +} + +static void nuclei_eclic_update_intattr(NucLeiECLICState *eclic, + int irq, int new_intattr) +{ + eclic->clicintattr[irq] = new_intattr; + update_eclic_int_info(eclic, irq); + nuclei_eclic_next_interrupt(eclic); +} + +static void nuclei_eclic_update_intctl(NucLeiECLICState *eclic, + int irq, int new_intctl) +{ + eclic->clicintctl[irq] = new_intctl; + update_eclic_int_info(eclic, irq); + nuclei_eclic_next_interrupt(eclic); +} + +qemu_irq nuclei_eclic_get_irq(DeviceState *dev, int irq) +{ + NucLeiECLICState *eclic = NUCLEI_ECLIC(dev); + return eclic->irqs[irq]; +} + +static uint64_t nuclei_eclic_read(void *opaque, hwaddr offset, unsigned size) +{ + NucLeiECLICState *eclic = NUCLEI_ECLIC(opaque); + uint64_t value = 0; + uint32_t id = 0; + if (offset >= NUCLEI_ECLIC_REG_CLICINTIP_BASE) { + if ((offset - 0x1000) % 4 == 0) { + id = (offset - 0x1000) / 4; + } else if ((offset - 0x1001) % 4 == 0) { + id = (offset - 0x1001) / 4; + } else if ((offset - 0x1002) % 4 == 0) { + id = (offset - 0x1002) / 4; + } else if ((offset - 0x1003) % 4 == 0) { + id = (offset - 0x1003) / 4; + } + offset = offset - 4 * id; + } + + switch (offset) { + case NUCLEI_ECLIC_REG_CLICCFG: + value = eclic->cliccfg & 0xFF; + break; + case NUCLEI_ECLIC_REG_CLICINFO: + value = (CLICINTCTLBITS << 21) & 0xFFFFFFFF; + break; + case NUCLEI_ECLIC_REG_MTH: + value = eclic->mth & 0xFF; + break; + case NUCLEI_ECLIC_REG_CLICINTIP_BASE: + value = eclic->clicintip[id] & 0xFF; + break; + case NUCLEI_ECLIC_REG_CLICINTIE_BASE: + value = eclic->clicintie[id] & 0xFF; + break; + case NUCLEI_ECLIC_REG_CLICINTATTR_BASE: + value = eclic->clicintattr[id] & 0xFF; + break; + case NUCLEI_ECLIC_REG_CLICINTCTL_BASE: + value = eclic->clicintctl[id] & 0xFF; + break; + default: + break; + } + + return value; +} + +static void nuclei_eclic_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + NucLeiECLICState *eclic = NUCLEI_ECLIC(opaque); + uint32_t id = 0; + if (offset >= NUCLEI_ECLIC_REG_CLICINTIP_BASE) { + + if ((offset - 0x1000) % 4 == 0) { + id = (offset - 0x1000) / 4; + } else if ((offset - 0x1001) % 4 == 0) { + id = (offset - 0x1001) / 4; + } else if ((offset - 0x1002) % 4 == 0) { + id = (offset - 0x1002) / 4; + } else if ((offset - 0x1003) % 4 == 0) { + id = (offset - 0x1003) / 4; + } + offset = offset - 4 * id; + } + switch (offset) { + case NUCLEI_ECLIC_REG_CLICCFG: + eclic->cliccfg = value & 0xFF; + for (id = 0; id < eclic->num_sources; id++) { + update_eclic_int_info(eclic, id); + } + break; + case NUCLEI_ECLIC_REG_MTH: + nuclei_eclic_update_intmth(eclic, id, value & 0xFF); + break; + case NUCLEI_ECLIC_REG_CLICINTIP_BASE: + if ((eclic->clicintlist[id].trigger & 0x1) != 0) { + if ((eclic->clicintip[id] == 0) && (value & 0x1) == 1) { + eclic->clicintip[id] = 1; + eclic_insert_pending_list(eclic, id); + } else if ((eclic->clicintip[id] == 1) && (value & 0x1) == 0) { + eclic->clicintip[id] = 0; + eclic_remove_pending_list(eclic, id); + } + } + nuclei_eclic_next_interrupt(eclic); + break; + case NUCLEI_ECLIC_REG_CLICINTIE_BASE: + nuclei_eclic_update_intie(eclic, id, value & 0xFF); + break; + case NUCLEI_ECLIC_REG_CLICINTATTR_BASE: + nuclei_eclic_update_intattr(eclic, id, value & 0xFF); + break; + case NUCLEI_ECLIC_REG_CLICINTCTL_BASE: + nuclei_eclic_update_intctl(eclic, id, value & 0xFF); + break; + default: + break; + } +} + +static const MemoryRegionOps nuclei_eclic_ops = { + .read = nuclei_eclic_read, + .write = nuclei_eclic_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +void riscv_cpu_eclic_clean_pending(void *eclic_ptr, int irq) +{ + NucLeiECLICState *eclic = (NucLeiECLICState *)eclic_ptr; + if ((eclic->clicintlist[irq].trigger & 0x1) != 0 && irq >= 0) { + eclic->clicintip[irq] = 0; + eclic_remove_pending_list(eclic, irq); + } +} + +void riscv_cpu_eclic_get_next_interrupt(void *eclic_ptr) +{ + NucLeiECLICState *eclic = (NucLeiECLICState *)eclic_ptr; + nuclei_eclic_next_interrupt(eclic); +} + + + +static void nuclei_eclic_irq_request(void *opaque, int id, int new_intip) +{ + NucLeiECLICState *eclic = NUCLEI_ECLIC(opaque); + nuclei_eclic_update_intip(eclic, id, new_intip); +} + +static void nuclei_eclic_realize(DeviceState *dev, Error **errp) +{ + NucLeiECLICState *eclic = NUCLEI_ECLIC(dev); + int id; + + memory_region_init_io(&eclic->mmio, OBJECT(dev), &nuclei_eclic_ops, eclic, + TYPE_NUCLEI_ECLIC, eclic->aperture_size); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &eclic->mmio); + + eclic->clicintip = g_new0(uint8_t, eclic->num_sources); + eclic->clicintlist = g_new0(ECLICPendingInterrupt, eclic->num_sources); + eclic->clicintie = g_new0(uint8_t, eclic->num_sources); + eclic->clicintattr = g_new0(uint8_t, eclic->num_sources); + eclic->clicintctl = g_new0(uint8_t, eclic->num_sources); + eclic->irqs = g_new0(qemu_irq, eclic->num_sources); + QLIST_INIT(&eclic->pending_list); + for (id = 0; id < eclic->num_sources; id++) { + eclic->clicintlist[id].irq = id; + update_eclic_int_info(eclic, id); + } + eclic->active_count = 0; + + /* Init ECLIC IRQ */ + eclic->irqs[Internal_SysTimerSW_IRQn] = + qemu_allocate_irq(nuclei_eclic_irq_request, + eclic, Internal_SysTimerSW_IRQn); + eclic->irqs[Internal_SysTimer_IRQn] = + qemu_allocate_irq(nuclei_eclic_irq_request, + eclic, Internal_SysTimer_IRQn); + + for (id = Internal_Reserved_Max_IRQn; id < eclic->num_sources; id++) { + eclic->irqs[id] = qemu_allocate_irq(nuclei_eclic_irq_request, + eclic, id); + } + + RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(0)); + cpu->env.eclic = eclic; +} + +static Property nuclei_eclic_properties[] = { + DEFINE_PROP_UINT32("aperture-size", NucLeiECLICState, aperture_size, 0), + DEFINE_PROP_UINT32("num-sources", NucLeiECLICState, num_sources, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void nuclei_eclic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, nuclei_eclic_properties); + dc->realize = nuclei_eclic_realize; +} + +static const TypeInfo nuclei_eclic_info = { + .name = TYPE_NUCLEI_ECLIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NucLeiECLICState), + .class_init = nuclei_eclic_class_init, +}; + +static void nuclei_eclic_register_types(void) +{ + type_register_static(&nuclei_eclic_info); +} + +type_init(nuclei_eclic_register_types); + +void nuclei_eclic_systimer_cb(DeviceState *dev) +{ + NucLeiECLICState *eclic = NUCLEI_ECLIC(dev); + nuclei_eclic_irq_request(eclic, Internal_SysTimer_IRQn, 1); +} + +DeviceState *nuclei_eclic_create(hwaddr addr, + uint32_t aperture_size, uint32_t num_sources) +{ + DeviceState *dev = qdev_new(TYPE_NUCLEI_ECLIC); + + qdev_prop_set_uint32(dev, "aperture-size", aperture_size); + qdev_prop_set_uint32(dev, "num-sources", num_sources); + + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + return dev; +} diff --git a/include/hw/intc/nuclei_eclic.h b/include/hw/intc/nuclei_eclic.h new file mode 100644 index 0000000000..18b25485b9 --- /dev/null +++ b/include/hw/intc/nuclei_eclic.h @@ -0,0 +1,115 @@ +/* + * NUCLEI ECLIC (Enhanced Core Local Interrupt Controller) interface + * + * Copyright (c) 2020 Gao ZhiYuan <alaph...@gmail.com> + * Copyright (c) 2020-2021 PLCT Lab.All rights reserved. + * + * This provides a parameterizable interrupt controller based on NucLei's ECLIC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HW_NUCLEI_ECLIC_H +#define HW_NUCLEI_ECLIC_H + +#include "hw/irq.h" +#include "hw/sysbus.h" + +#define TYPE_NUCLEI_ECLIC "riscv.nuclei.eclic" + +#define INTERRUPT_SOURCE_MIN_ID (18) +#define INTERRUPT_SOURCE_MAX_ID (4096) + +typedef struct NucLeiECLICState NucLeiECLICState; +DECLARE_INSTANCE_CHECKER(NucLeiECLICState, NUCLEI_ECLIC, + TYPE_NUCLEI_ECLIC) + +typedef struct ECLICPendingInterrupt { + int irq; + int prio; + int level; + int enable; + int trigger; + int sig; + QLIST_ENTRY(ECLICPendingInterrupt) next; +} ECLICPendingInterrupt; + +#define NUCLEI_ECLIC_REG_CLICCFG 0x0000 +#define NUCLEI_ECLIC_REG_CLICINFO 0x0004 +#define NUCLEI_ECLIC_REG_MTH 0x000b +#define NUCLEI_ECLIC_REG_CLICINTIP_BASE 0x1000 +#define NUCLEI_ECLIC_REG_CLICINTIE_BASE 0x1001 +#define NUCLEI_ECLIC_REG_CLICINTATTR_BASE 0x1002 +#define NUCLEI_ECLIC_REG_CLICINTCTL_BASE 0x1003 + +#define CLICINTCTLBITS 0x6 + +typedef struct NucLeiECLICState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + + uint32_t num_sources; /* 4-1024 */ + + /* config */ + uint32_t sources_id; + uint8_t cliccfg; /* nlbits(1~4) */ + uint32_t clicinfo; + uint8_t mth; /* mth(0~7) */ + uint8_t *clicintip; + uint8_t *clicintie; + uint8_t *clicintattr; /* shv(0) trig(1~2)*/ + uint8_t *clicintctl; + ECLICPendingInterrupt *clicintlist; + uint32_t aperture_size; + + QLIST_HEAD(, ECLICPendingInterrupt) + pending_list; + size_t active_count; + + /* ECLIC IRQ handlers */ + qemu_irq *irqs; + +} NucLeiECLICState; + +enum { + Internal_Reserved0_IRQn = 0, /*!< Internal reserved */ + Internal_Reserved1_IRQn = 1, /*!< Internal reserved */ + Internal_Reserved2_IRQn = 2, /*!< Internal reserved */ + Internal_SysTimerSW_IRQn = 3, /*!< System Timer SW interrupt */ + Internal_Reserved3_IRQn = 4, /*!< Internal reserved */ + Internal_Reserved4_IRQn = 5, /*!< Internal reserved */ + Internal_Reserved5_IRQn = 6, /*!< Internal reserved */ + Internal_SysTimer_IRQn = 7, /*!< System Timer Interrupt */ + Internal_Reserved6_IRQn = 8, /*!< Internal reserved */ + Internal_Reserved7_IRQn = 9, /*!< Internal reserved */ + Internal_Reserved8_IRQn = 10, /*!< Internal reserved */ + Internal_Reserved9_IRQn = 11, /*!< Internal reserved */ + Internal_Reserved10_IRQn = 12, /*!< Internal reserved */ + Internal_Reserved11_IRQn = 13, /*!< Internal reserved */ + Internal_Reserved12_IRQn = 14, /*!< Internal reserved */ + Internal_Reserved13_IRQn = 15, /*!< Internal reserved */ + Internal_Reserved14_IRQn = 16, /*!< Internal reserved */ + Internal_BusError_IRQn = 17, /*!< Bus Error interrupt */ + Internal_PerfMon_IRQn = 18, /*!< Performance Monitor */ + Internal_Reserved_Max_IRQn = 19, /*!< Internal reserved Max */ +}; + +DeviceState *nuclei_eclic_create(hwaddr addr, + uint32_t aperture_size, uint32_t num_sources); +qemu_irq nuclei_eclic_get_irq(DeviceState *dev, int irq); +void nuclei_eclic_systimer_cb(DeviceState *dev); + +#endif -- 2.17.1