Hi Brian,
+Peter for commit 3d7680fb18c ("bitops.h: Define bit operations
on 'uint32_t' arrays").
On 15/7/26 20:56, Brian Cain wrote:
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. This device model implements
the register interface, interrupt steering, and edge/level
type handling needed by the sysemu machine models.
Co-authored-by: Matheus Tavares Bernardino <[email protected]>
Co-authored-by: Damien Hedde <[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 | 87 ++++++
target/hexagon/cpu.h | 2 +
hw/intc/hex-l2vic.c | 532 +++++++++++++++++++++++++++++++++
target/hexagon/cpu.c | 2 +
target/hexagon/op_helper.c | 21 +-
hw/hexagon/Kconfig | 1 +
hw/intc/Kconfig | 3 +
hw/intc/meson.build | 2 +
hw/intc/trace-events | 4 +
12 files changed, 712 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 ecb8cfdc41e..7281a281632 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 b89bab9b306..84985973490 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..f6519bcd6c2
--- /dev/null
+++ b/include/hw/intc/hex-l2vic.h
@@ -0,0 +1,87 @@
+/*
+ * 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 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)
+ */
+
+#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;
+
+ /* Read VID register for given group */
+ uint32_t (*read_vid)(HexL2VicInterface *l2vic, uint32_t group);
+
+ /* Update VID register value */
+ 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);
+
+/* Convenience functions for interface users */
+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..6b2dc6bb091
--- /dev/null
+++ b/hw/intc/hex-l2vic.c
@@ -0,0 +1,532 @@
+/*
+ * 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/lockable.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"
+
+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;
+
+ QemuMutex active;
Can you describe what is protected by this mutex?
+ MemoryRegion iomem;
+ MemoryRegion fast_iomem;
+ /*
+ * offset 0:vid group 0 etc, 10 bits in each group
+ * are used:
+ */
+ uint32_t vid_group[4];
+ uint32_t vid0;
+ /* Enable interrupt source */
+ DECLARE_BITMAP32(int_enable, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
This bitmap alignment here is dubious. I suppose it relates to my other
comment on find_first_bit32(). If the callee requires alignment, then
the QEMU_ALIGNED() attribute should be used at the definition IMO, not
here. Peter, WDYT?
+ /* Present for debugging, not used */
+ DECLARE_BITMAP32(int_pending, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
+ /* Which enabled interrupt is active */
+ DECLARE_BITMAP32(int_status, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
Apparently int_status is mutex-protected.
+ /* Edge or Level interrupt */
+ DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
+ /* L2 interrupt group 0-3 0x600-0x7FF */
+ DECLARE_BITMAP32(int_group_n0, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
+ DECLARE_BITMAP32(int_group_n1, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
+ DECLARE_BITMAP32(int_group_n2, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
+ DECLARE_BITMAP32(int_group_n3, L2VIC_INTERRUPT_MAX) QEMU_ALIGNED(16);
+ qemu_irq irq[8];
+} HexL2VICState;
+
+typedef enum {
+ L2VIC_OP_WRITE,
+ L2VIC_OP_CLEAR,
+ L2VIC_OP_SET,
+} 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_WRITE, false },
+ { L2VIC_INT_CLEARn, offsetof(HexL2VICState, int_status),
+ L2VIC_OP_CLEAR, true },
+ { L2VIC_SOFT_INTn, offsetof(HexL2VICState, int_enable),
+ L2VIC_OP_SET, true },
+ { L2VIC_INT_PENDINGn, offsetof(HexL2VICState, int_pending),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_0, offsetof(HexL2VICState, int_group_n0),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_1, offsetof(HexL2VICState, int_group_n1),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_2, offsetof(HexL2VICState, int_group_n2),
+ L2VIC_OP_WRITE, false },
+ { L2VIC_INT_GRPn_3, offsetof(HexL2VICState, int_group_n3),
+ 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;
+ default:
+ g_assert_not_reached();
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Find out if this irq is associated with a group other than
+ * the default group
+ */
+static uint32_t *get_int_group(HexL2VICState *s, int irq)
+{
+ int n = irq & 0x1f;
+ if (n < 8) {
+ return s->int_group_n0;
+ }
+ if (n < 16) {
+ return s->int_group_n1;
+ }
+ if (n < 24) {
+ return s->int_group_n2;
+ }
+ return s->int_group_n3;
+}
+
+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)];
+ /* Mask with 0x7 to remove the GRP:EN bit */
+ uint32_t val = slice >> ((irq & 0x7) * 4);
+ if (val & 0x8) {
+ return val & 0x7;
+ } else {
+ return 0;
+ }
+}
+
+static inline bool vid_active(HexL2VICState *s)
+{
+ /* scan all 1024 bits in int_status array */
+ const uint32_t size = L2VIC_INTERRUPT_MAX;
+ const uint32_t active_irq = find_first_bit32(s->int_status, size);
Is int_status protected here?
+ 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);
Is int_status protected here? Do we want to use the
set_bit32_atomic() alternative?
+ 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->vid0 = 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;
+
+ QEMU_LOCK_GUARD(&s->active);
Ah, here you protect int_pending, described as "Present for debugging,
not used".
+ 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;
+
+ QEMU_LOCK_GUARD(&s->active);
Here you protect the whole device via MMIO access.
+ 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 irq = ctz32((uint32_t)val);
+ irq += ((offset - L2VIC_SOFT_INTn) >> 2) * 32;
+
+ if (test_bit32(irq, s->int_type)) {
+ set_bit32(irq, s->int_pending);
+ }
+ }
+
+ l2vic_update_all(s);
+}
+
+static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint64_t value;
+ HexL2VICState *s = (HexL2VICState *)opaque;
+
+ QEMU_LOCK_GUARD(&s->active);
Yet another whole device via MMIO access guard. What are the other
uses you want to protect?
+
+ 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 const MemoryRegionOps fastl2vic_ops = {
+ .write = fastl2vic_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+/* L2VIC Interface Implementation */
+static uint32_t l2vic_interface_read_vid_impl(HexL2VicInterface *iface,
+ uint32_t group)
+{
+ HexL2VICState *s = HEX_L2VIC(iface);
+ uint32_t result = 0;
+
+ QEMU_LOCK_GUARD(&s->active);
Ah, another whole device via MMIO access guard.
+ if (group == 0) {
+ /* VID register: VID1 (bits 16-31), VID0 (bits 0-15) */
+ result = deposit32(result, 0, 16, s->vid_group[0]);
+ result = deposit32(result, 16, 16, s->vid_group[1]);
+ } else if (group == 1) {
+ /* VID1 register: VID3 (bits 16-31), VID2 (bits 0-15) */
+ 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);
+
+ QEMU_LOCK_GUARD(&s->active);
+
+ if (group == 0) {
+ /* VID register: unpack VID0 and VID1 */
+ s->vid_group[0] = extract32(value, 0, 16);
+ s->vid_group[1] = extract32(value, 16, 16);
+ } else if (group == 1) {
+ /* VID1 register: unpack VID2 and VID3 */
+ 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);
+
+ QEMU_LOCK_GUARD(&s->active);
OK, this is apparently a non-MMIO call via the HexL2VicInterfaceClass.
Is that why we need this mutex? So far I can not see any consumer of
this interface in this series.
+ if (s->vid0 < L2VIC_INTERRUPT_MAX) {
+ clear_bit32(s->vid0, s->int_status);
+ }
+ l2vic_update_all(s);
+}
+
+static void l2vic_reset_hold(Object *obj, ResetType type G_GNUC_UNUSED)
+{
+ HexL2VICState *s = HEX_L2VIC(obj);
+
+ QEMU_LOCK_GUARD(&s->active);
+ 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_n0, 0, sizeof(s->int_group_n0));
+ memset(s->int_group_n1, 0, sizeof(s->int_group_n1));
+ memset(s->int_group_n2, 0, sizeof(s->int_group_n2));
+ memset(s->int_group_n3, 0, sizeof(s->int_group_n3));
+ memset(s->vid_group, 0, sizeof(s->vid_group));
+ s->vid0 = 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]);
+ }
+ qemu_mutex_init(&s->active);
+}
+
+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(vid0, 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_ARRAY(int_group_n0, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_ARRAY(int_group_n1, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_ARRAY(int_group_n2, HexL2VICState, SLICE_MAX),
+ VMSTATE_UINT32_ARRAY(int_group_n3, HexL2VICState, 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 void l2vic_register_types(void)
+{
+ type_register_static(&l2vic_interface_info);
+ type_register_static(&l2vic_info);
+}
+
+type_init(l2vic_register_types)
Prefer DEFINE_TYPES() for multiple TypeInfo.