[PATCH v4 07/18] hw/intc: Add l2vic interrupt controller

Brian Cain <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
From: Sid Manning <[email protected]>

The Hexagon DSP requires an L2VIC to route up to 1024 external
interrupt sources through 4 VID output groups into the core's 8
interrupt inputs.  Add a device model for it.

Co-authored-by: Matheus Tavares Bernardino <[email protected]>
Co-authored-by: Damien Hedde <[email protected]>
Reviewed-by: Pierrick Bouvier <[email protected]>
Signed-off-by: Brian Cain <[email protected]>
---
 MAINTAINERS                    |   3 +
 docs/devel/hexagon-l2vic.rst   |  55 ++++
 docs/devel/index-internals.rst |   1 +
 include/hw/intc/hex-l2vic.h    |  61 ++++
 target/hexagon/cpu.h           |   2 +
 hw/intc/hex-l2vic.c            | 556 +++++++++++++++++++++++++++++++++
 target/hexagon/cpu.c           |   2 +
 target/hexagon/op_helper.c     |  16 +-
 hw/hexagon/Kconfig             |   1 +
 hw/intc/Kconfig                |   3 +
 hw/intc/meson.build            |   2 +
 hw/intc/trace-events           |   4 +
 12 files changed, 705 insertions(+), 1 deletion(-)
 create mode 100644 docs/devel/hexagon-l2vic.rst
 create mode 100644 include/hw/intc/hex-l2vic.h
 create mode 100644 hw/intc/hex-l2vic.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a28935c8986..a76366d6da5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -250,6 +250,8 @@ M: Brian Cain <[email protected]>
 R: Pierrick Bouvier <[email protected]>
 S: Supported
 F: target/hexagon/
+F: hw/intc/hex-l2vic.c
+F: include/hw/intc/hex-l2vic.h
 X: target/hexagon/idef-parser/
 X: target/hexagon/gen_idef_parser_funcs.py
 F: linux-user/hexagon/
@@ -262,6 +264,7 @@ F: gdbstub/gdb-xml/hexagon*.xml
 F: docs/system/target-hexagon.rst
 F: docs/system/hexagon/
 F: docs/devel/hexagon-sys.rst
+F: docs/devel/hexagon-l2vic.rst
 T: git https://github.com/qualcomm/qemu.git hex-next
 
 Hexagon idef-parser
