On 7/14/2026 8:51 AM, 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            | 511 +++++++++++++++++++++++++++++++++
>  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, 691 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 e25df9493c5..1240d180b85 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..a284ae8a567
> --- /dev/null
> +++ b/hw/intc/hex-l2vic.c
> @@ -0,0 +1,511 @@
> +/*
> + * 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)
> +
> +typedef struct HexL2VICState {
> +    SysBusDevice parent_obj;
> +
> +    QemuMutex active;
> +    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);
> +    /* 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);
> +    /* 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;
> +
> +/*
> + * 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);
> +    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->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);
> +    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);
> +    trace_hex_l2vic_reg_write((unsigned)offset, (uint32_t)val);
> +
> +    if (offset >= L2VIC_INT_ENABLEn &&
> +               offset < (L2VIC_INT_ENABLE_CLEARn)) {
> +        bitmap32_write_word(s->int_enable,
> +            (offset - L2VIC_INT_ENABLEn) >> 2, val);
> +    } else if (offset >= L2VIC_INT_ENABLE_CLEARn &&
> +               offset < L2VIC_INT_ENABLE_SETn) {
> +        bitmap32_clear_word(s->int_enable,
> +            (offset - L2VIC_INT_ENABLE_CLEARn) >> 2, val);
> +    } else if (offset >= L2VIC_INT_ENABLE_SETn && offset < L2VIC_INT_TYPEn) {
> +        bitmap32_set_word(s->int_enable,
> +            (offset - L2VIC_INT_ENABLE_SETn) >> 2, val);
> +    } else if (offset >= L2VIC_INT_TYPEn && offset < L2VIC_INT_TYPEn + 0x80) 
> {
> +        bitmap32_write_word(s->int_type,
> +            (offset - L2VIC_INT_TYPEn) >> 2, val);
> +    } else if (offset >= L2VIC_INT_STATUSn && offset < L2VIC_INT_CLEARn) {
> +        bitmap32_write_word(s->int_status,
> +            (offset - L2VIC_INT_STATUSn) >> 2, val);
> +    } else if (offset >= L2VIC_INT_CLEARn && offset < L2VIC_SOFT_INTn) {
> +        bitmap32_clear_word(s->int_status,
> +            (offset - L2VIC_INT_CLEARn) >> 2, val);
> +    } else if (offset >= L2VIC_INT_PENDINGn &&
> +               offset < L2VIC_INT_PENDINGn + 0x80) {
> +        bitmap32_write_word(s->int_pending,
> +            (offset - L2VIC_INT_PENDINGn) >> 2, val);
> +    } else if (offset >= L2VIC_SOFT_INTn && offset < L2VIC_INT_PENDINGn) {
> +        int irq;
> +        hwaddr byteoffset = offset - L2VIC_SOFT_INTn;
> +
> +        bitmap32_set_word(s->int_enable, (offset - L2VIC_SOFT_INTn) >> 2, 
> val);
> +        if (val) {
> +            irq = ctz32((uint32_t)val);
> +            irq += byteoffset * 8;
> +
> +            if (test_bit32(irq, s->int_type)) {
> +                set_bit32(irq, s->int_pending);
> +            }
> +        }
> +    } else if (offset >= L2VIC_INT_GRPn_0 && offset < L2VIC_INT_GRPn_1) {
> +        bitmap32_write_word(s->int_group_n0,
> +            (offset - L2VIC_INT_GRPn_0) >> 2, val);
> +    } else if (offset >= L2VIC_INT_GRPn_1 && offset < L2VIC_INT_GRPn_2) {
> +        bitmap32_write_word(s->int_group_n1,
> +            (offset - L2VIC_INT_GRPn_1) >> 2, val);
> +    } else if (offset >= L2VIC_INT_GRPn_2 && offset < L2VIC_INT_GRPn_3) {
> +        bitmap32_write_word(s->int_group_n2,
> +            (offset - L2VIC_INT_GRPn_2) >> 2, val);
> +    } else if (offset >= L2VIC_INT_GRPn_3 && offset < L2VIC_INT_GRPn_3 + 
> 0x80) {
> +        bitmap32_write_word(s->int_group_n3,
> +            (offset - L2VIC_INT_GRPn_3) >> 2, val);
> +    } else {
> +        qemu_log_mask(LOG_UNIMP,
> +                      "%s: offset 0x%" HWADDR_PRIx " unimplemented\n",
> +                      __func__, offset);
> +    }

The if cascade is pretty hard to read (same for read function).
Could we solve this with a static const array holding struct with
{offset, size, state_offset_read, state_offset_write, write_only}, and
have a single loop to walk through all the possible subranges?

> +
> +    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);
> +
> +    if (offset == L2VIC_VID_GRP_0) {
> +        value = s->vid_group[0];
> +    } else if (offset == L2VIC_VID_GRP_1) {
> +        value = s->vid_group[1];
> +    } else if (offset == L2VIC_VID_GRP_2) {
> +        value = s->vid_group[2];
> +    } else if (offset == L2VIC_VID_GRP_3) {
> +        value = s->vid_group[3];
> +    } else if (offset >= L2VIC_INT_ENABLEn &&
> +               offset < L2VIC_INT_ENABLE_CLEARn) {
> +        value = bitmap32_read_word(s->int_enable,
> +            (offset - L2VIC_INT_ENABLEn) >> 2);
> +    } else if (offset >= L2VIC_INT_ENABLE_CLEARn &&
> +               offset < L2VIC_INT_ENABLE_SETn) {
> +        value = 0;
> +    } else if (offset >= L2VIC_INT_ENABLE_SETn && offset < L2VIC_INT_TYPEn) {
> +        value = 0;
> +    } else if (offset >= L2VIC_INT_TYPEn && offset < L2VIC_INT_TYPEn + 0x80) 
> {
> +        value = bitmap32_read_word(s->int_type,
> +            (offset - L2VIC_INT_TYPEn) >> 2);
> +    } else if (offset >= L2VIC_INT_STATUSn && offset < L2VIC_INT_CLEARn) {
> +        value = bitmap32_read_word(s->int_status,
> +            (offset - L2VIC_INT_STATUSn) >> 2);
> +    } else if (offset >= L2VIC_INT_CLEARn && offset < L2VIC_SOFT_INTn) {
> +        /* INT_CLEARn is write-only, return 0 on read */
> +        value = 0;
> +    } else if (offset >= L2VIC_SOFT_INTn && offset < L2VIC_INT_PENDINGn) {
> +        value = 0;
> +    } else if (offset >= L2VIC_INT_PENDINGn &&
> +               offset < L2VIC_INT_PENDINGn + 0x80) {
> +        value = bitmap32_read_word(s->int_pending,
> +            (offset - L2VIC_INT_PENDINGn) >> 2);
> +    } else if (offset >= L2VIC_INT_GRPn_0 && offset < L2VIC_INT_GRPn_1) {
> +        value = bitmap32_read_word(s->int_group_n0,
> +            (offset - L2VIC_INT_GRPn_0) >> 2);
> +    } else if (offset >= L2VIC_INT_GRPn_1 && offset < L2VIC_INT_GRPn_2) {
> +        value = bitmap32_read_word(s->int_group_n1,
> +            (offset - L2VIC_INT_GRPn_1) >> 2);
> +    } else if (offset >= L2VIC_INT_GRPn_2 && offset < L2VIC_INT_GRPn_3) {
> +        value = bitmap32_read_word(s->int_group_n2,
> +            (offset - L2VIC_INT_GRPn_2) >> 2);
> +    } else if (offset >= L2VIC_INT_GRPn_3 && offset < L2VIC_INT_GRPn_3 + 
> 0x80) {
> +        value = bitmap32_read_word(s->int_group_n3,
> +            (offset - L2VIC_INT_GRPn_3) >> 2);
> +    } else {
> +        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);
> +    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);
> +    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;
> +

You can move this declaration in the for loop.

> +    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)
> diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
> index 42d93e5da47..be56ac0419f 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 125952aee59..0d88ae65e75 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
> @@ -1557,7 +1558,25 @@ 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 = cpu->globalregs ?
> +        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
> +                               env->threadId) : 0;
> +    iad = fGET_FIELD(ipendad, IPENDAD_IAD);
> +    fSET_FIELD(ipendad, IPENDAD_IAD, iad & ~(mask));
> +    if (cpu->globalregs) {
> +        hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
> +                                ipendad, env->threadId);
> +    }
> +    if (cpu->l2vic) {
> +        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 52065ab3b22..191c6c5d65c 100644
> --- a/hw/hexagon/Kconfig
> +++ b/hw/hexagon/Kconfig
> @@ -2,6 +2,7 @@ config HEX_DSP
>      bool
>      default y
>      depends on HEXAGON
> +    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


Reply via email to