Model the TriCore IR which manages Service Request Nodes (SRNs). Each SRN has priority, enable, TOS (CPU selector) and request flag bits. The IR picks the highest-priority pending SRN per TOS and raises the corresponding CPU output line.
Originally-by: David Brenken <[email protected]> Signed-off-by: Parthiban Nallathambi <[email protected]> --- hw/intc/Kconfig | 3 + hw/intc/meson.build | 1 + hw/intc/tricore_ir.c | 264 +++++++++++++++++++++++++++++++++++++++++++ include/hw/intc/tricore_ir.h | 62 ++++++++++ 4 files changed, 330 insertions(+) diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index 636d00b7e8..686c7d22fe 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -117,3 +117,6 @@ config LOONGARCH_EXTIOI config LOONGARCH_DINTC bool + +config TRICORE_IRBUS + bool diff --git a/hw/intc/meson.build b/hw/intc/meson.build index fac2d228f9..10c316b62b 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -88,3 +88,4 @@ specific_ss.add(when: 'CONFIG_LOONGARCH_EXTIOI', if_true: files('loongarch_extio specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_LOONGARCH_EXTIOI'], if_true: files('loongarch_extioi_kvm.c')) specific_ss.add(when: 'CONFIG_LOONGARCH_DINTC', if_true: files('loongarch_dintc.c')) +system_ss.add(when: 'CONFIG_TRICORE_IRBUS', if_true: files('tricore_ir.c')) diff --git a/hw/intc/tricore_ir.c b/hw/intc/tricore_ir.c new file mode 100644 index 0000000000..44861bf899 --- /dev/null +++ b/hw/intc/tricore_ir.c @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * QEMU TriCore Interrupt Router (IR) + * + * Copyright (c) 2017 David Brenken <[email protected]> + * Copyright (c) 2026 Parthiban Nallathambi <[email protected]> + */ + +#include "qemu/osdep.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/registerfields.h" +#include "hw/core/sysbus.h" +#include "hw/intc/tricore_ir.h" +#include "qemu/log.h" + +static void irq_evaluate(void *opaque) +{ + TriCoreIRState *s = opaque; + uint16_t tos_irq[8] = { + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + }; + uint8_t tos_priority[8] = { 0 }; + uint32_t srcnum; + uint8_t tos_idx; + + for (srcnum = 0; srcnum < s->num_irqs; srcnum++) { + uint32_t src_reg = s->src_regs[srcnum]; + uint8_t priority = FIELD_EX32(src_reg, SRC, SRPN); + uint8_t tos = FIELD_EX32(src_reg, SRC_TC3X, TOS); + + if ((src_reg & R_SRC_SRR_MASK) && + (src_reg & R_SRC_TC3X_SRE_MASK)) { + if (qemu_loglevel_mask(CPU_LOG_INT)) { + qemu_log("tricore_ir: pending irq #%u" + " (priority %u, TOS %u)\n", + srcnum, priority, tos); + } + tos_priority[tos] = priority; + tos_irq[tos] = srcnum; + } + } + + for (tos_idx = 0; tos_idx < s->num_isps; tos_idx++) { + if (tos_irq[tos_idx] == 0xFFFF) { + s->lwsr[tos_idx] = 0; + qemu_irq_lower(s->isp_irqs[tos_idx]); + } else { + s->lwsr[tos_idx] = + FIELD_DP32(0, LWSR, STAT, 1) | + FIELD_DP32(0, LWSR, ID, tos_irq[tos_idx]) | + FIELD_DP32(0, LWSR, VALID, 1) | + FIELD_DP32(0, LWSR, PN, tos_priority[tos_idx]); + + if (qemu_loglevel_mask(CPU_LOG_INT)) { + qemu_log("tricore_ir: raise TOS %u irq line" + " (irq: %u, priority: %u)\n", + tos_idx, tos_irq[tos_idx], + tos_priority[tos_idx]); + } + qemu_irq_raise(s->isp_irqs[tos_idx]); + } + } +} + +static void irq_handler(void *opaque, int srcnum, int level) +{ + TriCoreIRState *s = opaque; + uint32_t src_reg = s->src_regs[srcnum]; + + if (level) { + if (src_reg & R_SRC_SRR_MASK) { + src_reg |= R_SRC_IOV_MASK; + } + src_reg |= R_SRC_SRR_MASK; + } + s->src_regs[srcnum] = src_reg; + + irq_evaluate(opaque); +} + +void tricore_ir_irq_acknowledge(TriCoreIRState *s, uint16_t irq, uint8_t vm) +{ + uint32_t src_reg; + + if (!s || irq >= s->num_irqs || vm >= ARRAY_SIZE(s->lwsr)) { + return; + } + + /* Capture LWSR into LASR before clearing */ + s->lasr = s->lwsr[vm] | FIELD_DP32(0, LASR, ENTER, 1); + + src_reg = s->src_regs[irq]; + src_reg &= ~R_SRC_SRR_MASK; + s->src_regs[irq] = src_reg; + + if (FIELD_EX32(s->lwsr[vm], LWSR, ID) == irq) { + irq_evaluate(s); + } +} + +static uint64_t tricore_ir_src_regs_read(void *opaque, hwaddr offset, + unsigned size) +{ + TriCoreIRState *s = opaque; + hwaddr srcnum = offset >> 2; + + if (srcnum < s->num_irqs) { + return s->src_regs[srcnum]; + } + + return 0; +} + +static void tricore_ir_src_regs_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + TriCoreIRState *s = opaque; + hwaddr srcnum = offset >> 2; + uint32_t srcc; + bool setr; + bool clrr; + + if (srcnum >= s->num_irqs) { + qemu_log_mask(LOG_GUEST_ERROR, + "tricore_ir: write to unmapped SRC offset" + " 0x%" HWADDR_PRIx "\n", offset); + return; + } + + srcc = value & ~(R_SRC_SETR_MASK | R_SRC_CLRR_MASK); + setr = value & R_SRC_SETR_MASK; + clrr = value & R_SRC_CLRR_MASK; + + if (setr && !clrr) { + srcc |= R_SRC_SRR_MASK; + } else if (clrr && !setr) { + srcc &= ~R_SRC_SRR_MASK; + } + + s->src_regs[srcnum] = srcc; + + irq_evaluate(opaque); +} + +static const MemoryRegionOps tricore_ir_src_regs_ops = { + .read = tricore_ir_src_regs_read, + .write = tricore_ir_src_regs_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +/* + * IR INT registers (LWSR, LASR) + * + * TC3x LWSR offset: 0x200 + x * 0x10 (x = CPU index) + * TC3x LASR offset: 0x200 + x * 0x10 + 4 + */ +#define IR_INT_LWSR_BASE 0x200 +#define IR_INT_CPU_STRIDE 0x10 +#define IR_INT_ID_OFF 0x08 + +/* Module ID: IR module number 0x00B9, type 0xC0, rev 0x13 */ +#define IR_INT_MOD_ID 0x00B9C013 + +static uint64_t tricore_ir_intregs_read(void *opaque, hwaddr offset, + unsigned size) +{ + TriCoreIRState *s = opaque; + uint32_t x; + uint32_t sub; + + if (offset == IR_INT_ID_OFF) { + return IR_INT_MOD_ID; + } + + /* TC3x LWSR: 0x200 + x*0x10, LASR: 0x200 + x*0x10 + 4 */ + if (offset >= IR_INT_LWSR_BASE && + offset < IR_INT_LWSR_BASE + IR_INT_CPU_STRIDE * 8) { + x = (offset - IR_INT_LWSR_BASE) / IR_INT_CPU_STRIDE; + sub = (offset - IR_INT_LWSR_BASE) % IR_INT_CPU_STRIDE; + if (sub == 0 && x < 8) { + return s->lwsr[x]; + } + if (sub == 4 && x < 8) { + return s->lasr; + } + } + + return 0; +} + +static void tricore_ir_intregs_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + /* LWSR/LASR are read-only from software side */ +} + +static const MemoryRegionOps tricore_ir_intregs_ops = { + .read = tricore_ir_intregs_read, + .write = tricore_ir_intregs_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void tricore_ir_init(Object *obj) +{ + TriCoreIRState *s = TRICORE_IR(obj); + + memory_region_init_io(&s->src_region, obj, &tricore_ir_src_regs_ops, + s, "tricore_ir.src", 0x4000); + memory_region_init_io(&s->int_region, obj, &tricore_ir_intregs_ops, + s, "tricore_ir.int", 0x1000); +} + +static void tricore_ir_realize(DeviceState *dev, Error **errp) +{ + TriCoreIRState *s = TRICORE_IR(dev); + + s->src_regs = g_malloc0_n(s->num_irqs, sizeof(uint32_t)); + s->isp_irqs = g_malloc_n(s->num_isps, sizeof(qemu_irq)); + + qdev_init_gpio_in_named(DEVICE(s), irq_handler, "irq", s->num_irqs); + qdev_init_gpio_out_named(DEVICE(s), s->isp_irqs, "isp", s->num_isps); + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->int_region); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->src_region); +} + +static const Property tricore_ir_properties[] = { + DEFINE_PROP_UINT8("num-isps", TriCoreIRState, num_isps, 1), + DEFINE_PROP_UINT16("num-irqs", TriCoreIRState, num_irqs, 256), +}; + +static void tricore_ir_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->user_creatable = false; + dc->realize = tricore_ir_realize; + device_class_set_props(dc, tricore_ir_properties); +} + +static const TypeInfo tricore_ir_info = { + .name = TYPE_TRICORE_IR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(TriCoreIRState), + .instance_init = tricore_ir_init, + .class_init = tricore_ir_class_init, +}; + +static void tricore_ir_register(void) +{ + type_register_static(&tricore_ir_info); +} + +type_init(tricore_ir_register) diff --git a/include/hw/intc/tricore_ir.h b/include/hw/intc/tricore_ir.h new file mode 100644 index 0000000000..d7b167e50d --- /dev/null +++ b/include/hw/intc/tricore_ir.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * QEMU TriCore Interrupt Router (IR) + * + * Copyright (c) 2017 David Brenken <[email protected]> + * Copyright (c) 2026 Parthiban Nallathambi <[email protected]> + */ + +#ifndef HW_TRICORE_IR_H +#define HW_TRICORE_IR_H + +#include "hw/core/sysbus.h" +#include "hw/core/registerfields.h" +#include "qom/object.h" + +#define TYPE_TRICORE_IR "tricore_ir" +OBJECT_DECLARE_SIMPLE_TYPE(TriCoreIRState, TRICORE_IR) + +/* SRC register fields common to all TriCore variants */ +FIELD(SRC, SRPN, 0, 8) +FIELD(SRC, SRR, 24, 1) +FIELD(SRC, CLRR, 25, 1) +FIELD(SRC, SETR, 26, 1) +FIELD(SRC, IOV, 27, 1) +FIELD(SRC, IOVCLR, 28, 1) + +/* TC3x SRC bit layout */ +FIELD(SRC_TC3X, SRE, 10, 1) +FIELD(SRC_TC3X, TOS, 11, 3) + +/* LWSR register fields */ +FIELD(LWSR, PN, 0, 8) +FIELD(LWSR, VALID, 12, 1) +FIELD(LWSR, ID, 16, 9) +FIELD(LWSR, STAT, 31, 1) + +/* LASR register fields */ +FIELD(LASR, PN, 0, 8) +FIELD(LASR, ID, 16, 11) +FIELD(LASR, ENTER, 31, 1) + +struct TriCoreIRState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion src_region; + MemoryRegion int_region; + + uint32_t *src_regs; + uint32_t lwsr[8]; + uint32_t lasr; + + qemu_irq *isp_irqs; + + uint8_t num_isps; + uint16_t num_irqs; +}; + +void tricore_ir_irq_acknowledge(TriCoreIRState *s, uint16_t irq, uint8_t vm); + +#endif /* HW_TRICORE_IR_H */ -- 2.47.3