diff --git a/docs/devel/hexagon-l2vic.rst b/docs/devel/hexagon-l2vic.rst
new file mode 100644
index 00000000000..9cb2a86871e
--- /dev/null
+++ b/docs/devel/hexagon-l2vic.rst
@@ -0,0 +1,55 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Hexagon L2 Vectored Interrupt Controller
+========================================
+
+
+.. code-block:: none
+
+                +-------------+                  +----------------------+
+                |    l2vic    |                  |     hexagon core     |
+                |             |                  |                      |
+    IRQ in ---->|             |                  |                      |
+    IRQ in ---->|    VID0    -|----------------->| irq2                 |
+      ...  ---->|             |                  |   |                  |
+    IRQ in ---->|             |                  |   v                  |
+                |     ...     |                  | <int steering>       |
+                |             |                  |  /   |    |    \     |
+    IRQ in ---->|             |                  | t0   t1   t2   t3 ...|
+    IRQ in ---->|    VIDN    -|                  |                      |
+      ...  ---->|             |                  |                      |
+    IRQ in ---->|             |                  | Global SREG File     |
+                |             |                  |                      |
+                |    State    |                  |                      |
+                | [      ] <--|==================|==> [ VID  ]          |
+                | [      ] <--|==================|==> [ VID1 ]          |
+                |             |                  |                      |
+                +-------------+                  +----------------------+
+
+L2VIC/Core Integration
+----------------------
+
+* hexagon core supports 8 external interrupt sources
+* l2vic supports 1024 input interrupts mapped among 4 output interrupts
+* l2vic has four output signals: { VID0, VID1, VID2, VID3 }
+* l2vic device has a bank of registers per-VID that can be used to query
+  the status or assert new interrupts.
+* Interrupts are 'steered' to threads based on { thread priority, 'EX' state,
+  thread interrupt mask, thread interrupt enable, global interrupt enable,
+  etc. }.
+* Any hardware thread could conceivably handle any input interrupt, dependent
+  on state.
+* The system register transfer instruction can read the VID0-VID3 values from
+  the l2vic when reading from hexagon core system registers "VID" and "VID1".
+* When l2vic VID0 has multiple active interrupts, it pulses the VID0 output
+  IRQ and stores the IRQ number for the VID0 register field.  Only after this
+  interrupt is cleared can the l2vic pulse the VID0 output IRQ again and provide
+  the next interrupt number on the VID0 register.
+* The ``ciad`` instruction clears the l2vic input interrupt and un-disables the
+  core interrupt.  If some/an l2vic VID0 interrupt is pending when this occurs,
+  the next interrupt should fire and any subsequent reads of the VID register
+  should reflect the newly raised interrupt.
+* In QEMU, on an external interrupt or an unmasked-pending interrupt,
+  all vCPUs are triggered (has_work==true) and each will grab the IO lock
+  while considering the steering logic to determine whether they're the thread
+  that must handle the interrupt.
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst
index a8f5e310df3..763cda1e76f 100644
--- a/docs/devel/index-internals.rst
+++ b/docs/devel/index-internals.rst
@@ -15,6 +15,7 @@ Details about QEMU's various subsystems including how to add features to them.
    clocks
    ebpf_rss
    hexagon-sys
+   hexagon-l2vic
    migration/index
    multi-process
    reset
diff --git a/include/hw/intc/hex-l2vic.h b/include/hw/intc/hex-l2vic.h
new file mode 100644
index 00000000000..edc278266cb
--- /dev/null
+++ b/include/hw/intc/hex-l2vic.h
@@ -0,0 +1,61 @@
+/*
+ * QEMU L2VIC Interrupt Controller
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_INTC_HEX_L2VIC_H
+#define HW_INTC_HEX_L2VIC_H
+
+#include "qom/object.h"
+
+#define TYPE_HEX_L2VIC "hex-l2vic"
+/*
+ * L2VIC Interface for CPU/GlobalReg interaction
+ */
+#define TYPE_HEX_L2VIC_INTERFACE "hex-l2vic-if"
+
+typedef struct HexL2VicInterface HexL2VicInterface;
+
+typedef struct HexL2VicInterfaceClass {
+    InterfaceClass parent_class;
+
+    uint32_t (*read_vid)(HexL2VicInterface *l2vic, uint32_t group);
+
+    /*
+     * Write the VID: unpack the fields into per-group VIDs.  This does
+     * not deliver or clear any interrupt; a pending interrupt stays
+     * gated until ciad.
+     */
+    void (*update_vid)(HexL2VicInterface *l2vic, uint32_t group,
+                        uint32_t value);
+
+    /* Clear interrupt using CIAD instruction */
+    void (*clear_interrupt)(HexL2VicInterface *l2vic);
+} HexL2VicInterfaceClass;
+
+DECLARE_OBJ_CHECKERS(HexL2VicInterface, HexL2VicInterfaceClass,
+                     HEX_L2VIC_INTERFACE, TYPE_HEX_L2VIC_INTERFACE);
+
+static inline uint32_t l2vic_read_vid(HexL2VicInterface *l2vic,
+                                       uint32_t group)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+    return k->read_vid(l2vic, group);
+}
+
+static inline void l2vic_update_vid(HexL2VicInterface *l2vic, uint32_t group,
+                                     uint32_t value)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+    k->update_vid(l2vic, group, value);
+}
+
+static inline void l2vic_clear_interrupt(HexL2VicInterface *l2vic)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+    k->clear_interrupt(l2vic);
+}
+
+#endif /* HW_INTC_HEX_L2VIC_H */
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 7694fd91fa8..f6c3c639325 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -39,6 +39,7 @@ typedef struct HexagonGlobalRegState HexagonGlobalRegState;
 #include "qemu/bitmap.h"
 
 #include "target/hexagon/reg_fields.h"
+#include "hw/intc/hex-l2vic.h"
 
 #define NUM_PREGS 4
 #define TOTAL_PER_THREAD_REGS 64
@@ -198,6 +199,7 @@ struct ArchCPU {
     uint32_t boot_addr;
     HexagonGlobalRegState *globalregs;
     uint32_t htid;
+    HexL2VicInterface *l2vic;
 #endif
 };
 
diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
new file mode 100644
index 00000000000..f07ec850d49
--- /dev/null
+++ b/hw/intc/hex-l2vic.c
@@ -0,0 +1,556 @@
+/*
+ * QEMU L2VIC Interrupt Controller
+ *
+ * Arm PrimeCell PL190 Vector Interrupt Controller was used as a reference.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitmap.h"
+#include "qemu/bitops.h"
+#include "hw/intc/hex-l2vic.h"
+#include "trace.h"
+
+#define L2VIC_VID_GRP_0 0x0 /* Read */
+#define L2VIC_VID_GRP_1 0x4 /* Read */
+#define L2VIC_VID_GRP_2 0x8 /* Read */
+#define L2VIC_VID_GRP_3 0xC /* Read */
+#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
+#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */
+#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */
+#define L2VIC_INT_TYPEn 0x280 /* Read/Write */
+#define L2VIC_INT_STATUSn 0x380 /* Read */
+#define L2VIC_INT_CLEARn 0x400 /* Write */
+#define L2VIC_SOFT_INTn 0x480 /* Write */
+#define L2VIC_INT_PENDINGn 0x500 /* Read */
+#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */
+#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */
+#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */
+#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */
+
+#define L2VIC_INTERRUPT_MAX 1024
+/*
+ * Note about l2vic groups:
+ * Each interrupt to L2VIC can be configured to associate with one of
+ * four groups.
+ * Group 0 interrupts go to IRQ2 via VID 0 (SSR: 0xC2, the default)
+ * Group 1 interrupts go to IRQ3 via VID 1 (SSR: 0xC3)
+ * Group 2 interrupts go to IRQ4 via VID 2 (SSR: 0xC4)
+ * Group 3 interrupts go to IRQ5 via VID 3 (SSR: 0xC5)
+ */
+
+static void bitmap32_write_word(uint32_t *bitmap, int word_offset, uint32_t val)
+{
+    bitmap[word_offset] = val;
+}
+
+static void bitmap32_clear_word(uint32_t *bitmap, int word_offset,
+                                uint32_t mask)
+{
+    bitmap[word_offset] &= ~mask;
+}
+
+static void bitmap32_set_word(uint32_t *bitmap, int word_offset, uint32_t mask)
+{
+    bitmap[word_offset] |= mask;
+}
+
+static uint32_t bitmap32_read_word(uint32_t *bitmap, int word_offset)
+{
+    return bitmap[word_offset];
+}
+
+OBJECT_DECLARE_SIMPLE_TYPE(HexL2VICState, HEX_L2VIC)
+
+#define SLICE_MAX (L2VIC_INTERRUPT_MAX / 32)
+#define L2VIC_REG_RANGE_SIZE 0x80
+
+typedef struct HexL2VICState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    MemoryRegion fast_iomem;
+    /*
+     * vid_group[i] is readable at L2VIC_VID_GRP_i (offset i*4): the irq
+     * last delivered through VID group i, 0-1023 so only 10 bits are used.
+     */
+    uint32_t vid_group[4];
+    /*
+     * Last irq delivered on any VID group; not specific to group 0.
+     * Used by the ciad path to clear the most-recently-delivered
+     * interrupt from int_status.
+     */
+    uint32_t vid;
+    DECLARE_BITMAP32(int_enable, L2VIC_INTERRUPT_MAX);
+    /* Asserted interrupts awaiting delivery once no VID is active */
+    DECLARE_BITMAP32(int_pending, L2VIC_INTERRUPT_MAX);
+    /* Which enabled interrupt is active */
+    DECLARE_BITMAP32(int_status, L2VIC_INTERRUPT_MAX);
+    /* Edge or Level interrupt */
+    DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX);
+    DECLARE_BITMAP32(int_group_n[4], L2VIC_INTERRUPT_MAX);
+    qemu_irq irq[8];
+} HexL2VICState;
+
+typedef enum {
+    L2VIC_OP_WRITE,
+    L2VIC_OP_CLEAR,
+    L2VIC_OP_SET,
+    L2VIC_OP_NONE,
+} L2VicWriteOp;
+
+typedef struct {
+    hwaddr base;
+    size_t state_offset;
+    L2VicWriteOp write_op;
+    bool write_only;
+} L2VicRegRange;
+
+static const L2VicRegRange l2vic_reg_ranges[] = {
+    { L2VIC_INT_ENABLEn, offsetof(HexL2VICState, int_enable),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_ENABLE_CLEARn, offsetof(HexL2VICState, int_enable),
+      L2VIC_OP_CLEAR, true },
+    { L2VIC_INT_ENABLE_SETn, offsetof(HexL2VICState, int_enable),
+      L2VIC_OP_SET, true },
+    { L2VIC_INT_TYPEn, offsetof(HexL2VICState, int_type),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_STATUSn, offsetof(HexL2VICState, int_status),
+      L2VIC_OP_NONE, false },
+    { L2VIC_INT_CLEARn, offsetof(HexL2VICState, int_status),
+      L2VIC_OP_CLEAR, true },
+    { L2VIC_SOFT_INTn, offsetof(HexL2VICState, int_pending),
+      L2VIC_OP_NONE, true },
+    { L2VIC_INT_PENDINGn, offsetof(HexL2VICState, int_pending),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_0, offsetof(HexL2VICState, int_group_n[0]),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_1, offsetof(HexL2VICState, int_group_n[1]),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_2, offsetof(HexL2VICState, int_group_n[2]),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_3, offsetof(HexL2VICState, int_group_n[3]),
+      L2VIC_OP_WRITE, false },
+};
+
+static uint32_t *l2vic_state_bitmap(HexL2VICState *s, size_t state_offset)
+{
+    return (uint32_t *)((char *)s + state_offset);
+}
+
+static bool l2vic_reg_read_range(HexL2VICState *s, hwaddr offset,
+                                 uint64_t *value)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) {
+        const L2VicRegRange *r = &l2vic_reg_ranges[i];
+
+        if (offset >= r->base &&
+            offset < r->base + L2VIC_REG_RANGE_SIZE) {
+            if (r->write_only) {
+                *value = 0;
+            } else {
+                uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset);
+                *value = bitmap32_read_word(bitmap,
+                                            (offset - r->base) >> 2);
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool l2vic_reg_write_range(HexL2VICState *s, hwaddr offset,
+                                  uint32_t val)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) {
+        const L2VicRegRange *r = &l2vic_reg_ranges[i];
+
+        if (offset >= r->base &&
+            offset < r->base + L2VIC_REG_RANGE_SIZE) {
+            uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset);
+            int word = (offset - r->base) >> 2;
+
+            switch (r->write_op) {
+            case L2VIC_OP_WRITE:
+                bitmap32_write_word(bitmap, word, val);
+                break;
+            case L2VIC_OP_CLEAR:
+                bitmap32_clear_word(bitmap, word, val);
+                break;
+            case L2VIC_OP_SET:
+                bitmap32_set_word(bitmap, word, val);
+                break;
+            case L2VIC_OP_NONE:
+                /* Read-only or handled elsewhere; ignore the write. */
+                break;
+            default:
+                g_assert_not_reached();
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * The four INT_GRPn_* register arrays are interleaved across irqs in
+ * blocks of 8: irq 0-7 live in group_n[0], irq 8-15 in group_n[1], irq
+ * 16-23 in group_n[2], irq 24-31 in group_n[3], irq 32-39 back in
+ * group_n[0], and so on.
+ */
+static uint32_t *get_int_group(HexL2VICState *s, int irq)
+{
+    return s->int_group_n[extract32(irq, 3, 2)];
+}
+
+static int find_slice(int irq)
+{
+    return irq / 32;
+}
+
+static int get_vid(HexL2VICState *s, int irq)
+{
+    uint32_t *group = get_int_group(s, irq);
+    uint32_t slice = group[find_slice(irq)];
+    uint32_t vid;
+    /*
+     * Each irq occupies a 4-bit field: bit 3 is the group-enable bit,
+     * bits 0-2 select the VID group.  Shift down to this irq's field.
+     */
+    uint32_t val = slice >> ((irq & 0x7) * 4);
+
+    if (!(val & 0x8)) {
+        return 0;
+    }
+    vid = val & 0x7;
+    if (vid >= ARRAY_SIZE(s->vid_group)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "L2VIC: irq %d requests invalid vid group %u\n",
+                      irq, vid);
+        return 0;
+    }
+    return vid;
+}
+
+static inline bool vid_active(HexL2VICState *s)
+{
+    const uint32_t size = L2VIC_INTERRUPT_MAX;
+    const uint32_t active_irq = find_first_bit32(s->int_status, size);
+    return active_irq != size;
+}
+
+static bool l2vic_update(HexL2VICState *s, int irq)
+{
+    bool pending;
+    bool enable;
+
+    if (vid_active(s)) {
+        return true;
+    }
+
+    pending = test_bit32(irq, s->int_pending);
+    enable = test_bit32(irq, s->int_enable);
+    if (pending && enable) {
+        int vid = get_vid(s, irq);
+        set_bit32(irq, s->int_status);
+        clear_bit32(irq, s->int_pending);
+        /*
+         * Only auto-disable for edge-triggered interrupts (type=1).
+         * Level-triggered interrupts (type=0, the default) keep their
+         * enable bit set across deliveries -- the firmware enables once
+         * and expects the interrupt to remain enabled.
+         */
+        if (test_bit32(irq, s->int_type)) {
+            clear_bit32(irq, s->int_enable);
+        }
+        s->vid = irq;
+        s->vid_group[vid] = irq;
+
+        qemu_irq_pulse(s->irq[vid + 2]);
+        trace_hex_l2vic_delivered(irq, vid);
+        return true;
+    }
+    return false;
+}
+
+static void l2vic_update_all(HexL2VICState *s)
+{
+    for (int i = 0; i < L2VIC_INTERRUPT_MAX; i++) {
+        if (l2vic_update(s, i)) {
+            /* once vid is active, no-one else can set it until ciad */
+            return;
+        }
+    }
+}
+
+static void l2vic_set_irq(void *opaque, int irq, int level)
+{
+    HexL2VICState *s = (HexL2VICState *)opaque;
+
+    if (level) {
+        set_bit32(irq, s->int_pending);
+    }
+    l2vic_update(s, irq);
+}
+
+static void l2vic_write(void *opaque, hwaddr offset, uint64_t val,
+                        unsigned size)
+{
+    HexL2VICState *s = (HexL2VICState *)opaque;
+
+    trace_hex_l2vic_reg_write((unsigned)offset, (uint32_t)val);
+
+    if (!l2vic_reg_write_range(s, offset, val)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: offset 0x%" HWADDR_PRIx " unimplemented\n",
+                      __func__, offset);
+    }
+
+    /* SOFT_INT also sets pending for edge-triggered interrupts */
+    if (offset >= L2VIC_SOFT_INTn &&
+        offset < L2VIC_SOFT_INTn + L2VIC_REG_RANGE_SIZE && val) {
+        int base_irq = ((offset - L2VIC_SOFT_INTn) >> 2) * 32;
+        uint32_t bits = val;
+        int bit;
+
+        while ((bit = ctz32(bits)) < 32) {
+            int irq = base_irq + bit;
+
+            if (test_bit32(irq, s->int_type)) {
+                set_bit32(irq, s->int_pending);
+            }
+            bits &= ~(1u << bit);
+        }
+    }
+
+    l2vic_update_all(s);
+}
+
+static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+    uint64_t value;
+    HexL2VICState *s = (HexL2VICState *)opaque;
+
+    if (offset <= L2VIC_VID_GRP_3) {
+        value = s->vid_group[offset >> 2];
+    } else if (!l2vic_reg_read_range(s, offset, &value)) {
+        value = 0;
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "L2VIC: %s: offset 0x%" HWADDR_PRIx "\n", __func__,
+                      offset);
+    }
+
+    trace_hex_l2vic_reg_read((unsigned)offset, (uint32_t)value);
+    return value;
+}
+
+static const MemoryRegionOps l2vic_ops = {
+    .read = l2vic_read,
+    .write = l2vic_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .valid.unaligned = false,
+};
+
+#define FASTL2VIC_ENABLE 0x0
+#define FASTL2VIC_DISABLE 0x1
+#define FASTL2VIC_INT 0x2
+
+static void fastl2vic_write(void *opaque, hwaddr offset, uint64_t val,
+                            unsigned size)
+{
+    if (offset == 0) {
+        uint32_t cmd = (val >> 16) & 0x3;
+        uint32_t irq = val & 0x3ff;
+        uint32_t slice = (irq / 32) * 4;
+        val = 1 << (irq % 32);
+
+        if (cmd == FASTL2VIC_ENABLE) {
+            l2vic_write(opaque, L2VIC_INT_ENABLE_SETn + slice, val, size);
+        } else if (cmd == FASTL2VIC_DISABLE) {
+            l2vic_write(opaque, L2VIC_INT_ENABLE_CLEARn + slice, val, size);
+        } else if (cmd == FASTL2VIC_INT) {
+            l2vic_write(opaque, L2VIC_SOFT_INTn + slice, val, size);
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: invalid write cmd %" PRId32 "\n",
+                          __func__, cmd);
+        }
+        return;
+    }
+    qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write offset 0x%08" HWADDR_PRIx
+            "\n", __func__, offset);
+}
+
+static uint64_t fastl2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+    return 0;
+}
+
+static const MemoryRegionOps fastl2vic_ops = {
+    .read = fastl2vic_read,
+    .write = fastl2vic_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .valid.unaligned = false,
+};
+
+static uint32_t l2vic_interface_read_vid_impl(HexL2VicInterface *iface,
+                                               uint32_t group)
+{
+    HexL2VICState *s = HEX_L2VIC(iface);
+    uint32_t result = 0;
+
+    if (group == 0) {
+        /* VID register combines vid_group[0] (VID0) and vid_group[1] (VID1) */
+        result = deposit32(result, 0, 16, s->vid_group[0]);
+        result = deposit32(result, 16, 16, s->vid_group[1]);
+    } else if (group == 1) {
+        /* VID1 register combines vid_group[2] (VID2) and vid_group[3] (VID3) */
+        result = deposit32(result, 0, 16, s->vid_group[2]);
+        result = deposit32(result, 16, 16, s->vid_group[3]);
+    }
+    return result;
+}
+
+static void l2vic_interface_update_vid_impl(HexL2VicInterface *iface,
+                                            uint32_t group, uint32_t value)
+{
+    HexL2VICState *s = HEX_L2VIC(iface);
+
+    if (group == 0) {
+        s->vid_group[0] = extract32(value, 0, 16);
+        s->vid_group[1] = extract32(value, 16, 16);
+    } else if (group == 1) {
+        s->vid_group[2] = extract32(value, 0, 16);
+        s->vid_group[3] = extract32(value, 16, 16);
+    }
+
+    l2vic_update_all(s);
+}
+
+static void l2vic_interface_clear_interrupt_impl(HexL2VicInterface *iface)
+{
+    HexL2VICState *s = HEX_L2VIC(iface);
+
+    if (s->vid < L2VIC_INTERRUPT_MAX) {
+        clear_bit32(s->vid, s->int_status);
+    }
+    l2vic_update_all(s);
+}
+
+static void l2vic_reset_hold(Object *obj, ResetType type G_GNUC_UNUSED)
+{
+    HexL2VICState *s = HEX_L2VIC(obj);
+
+    memset(s->int_enable, 0, sizeof(s->int_enable));
+    memset(s->int_pending, 0, sizeof(s->int_pending));
+    memset(s->int_status, 0, sizeof(s->int_status));
+    memset(s->int_type, 0, sizeof(s->int_type));
+    memset(s->int_group_n, 0, sizeof(s->int_group_n));
+    memset(s->vid_group, 0, sizeof(s->vid_group));
+    s->vid = 0;
+
+    l2vic_update_all(s);
+}
+
+static void reset_irq_handler(void *opaque, int irq, int level)
+{
+    Object *obj = OBJECT(opaque);
+
+    if (level) {
+        l2vic_reset_hold(obj, RESET_TYPE_COLD);
+    }
+}
+
+static void l2vic_init(Object *obj)
+{
+    DeviceState *dev = DEVICE(obj);
+    HexL2VICState *s = HEX_L2VIC(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    int i;
+
+    memory_region_init_io(&s->iomem, obj, &l2vic_ops, s, "l2vic", 0x1000);
+    sysbus_init_mmio(sbd, &s->iomem);
+    memory_region_init_io(&s->fast_iomem, obj, &fastl2vic_ops, s, "fast",
+                          0x10000);
+    sysbus_init_mmio(sbd, &s->fast_iomem);
+
+    qdev_init_gpio_in(dev, l2vic_set_irq, L2VIC_INTERRUPT_MAX);
+    qdev_init_gpio_in_named(dev, reset_irq_handler, "reset", 1);
+    for (i = 0; i < 8; i++) {
+        sysbus_init_irq(sbd, &s->irq[i]);
+    }
+}
+
+static const VMStateDescription vmstate_l2vic = {
+    .name = "l2vic",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields =
+        (VMStateField[]){
+            VMSTATE_UINT32_ARRAY(vid_group, HexL2VICState, 4),
+            VMSTATE_UINT32(vid, HexL2VICState),
+            VMSTATE_UINT32_ARRAY(int_enable, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_ARRAY(int_type, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_ARRAY(int_status, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_ARRAY(int_pending, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_2DARRAY(int_group_n, HexL2VICState, 4, SLICE_MAX),
+            VMSTATE_END_OF_LIST() }
+};
+
+static void l2vic_interface_class_init(ObjectClass *klass, const void *data)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_CLASS(klass);
+
+    k->read_vid = l2vic_interface_read_vid_impl;
+    k->update_vid = l2vic_interface_update_vid_impl;
+    k->clear_interrupt = l2vic_interface_clear_interrupt_impl;
+}
+
+static void l2vic_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_l2vic;
+    rc->phases.hold = l2vic_reset_hold;
+}
+
+static const TypeInfo l2vic_interface_info = {
+    .name = TYPE_HEX_L2VIC_INTERFACE,
+    .parent = TYPE_INTERFACE,
+    .class_size = sizeof(HexL2VicInterfaceClass),
+    .class_init = l2vic_interface_class_init,
+};
+
+static const TypeInfo l2vic_info = {
+    .name = TYPE_HEX_L2VIC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(HexL2VICState),
+    .instance_init = l2vic_init,
+    .class_init = l2vic_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_HEX_L2VIC_INTERFACE },
+        { }
+    },
+};
+
+static const TypeInfo l2vic_types[] = {
+    l2vic_interface_info,
+    l2vic_info,
+};
+
+DEFINE_TYPES(l2vic_types)
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 12d27ce1381..75cae450299 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -65,6 +65,8 @@ static const Property hexagon_cpu_properties[] = {
     DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB,
                      HexagonTLBState *),
     DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffff),
+    DEFINE_PROP_LINK("l2vic", HexagonCPU, l2vic,
+                     TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *),
     DEFINE_PROP_LINK("global-regs", HexagonCPU, globalregs,
         TYPE_HEXAGON_GLOBALREG, HexagonGlobalRegState *),
     DEFINE_PROP_UINT32("htid", HexagonCPU, htid, 0),
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 3ce223caba3..8fb3d43c33b 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -40,6 +40,7 @@
 #include "hw/hexagon/hexagon_globalreg.h"
 #include "hex_mmu.h"
 #include "hw/hexagon/hexagon_tlb.h"
+#include "hw/intc/hex-l2vic.h"
 #include "hex_interrupts.h"
 #include "hexswi.h"
 #endif
@@ -1564,7 +1565,20 @@ void HELPER(raise_stack_overflow)(CPUHexagonState *env, uint32_t slot,
 
 void HELPER(ciad)(CPUHexagonState *env, uint32_t mask)
 {
-    g_assert_not_reached();
+    uint32_t ipendad;
+    uint32_t iad;
+    HexagonCPU *cpu;
+
+    BQL_LOCK_GUARD();
+    cpu = env_archcpu(env);
+    ipendad = hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                                      env->threadId);
+    iad = fGET_FIELD(ipendad, IPENDAD_IAD);
+    fSET_FIELD(ipendad, IPENDAD_IAD, iad & ~(mask));
+    hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                            ipendad, env->threadId);
+    l2vic_clear_interrupt(cpu->l2vic);
+    hex_interrupt_update(env);
 }
 
 void HELPER(siad)(CPUHexagonState *env, uint32_t mask)
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
index 121c548bbb9..c75090c44a1 100644
--- a/hw/hexagon/Kconfig
+++ b/hw/hexagon/Kconfig
@@ -3,6 +3,7 @@ config HEX_DSP
     default y
     depends on HEXAGON
     select CPU_CLUSTER
+    select HEX_L2VIC
 
 config HEX_VIRT
     bool
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index 636d00b7e88..097de4efc5d 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -8,6 +8,9 @@ config I8259
 config PL190
     bool
 
+config HEX_L2VIC
+    bool
+
 config IOAPIC
     bool
     select I8259
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index fac2d228f9b..9077a3f3297 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -74,6 +74,8 @@ specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('xics_spapr.c', 'spapr_xi
 specific_ss.add(when: 'CONFIG_XIVE', if_true: files('xive.c'))
 specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'],
 		if_true: files('spapr_xive_kvm.c'))
+
+specific_ss.add(when: 'CONFIG_HEX_L2VIC', if_true: files('hex-l2vic.c'))
 specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
 specific_ss.add(when: 'CONFIG_LOONGSON_IPI_COMMON', if_true: files('loongson_ipi_common.c'))
 specific_ss.add(when: 'CONFIG_LOONGSON_IPI', if_true: files('loongson_ipi.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index e7d6c304487..7512c6e6004 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -337,6 +337,10 @@ sh_intc_register(const char *s, int id, unsigned short v, int c, int m) "%s %u -
 sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx"
 sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx"
 sh_intc_set(int id, int enable) "setting interrupt group %d to %d"
+# hex-l2vic.c
+hex_l2vic_reg_write(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32
+hex_l2vic_reg_read(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32
+hex_l2vic_delivered(int irq, int vid) "l2vic: delivered %d (vid %d)"
 
 # loongson_ipi.c
 loongson_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.