diff mbox series

[RFC,03/11] hw/intc: Add CLIC device

Message ID 20210409074857.166082-4-zhiwei_liu@c-sky.com (mailing list archive)
State New, archived
Headers show
Series RISC-V: support clic v0.9 specification | expand

Commit Message

LIU Zhiwei April 9, 2021, 7:48 a.m. UTC
The Core-Local Interrupt Controller (CLIC) provides low-latency,
vectored, pre-emptive interrupts for RISC-V systems.

The CLIC also supports a new Selective Hardware Vectoring feature
that allow users to optimize each interrupt for either faster
response or smaller code size.

Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
---
 default-configs/devices/riscv32-softmmu.mak |   1 +
 default-configs/devices/riscv64-softmmu.mak |   1 +
 hw/intc/Kconfig                             |   3 +
 hw/intc/meson.build                         |   1 +
 hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
 include/hw/intc/riscv_clic.h                | 103 +++
 target/riscv/cpu.h                          |   2 +
 7 files changed, 946 insertions(+)
 create mode 100644 hw/intc/riscv_clic.c
 create mode 100644 include/hw/intc/riscv_clic.h

Comments

Alistair Francis April 19, 2021, 11:25 p.m. UTC | #1
On Fri, Apr 9, 2021 at 5:56 PM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:
>
> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true: files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.

Shouldn't this maintain the original copyright?

Alistair

> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the harts
> + * are placed contiguously in the memory space, followed by the S-mode CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode])) {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level << 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) / sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) * sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand the bit
> +             * field, from FW perspective clicintip[i]=zero means no interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode = extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value & MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in pending
> +     * bit, while the level-triggered interrupt should be kept in the level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits, 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
LIU Zhiwei April 20, 2021, 12:57 a.m. UTC | #2
On 2021/4/20 上午7:25, Alistair Francis wrote:
> On Fri, Apr 9, 2021 at 5:56 PM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:
>> The Core-Local Interrupt Controller (CLIC) provides low-latency,
>> vectored, pre-emptive interrupts for RISC-V systems.
>>
>> The CLIC also supports a new Selective Hardware Vectoring feature
>> that allow users to optimize each interrupt for either faster
>> response or smaller code size.
>>
>> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
>> ---
>>   default-configs/devices/riscv32-softmmu.mak |   1 +
>>   default-configs/devices/riscv64-softmmu.mak |   1 +
>>   hw/intc/Kconfig                             |   3 +
>>   hw/intc/meson.build                         |   1 +
>>   hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>>   include/hw/intc/riscv_clic.h                | 103 +++
>>   target/riscv/cpu.h                          |   2 +
>>   7 files changed, 946 insertions(+)
>>   create mode 100644 hw/intc/riscv_clic.c
>>   create mode 100644 include/hw/intc/riscv_clic.h
>>
>> diff --git a/default-configs/devices/riscv32-softmmu.mak b/default-configs/devices/riscv32-softmmu.mak
>> index d847bd5692..1430c30588 100644
>> --- a/default-configs/devices/riscv32-softmmu.mak
>> +++ b/default-configs/devices/riscv32-softmmu.mak
>> @@ -5,6 +5,7 @@
>>   #CONFIG_PCI_DEVICES=n
>>   CONFIG_SEMIHOSTING=y
>>   CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>   # Boards:
>>   #
>> diff --git a/default-configs/devices/riscv64-softmmu.mak b/default-configs/devices/riscv64-softmmu.mak
>> index d5eec75f05..396800bbbd 100644
>> --- a/default-configs/devices/riscv64-softmmu.mak
>> +++ b/default-configs/devices/riscv64-softmmu.mak
>> @@ -5,6 +5,7 @@
>>   #CONFIG_PCI_DEVICES=n
>>   CONFIG_SEMIHOSTING=y
>>   CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>   # Boards:
>>   #
>> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>> index f4694088a4..5bf492b48f 100644
>> --- a/hw/intc/Kconfig
>> +++ b/hw/intc/Kconfig
>> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>   config SIFIVE_PLIC
>>       bool
>>
>> +config RISCV_CLIC
>> +    bool
>> +
>>   config GOLDFISH_PIC
>>       bool
>>
>> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>> index 1c299039f6..2aa71b6738 100644
>> --- a/hw/intc/meson.build
>> +++ b/hw/intc/meson.build
>> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c'))
>>   specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>>   specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true: files('sifive_clint.c'))
>>   specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c'))
>> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>>   specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>   specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                  if_true: files('xics_kvm.c'))
>> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>> new file mode 100644
>> index 0000000000..8ad534c506
>> --- /dev/null
>> +++ b/hw/intc/riscv_clic.c
>> @@ -0,0 +1,835 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> Shouldn't this maintain the original copyright?

It is OK.

As it has changed a lot from the origin version, it is just another 
device called sifive_clic  in my branch, and this is the device called 
riscv_clic.

If that is the rule, I am very glad to obey it.

Thanks for pointing it out.

Zhiwei

> Alistair
>
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "sysemu/qtest.h"
>> +#include "target/riscv/cpu.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/intc/riscv_clic.h"
>> +
>> +/*
>> + * The 2-bit trig WARL field specifies the trigger type and polarity for each
>> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is defined as
>> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
>> + */
>> +
>> +static inline TRIG_TYPE
>> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int npbits = clic->clicintctlbits - clic->nlbits;
>> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>> +
>> +    if (npbits < 0) {
>> +        return UINT8_MAX;
>> +    }
>> +    /* unused priority bits are set to 1 */
>> +    return (intctl & mask_priority) | mask_padding;
>> +}
>> +
>> +static void
>> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>> +                         uint8_t *mode, uint8_t *level,
>> +                         uint8_t *priority)
>> +{
>> +    *mode = intcfg >> 8;
>> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
>> +}
>> +
>> +/*
>> + * In a system with multiple harts, the M-mode CLIC regions for all the harts
>> + * are placed contiguously in the memory space, followed by the S-mode CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>> +static size_t
>> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int irq)
>> +{
>> +    size_t mode_offset = 0;
>> +    size_t unit = clic->num_harts * clic->num_sources;
>> +
>> +    switch (mode) {
>> +    case PRV_M:
>> +        mode_offset = 0;
>> +        break;
>> +    case PRV_S:
>> +        mode_offset = unit;
>> +        break;
>> +    case PRV_U:
>> +        mode_offset = clic->prv_s ? 2 * unit : unit;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid mode %d\n", mode);
>> +        exit(1);
>> +    }
>> +    return mode_offset + hartid * clic->num_sources + irq;
>> +}
>> +
>> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>> +{
>> +    /*
>> +     * Scan active list for highest priority pending interrupts
>> +     * comparing against this harts mintstatus register and interrupt
>> +     * the core if we have a higher priority interrupt to deliver
>> +     */
>> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>> +    CPURISCVState *env = &cpu->env;
>> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>> +
>> +    int il[4] = {
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>> +            clic->mintthresh), /* PRV_U */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>> +            clic->sintthresh), /* PRV_S */
>> +        0,                     /* reserverd */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>> +            clic->uintthresh)  /* PRV_M */
>> +    };
>> +
>> +    /* Get sorted list of enabled interrupts for this hart */
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>> +    size_t active_count = clic->active_count[hartid];
>> +    uint8_t mode, level, priority;
>> +
>> +    /* Loop through the enabled interrupts sorted by mode+priority+level */
>> +    while (active_count) {
>> +        size_t irq_offset;
>> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>> +                                 &priority);
>> +        if (mode < env->priv || (mode == env->priv && level <= il[mode])) {
>> +            /*
>> +             * No pending interrupts with high enough mode+priority+level
>> +             * break and clear pending interrupt for this hart
>> +             */
>> +            break;
>> +        }
>> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, active->irq);
>> +        /* Check pending interrupt with high enough mode+priority+level */
>> +        if (clic->clicintip[irq_offset]) {
>> +            /* Clean vector edge-triggered pending */
>> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>> +                clic->clicintip[irq_offset] = 0;
>> +            }
>> +            /* Post pending interrupt for this hart */
>> +            clic->exccode[hartid] = active->irq | mode << 12 | level << 14;
>> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>> +            return;
>> +        }
>> +        /* Check next enabled interrupt */
>> +        active_count--;
>> +        active++;
>> +    }
>> +}
>> +
>> +/*
>> + * Any interrupt i that is not accessible to S-mode or U-Mode
>> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>> + */
>> +static bool
>> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    if (!clic->prv_s && !clic->prv_u) { /* M */
>> +        return mode == PRV_M;
>> +    } else if (!clic->prv_s) { /* M/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
>> +                                                          (mode == PRV_U);
>> +        default:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 for M/U hart");
>> +            exit(1);
>> +        }
>> +    } else { /* M/S/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
>> +                                                          (mode == PRV_S);
>> +        case 2:
>> +            return mode == clic->clicintattr[irq_offset];
>> +        case 3:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
>> +            exit(1);
>> +        }
>> +    }
>> +    return false;
>> +}
>> +
>> +/*
>> + * For level-triggered interrupts, software writes to pending bits are
>> + * ignored completely. (Section 3.4)
>> + */
>> +static bool
>> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t value)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    clic->clicintip[irq_offset] = !!value;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +/*
>> + * For security purpose, the field can only be set to a privilege
>> + * level that is equal mode to or lower than the currently running
>> + * privilege level.(Section 3.6)
>> + */
>> +
>> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t value)
>> +{
>> +    int mode = extract64(value, 6, 2);
>> +
>> +    if (!qtest_enabled()) {
>> +        CPURISCVState *env = current_cpu->env_ptr;
>> +        if (env->priv < mode) {
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
>> +{
>> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
>> +           (i->irq & 0xfff);             /* Highest irq number */
>> +}
>> +
>> +static int riscv_clic_active_compare(const void *a, const void *b)
>> +{
>> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +                                              sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) / sizeof(CLICActiveInterrupt);
>> +        size_t sz = (--(*active_count) - elem) * sizeof(CLICActiveInterrupt);
>> +        assert(result);
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand the bit
>> +             * field, from FW perspective clicintip[i]=zero means no interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode = extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>> +        /*
>> +         * The implemented bits are kept left-justified in the most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/* Return target interrupt mode */
>> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>> +    switch (mode) {
>> +    case 0:
>> +        return PRV_M;
>> +    case 1:
>> +        assert(clic->prv_s || clic->prv_u);
>> +        return clic->prv_s ? PRV_S : PRV_U;
>> +    case 2:
>> +        assert(clic->prv_s && clic->prv_u);
>> +        return PRV_U;
>> +    default:
>> +        g_assert_not_reached();
>> +        break;
>> +    }
>> +}
>> +
>> +/* Return target hart id */
>> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>> +    int hart_unit = 4 * clic->num_sources;
>> +
>> +    return (addr % mode_unit) / hart_unit;
>> +}
>> +
>> +/* Return target interrupt number */
>> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int hart_unit = 4 * clic->num_sources;
>> +    return (addr % hart_unit) / 4;
>> +}
>> +
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                {
>> +                    uint8_t nlbits = extract32(value, 1, 4);
>> +                    uint8_t nmbits = extract32(value, 5, 2);
>> +
>> +                    /*
>> +                     * The 4-bit cliccfg.nlbits WARL field.
>> +                     * Valid values are 0—8.
>> +                     */
>> +                    if (nlbits <= 8) {
>> +                        clic->nlbits = nlbits;
>> +                    }
>> +                    /* Valid values are given by implemented priviledges */
>> +                    if (clic->prv_s && clic->prv_u) {
>> +                        if (nmbits <= 2) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else if (clic->prv_u) {
>> +                        if (nmbits <= 1) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else {
>> +                        assert(!clic->prv_s);
>> +                        if (nmbits == 0) {
>> +                            clic->nmbits = 0;
>> +                        }
>> +                    }
>> +                    clic->nvbits = extract32(value, 0, 1);
>> +                    break;
>> +                }
>> +            case 1: /* clicinfo, read-only register */
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: write read-only clicinfo.\n");
>> +                break;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                {
>> +                    uint32_t interrupt_number = value & MAKE_64BIT_MASK(0, 13);
>> +                    if (interrupt_number <= clic->num_sources) {
>> +                        value &= ~MAKE_64BIT_MASK(13, 18);
>> +                        clic->clicinttrig[index - 0x10] = value;
>> +                    }
>> +                    break;
>> +                }
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    clic->mintthresh = value;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
>> +                              addr);
>> +                return;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                riscv_clic_hart_write(clic, addr, value, size, mode,
>> +                                      hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +}
>> +
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                /*
>> +                 * clicinttrig register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 enable
>> +                 * 30:13 reserved (WARL 0)
>> +                 * 12:0 interrupt_number
>> +                 */
>> +                return clic->clicinttrig[index - 0x10] &
>> +                       ~MAKE_64BIT_MASK(13, 18);
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    return clic->mintthresh;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                return riscv_clic_hart_read(clic, addr, mode, hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void riscv_clic_set_irq(void *opaque, int id, int level)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    int irq, hartid, mode;
>> +    hwaddr addr = 4 * id;
>> +    TRIG_TYPE type;
>> +
>> +    hartid = riscv_clic_get_hartid(clic, addr);
>> +    mode = riscv_clic_get_mode(clic, addr);
>> +    irq = riscv_clic_get_irq(clic, addr);
>> +    type = riscv_clic_get_trigger_type(clic, id);
>> +
>> +    /*
>> +     * In general, the edge-triggered interrupt state should be kept in pending
>> +     * bit, while the level-triggered interrupt should be kept in the level
>> +     * state of the incoming wire.
>> +     *
>> +     * For CLIC, model the level-triggered interrupt by read-only pending bit.
>> +     */
>> +    if (level) {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +        case POSITIVE_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case NEG_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        case NEG_EDGE:
>> +            break;
>> +        }
>> +    } else {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case POSITIVE_EDGE:
>> +            break;
>> +        case NEG_LEVEL:
>> +        case NEG_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
>> +{
>> +    CPURISCVState *env = (CPURISCVState *)opaque;
>> +    RISCVCLICState *clic = env->clic;
>> +    CPUState *cpu = env_cpu(env);
>> +
>> +    if (level) {
>> +        env->exccode = clic->exccode[cpu->cpu_index];
>> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps riscv_clic_ops = {
>> +    .read = riscv_clic_read,
>> +    .write = riscv_clic_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 8
>> +    }
>> +};
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>> +
>> +    /* Allocate irq through gpio, so that we can use qtest */
>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>> +
>> +    for (i = 0; i < clic->num_harts; i++) {
>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>> +                                         &cpu->env, 1);
>> +        qdev_connect_gpio_out(dev, i, irq);
>> +        cpu->env.clic = clic;
>> +    }
>> +}
>> +
>> +static Property riscv_clic_properties[] = {
>> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
>> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits, 0),
>> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = riscv_clic_realize;
>> +    device_class_set_props(dc, riscv_clic_properties);
>> +}
>> +
>> +static const TypeInfo riscv_clic_info = {
>> +    .name          = TYPE_RISCV_CLIC,
>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(RISCVCLICState),
>> +    .class_init    = riscv_clic_class_init,
>> +};
>> +
>> +static void riscv_clic_register_types(void)
>> +{
>> +    type_register_static(&riscv_clic_info);
>> +}
>> +
>> +type_init(riscv_clic_register_types)
>> +
>> +/*
>> + * riscv_clic_create:
>> + *
>> + * @addr: base address of M-Mode CLIC memory-mapped registers
>> + * @prv_s: have smode region
>> + * @prv_u: have umode region
>> + * @num_harts: number of CPU harts
>> + * @num_sources: number of interrupts supporting by each aperture
>> + * @clicintctlbits: bits are actually implemented in the clicintctl registers
>> + * @version: clic version, such as "v0.9"
>> + *
>> + * Returns: the device object
>> + */
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version)
>> +{
>> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>> +
>> +    assert(num_sources <= 4096);
>> +    assert(num_harts <= 1024);
>> +    assert(clicintctlbits <= 8);
>> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>> +
>> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>> +    qdev_prop_set_string(dev, "version", version);
>> +
>> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>> +    return dev;
>> +}
>> +
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>> +}
>> +
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
>> +    clic->clicintip[irq_offset] = 0;
>> +}
>> +
>> +/*
>> + * The new CLIC interrupt-handling mode is encoded as a new state in
>> + * the existing WARL xtvec register, where the low two bits of  are 11.
>> + */
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>> +{
>> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
>> +    return env->clic && ((xtvec & 0x3) == 3);
>> +}
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>> +                               int *il, int *irq)
>> +{
>> +    *irq = extract32(exccode, 0, 12);
>> +    *mode = extract32(exccode, 12, 2);
>> +    *il = extract32(exccode, 14, 8);
>> +}
>> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
>> new file mode 100644
>> index 0000000000..e5f89672a6
>> --- /dev/null
>> +++ b/include/hw/intc/riscv_clic.h
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef RISCV_CLIC_H
>> +#define RISCV_CLIC_H
>> +
>> +#include "hw/irq.h"
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_RISCV_CLIC "riscv_clic"
>> +#define RISCV_CLIC(obj) \
>> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>> +
>> +/*
>> + * CLIC per hart active interrupts
>> + *
>> + * We maintain per hart lists of enabled interrupts sorted by
>> + * mode+level+priority. The sorting is done on the configuration path
>> + * so that the interrupt delivery fastpath can linear scan enabled
>> + * interrupts in priority order.
>> + */
>> +typedef struct CLICActiveInterrupt {
>> +    uint16_t intcfg;
>> +    uint16_t irq;
>> +} CLICActiveInterrupt;
>> +
>> +typedef enum TRIG_TYPE {
>> +    POSITIVE_LEVEL,
>> +    POSITIVE_EDGE,
>> +    NEG_LEVEL,
>> +    NEG_EDGE,
>> +} TRIG_TYPE;
>> +
>> +typedef struct RISCVCLICState {
>> +    /*< private >*/
>> +    SysBusDevice parent_obj;
>> +
>> +    /*< public >*/
>> +
>> +    /* Implementaion parameters */
>> +    bool prv_s;
>> +    bool prv_u;
>> +    uint32_t num_harts;
>> +    uint32_t num_sources;
>> +    uint32_t clic_size;
>> +    uint32_t clic_mmode_base;
>> +    uint32_t clicintctlbits;
>> +    uint64_t mclicbase;
>> +    char *version;
>> +
>> +    /* Global configuration */
>> +    uint8_t nmbits;
>> +    uint8_t nlbits;
>> +    uint8_t nvbits;
>> +    uint32_t clicinfo;
>> +    uint32_t clicinttrig[32];
>> +
>> +    /* Aperture configuration */
>> +    uint8_t *clicintip;
>> +    uint8_t *clicintie;
>> +    uint8_t *clicintattr;
>> +    uint8_t *clicintctl;
>> +
>> +    /* Complatible with v0.8 */
>> +    uint32_t mintthresh;
>> +    uint32_t sintthresh;
>> +    uint32_t uintthresh;
>> +
>> +    /* QEMU implementaion related fields */
>> +    uint32_t *exccode;
>> +    CLICActiveInterrupt *active_list;
>> +    size_t *active_count;
>> +    MemoryRegion mmio;
>> +    qemu_irq *cpu_irqs;
>> +} RISCVCLICState;
>> +
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version);
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int *irq);
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq);
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq);
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq);
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>> +#endif
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index a5eab26a69..9e389d7bbf 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -33,6 +33,7 @@
>>   #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>   #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>   #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>   #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>>   #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
>> @@ -247,6 +248,7 @@ struct CPURISCVState {
>>       /* Fields from here on are preserved across CPU reset. */
>>       QEMUTimer *timer; /* Internal timer */
>>       void *clic;       /* clic interrupt controller */
>> +    uint32_t exccode; /* clic irq encode */
>>   };
>>
>>   OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>> --
>> 2.25.1
>>
>>
Alistair Francis April 22, 2021, 12:16 a.m. UTC | #3
On Tue, Apr 20, 2021 at 10:57 AM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:
>
>
> On 2021/4/20 上午7:25, Alistair Francis wrote:
> > On Fri, Apr 9, 2021 at 5:56 PM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:
> >> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> >> vectored, pre-emptive interrupts for RISC-V systems.
> >>
> >> The CLIC also supports a new Selective Hardware Vectoring feature
> >> that allow users to optimize each interrupt for either faster
> >> response or smaller code size.
> >>
> >> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> >> ---
> >>   default-configs/devices/riscv32-softmmu.mak |   1 +
> >>   default-configs/devices/riscv64-softmmu.mak |   1 +
> >>   hw/intc/Kconfig                             |   3 +
> >>   hw/intc/meson.build                         |   1 +
> >>   hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
> >>   include/hw/intc/riscv_clic.h                | 103 +++
> >>   target/riscv/cpu.h                          |   2 +
> >>   7 files changed, 946 insertions(+)
> >>   create mode 100644 hw/intc/riscv_clic.c
> >>   create mode 100644 include/hw/intc/riscv_clic.h
> >>
> >> diff --git a/default-configs/devices/riscv32-softmmu.mak b/default-configs/devices/riscv32-softmmu.mak
> >> index d847bd5692..1430c30588 100644
> >> --- a/default-configs/devices/riscv32-softmmu.mak
> >> +++ b/default-configs/devices/riscv32-softmmu.mak
> >> @@ -5,6 +5,7 @@
> >>   #CONFIG_PCI_DEVICES=n
> >>   CONFIG_SEMIHOSTING=y
> >>   CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> >> +CONFIG_RISCV_CLIC=y
> >>
> >>   # Boards:
> >>   #
> >> diff --git a/default-configs/devices/riscv64-softmmu.mak b/default-configs/devices/riscv64-softmmu.mak
> >> index d5eec75f05..396800bbbd 100644
> >> --- a/default-configs/devices/riscv64-softmmu.mak
> >> +++ b/default-configs/devices/riscv64-softmmu.mak
> >> @@ -5,6 +5,7 @@
> >>   #CONFIG_PCI_DEVICES=n
> >>   CONFIG_SEMIHOSTING=y
> >>   CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> >> +CONFIG_RISCV_CLIC=y
> >>
> >>   # Boards:
> >>   #
> >> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> >> index f4694088a4..5bf492b48f 100644
> >> --- a/hw/intc/Kconfig
> >> +++ b/hw/intc/Kconfig
> >> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
> >>   config SIFIVE_PLIC
> >>       bool
> >>
> >> +config RISCV_CLIC
> >> +    bool
> >> +
> >>   config GOLDFISH_PIC
> >>       bool
> >>
> >> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> >> index 1c299039f6..2aa71b6738 100644
> >> --- a/hw/intc/meson.build
> >> +++ b/hw/intc/meson.build
> >> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c'))
> >>   specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
> >>   specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true: files('sifive_clint.c'))
> >>   specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c'))
> >> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
> >>   specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
> >>   specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
> >>                  if_true: files('xics_kvm.c'))
> >> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> >> new file mode 100644
> >> index 0000000000..8ad534c506
> >> --- /dev/null
> >> +++ b/hw/intc/riscv_clic.c
> >> @@ -0,0 +1,835 @@
> >> +/*
> >> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> >> + *
> >> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> > Shouldn't this maintain the original copyright?
>
> It is OK.
>
> As it has changed a lot from the origin version, it is just another
> device called sifive_clic  in my branch, and this is the device called
> riscv_clic.

I can't comment on how much it changed. In your cover letter you say
that it's based on an implementation so I would then guess that this
should keep the copyright if you used that as a starting point.

Alistair

>
> If that is the rule, I am very glad to obey it.
>
> Thanks for pointing it out.
>
> Zhiwei
>
> > Alistair
> >
> >> + *
> >> + * This program is free software; you can redistribute it and/or modify it
> >> + * under the terms and conditions of the GNU General Public License,
> >> + * version 2 or later, as published by the Free Software Foundation.
> >> + *
> >> + * This program is distributed in the hope it will be useful, but WITHOUT
> >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> >> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> >> + * more details.
> >> + *
> >> + * You should have received a copy of the GNU General Public License along with
> >> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> >> + */
> >> +
> >> +#include "qemu/osdep.h"
> >> +#include "qapi/error.h"
> >> +#include "qemu/log.h"
> >> +#include "hw/sysbus.h"
> >> +#include "sysemu/qtest.h"
> >> +#include "target/riscv/cpu.h"
> >> +#include "hw/qdev-properties.h"
> >> +#include "hw/intc/riscv_clic.h"
> >> +
> >> +/*
> >> + * The 2-bit trig WARL field specifies the trigger type and polarity for each
> >> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> >> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is defined as
> >> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> >> + */
> >> +
> >> +static inline TRIG_TYPE
> >> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> >> +{
> >> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> >> +}
> >> +
> >> +static inline bool
> >> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> >> +{
> >> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> >> +}
> >> +
> >> +static inline bool
> >> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> >> +{
> >> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> >> +}
> >> +
> >> +static uint8_t
> >> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> >> +{
> >> +    int nlbits = clic->nlbits;
> >> +
> >> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> >> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> >> +    /* unused level bits are set to 1 */
> >> +    return (intctl & mask_il) | mask_padding;
> >> +}
> >> +
> >> +static uint8_t
> >> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> >> +{
> >> +    int npbits = clic->clicintctlbits - clic->nlbits;
> >> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> >> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> >> +
> >> +    if (npbits < 0) {
> >> +        return UINT8_MAX;
> >> +    }
> >> +    /* unused priority bits are set to 1 */
> >> +    return (intctl & mask_priority) | mask_padding;
> >> +}
> >> +
> >> +static void
> >> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> >> +                         uint8_t *mode, uint8_t *level,
> >> +                         uint8_t *priority)
> >> +{
> >> +    *mode = intcfg >> 8;
> >> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> >> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> >> +}
> >> +
> >> +/*
> >> + * In a system with multiple harts, the M-mode CLIC regions for all the harts
> >> + * are placed contiguously in the memory space, followed by the S-mode CLIC
> >> + * regions for all harts. (Section 3.11)
> >> + */
> >> +static size_t
> >> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int irq)
> >> +{
> >> +    size_t mode_offset = 0;
> >> +    size_t unit = clic->num_harts * clic->num_sources;
> >> +
> >> +    switch (mode) {
> >> +    case PRV_M:
> >> +        mode_offset = 0;
> >> +        break;
> >> +    case PRV_S:
> >> +        mode_offset = unit;
> >> +        break;
> >> +    case PRV_U:
> >> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> >> +        break;
> >> +    default:
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid mode %d\n", mode);
> >> +        exit(1);
> >> +    }
> >> +    return mode_offset + hartid * clic->num_sources + irq;
> >> +}
> >> +
> >> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> >> +{
> >> +    /*
> >> +     * Scan active list for highest priority pending interrupts
> >> +     * comparing against this harts mintstatus register and interrupt
> >> +     * the core if we have a higher priority interrupt to deliver
> >> +     */
> >> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> >> +    CPURISCVState *env = &cpu->env;
> >> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> >> +
> >> +    int il[4] = {
> >> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> >> +            clic->mintthresh), /* PRV_U */
> >> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> >> +            clic->sintthresh), /* PRV_S */
> >> +        0,                     /* reserverd */
> >> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> >> +            clic->uintthresh)  /* PRV_M */
> >> +    };
> >> +
> >> +    /* Get sorted list of enabled interrupts for this hart */
> >> +    size_t hart_offset = hartid * clic->num_sources;
> >> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> >> +    size_t active_count = clic->active_count[hartid];
> >> +    uint8_t mode, level, priority;
> >> +
> >> +    /* Loop through the enabled interrupts sorted by mode+priority+level */
> >> +    while (active_count) {
> >> +        size_t irq_offset;
> >> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> >> +                                 &priority);
> >> +        if (mode < env->priv || (mode == env->priv && level <= il[mode])) {
> >> +            /*
> >> +             * No pending interrupts with high enough mode+priority+level
> >> +             * break and clear pending interrupt for this hart
> >> +             */
> >> +            break;
> >> +        }
> >> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, active->irq);
> >> +        /* Check pending interrupt with high enough mode+priority+level */
> >> +        if (clic->clicintip[irq_offset]) {
> >> +            /* Clean vector edge-triggered pending */
> >> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> >> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> >> +                clic->clicintip[irq_offset] = 0;
> >> +            }
> >> +            /* Post pending interrupt for this hart */
> >> +            clic->exccode[hartid] = active->irq | mode << 12 | level << 14;
> >> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> >> +            return;
> >> +        }
> >> +        /* Check next enabled interrupt */
> >> +        active_count--;
> >> +        active++;
> >> +    }
> >> +}
> >> +
> >> +/*
> >> + * Any interrupt i that is not accessible to S-mode or U-Mode
> >> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> >> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> >> + */
> >> +static bool
> >> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int irq)
> >> +{
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> >> +        return mode == PRV_M;
> >> +    } else if (!clic->prv_s) { /* M/U */
> >> +        switch (clic->nmbits) {
> >> +        case 0:
> >> +            return mode == PRV_M;
> >> +        case 1:
> >> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
> >> +                                                          (mode == PRV_U);
> >> +        default:
> >> +            qemu_log_mask(LOG_GUEST_ERROR,
> >> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> >> +            exit(1);
> >> +        }
> >> +    } else { /* M/S/U */
> >> +        switch (clic->nmbits) {
> >> +        case 0:
> >> +            return mode == PRV_M;
> >> +        case 1:
> >> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
> >> +                                                          (mode == PRV_S);
> >> +        case 2:
> >> +            return mode == clic->clicintattr[irq_offset];
> >> +        case 3:
> >> +            qemu_log_mask(LOG_GUEST_ERROR,
> >> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> >> +            exit(1);
> >> +        }
> >> +    }
> >> +    return false;
> >> +}
> >> +
> >> +/*
> >> + * For level-triggered interrupts, software writes to pending bits are
> >> + * ignored completely. (Section 3.4)
> >> + */
> >> +static bool
> >> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int irq)
> >> +{
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> >> +}
> >> +
> >> +static void
> >> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> >> +                        int irq, uint64_t value)
> >> +{
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    clic->clicintip[irq_offset] = !!value;
> >> +    riscv_clic_next_interrupt(clic, hartid);
> >> +}
> >> +
> >> +/*
> >> + * For security purpose, the field can only be set to a privilege
> >> + * level that is equal mode to or lower than the currently running
> >> + * privilege level.(Section 3.6)
> >> + */
> >> +
> >> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t value)
> >> +{
> >> +    int mode = extract64(value, 6, 2);
> >> +
> >> +    if (!qtest_enabled()) {
> >> +        CPURISCVState *env = current_cpu->env_ptr;
> >> +        if (env->priv < mode) {
> >> +            return false;
> >> +        }
> >> +    }
> >> +    return true;
> >> +}
> >> +
> >> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> >> +{
> >> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> >> +           (i->irq & 0xfff);             /* Highest irq number */
> >> +}
> >> +
> >> +static int riscv_clic_active_compare(const void *a, const void *b)
> >> +{
> >> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> >> +}
> >> +
> >> +static void
> >> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> >> +                        int irq, uint64_t new_intie)
> >> +{
> >> +    size_t hart_offset = hartid * clic->num_sources;
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> >> +    size_t *active_count = &clic->active_count[hartid];
> >> +
> >> +    uint8_t old_intie = clic->clicintie[irq_offset];
> >> +    clic->clicintie[irq_offset] = !!new_intie;
> >> +
> >> +    /* Add to or remove from list of active interrupts */
> >> +    if (new_intie && !old_intie) {
> >> +        active_list[*active_count].intcfg = (mode << 8) |
> >> +                                            clic->clicintctl[irq_offset];
> >> +        active_list[*active_count].irq = irq;
> >> +        (*active_count)++;
> >> +    } else if (!new_intie && old_intie) {
> >> +        CLICActiveInterrupt key = {
> >> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> >> +        };
> >> +        CLICActiveInterrupt *result = bsearch(&key,
> >> +                                              active_list, *active_count,
> >> +                                              sizeof(CLICActiveInterrupt),
> >> +                                              riscv_clic_active_compare);
> >> +        size_t elem = (result - active_list) / sizeof(CLICActiveInterrupt);
> >> +        size_t sz = (--(*active_count) - elem) * sizeof(CLICActiveInterrupt);
> >> +        assert(result);
> >> +        memmove(&result[0], &result[1], sz);
> >> +    }
> >> +
> >> +    /* Sort list of active interrupts */
> >> +    qsort(active_list, *active_count,
> >> +          sizeof(CLICActiveInterrupt),
> >> +          riscv_clic_active_compare);
> >> +
> >> +    riscv_clic_next_interrupt(clic, hartid);
> >> +}
> >> +
> >> +static void
> >> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> >> +                      uint64_t value, unsigned size,
> >> +                      int mode, int hartid, int irq)
> >> +{
> >> +    int req = extract32(addr, 0, 2);
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +
> >> +    if (hartid >= clic->num_harts) {
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> >> +                      hartid, addr);
> >> +        return;
> >> +    }
> >> +
> >> +    if (irq >= clic->num_sources) {
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
> >> +        return;
> >> +    }
> >> +
> >> +    switch (req) {
> >> +    case 0: /* clicintip[i] */
> >> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> >> +            /*
> >> +             * The actual pending bit is located at bit 0 (i.e., the
> >> +             * leastsignificant bit). In case future extensions expand the bit
> >> +             * field, from FW perspective clicintip[i]=zero means no interrupt
> >> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> >> +             * interrupt is pending. (Section 3.4)
> >> +             */
> >> +            if (value != clic->clicintip[irq_offset]) {
> >> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> >> +            }
> >> +        }
> >> +        break;
> >> +    case 1: /* clicintie[i] */
> >> +        if (clic->clicintie[irq_offset] != value) {
> >> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> >> +        }
> >> +        break;
> >> +    case 2: /* clicintattr[i] */
> >> +        if (riscv_clic_validate_intattr(clic, value)) {
> >> +            if (clic->clicintattr[irq_offset] != value) {
> >> +                /* When nmbits=2, check WARL */
> >> +                bool invalid = (clic->nmbits == 2) &&
> >> +                               (extract64(value, 6, 2) == 0b10);
> >> +                if (invalid) {
> >> +                    uint8_t old_mode = extract32(clic->clicintattr[irq_offset],
> >> +                                                 6, 2);
> >> +                    value = deposit32(value, 6, 2, old_mode);
> >> +                }
> >> +                clic->clicintattr[irq_offset] = value;
> >> +                riscv_clic_next_interrupt(clic, hartid);
> >> +            }
> >> +        }
> >> +        break;
> >> +    case 3: /* clicintctl[i] */
> >> +        if (value != clic->clicintctl[irq_offset]) {
> >> +            clic->clicintctl[irq_offset] = value;
> >> +            riscv_clic_next_interrupt(clic, hartid);
> >> +        }
> >> +        break;
> >> +    }
> >> +}
> >> +
> >> +static uint64_t
> >> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> >> +                     int hartid, int irq)
> >> +{
> >> +    int req = extract32(addr, 0, 2);
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +
> >> +    if (hartid >= clic->num_harts) {
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> >> +                      hartid, addr);
> >> +        return 0;
> >> +    }
> >> +
> >> +    if (irq >= clic->num_sources) {
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
> >> +        return 0;
> >> +    }
> >> +
> >> +    switch (req) {
> >> +    case 0: /* clicintip[i] */
> >> +        return clic->clicintip[irq_offset];
> >> +    case 1: /* clicintie[i] */
> >> +        return clic->clicintie[irq_offset];
> >> +    case 2: /* clicintattr[i] */
> >> +        /*
> >> +         * clicintattr register layout
> >> +         * Bits Field
> >> +         * 7:6 mode
> >> +         * 5:3 reserved (WPRI 0)
> >> +         * 2:1 trig
> >> +         * 0 shv
> >> +         */
> >> +        return clic->clicintattr[irq_offset] & ~0x38;
> >> +    case 3: /* clicintctrl */
> >> +        /*
> >> +         * The implemented bits are kept left-justified in the most-significant
> >> +         * bits of each 8-bit clicintctl[i] register, with the lower
> >> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> >> +         */
> >> +        return clic->clicintctl[irq_offset] |
> >> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> >> +    }
> >> +
> >> +    return 0;
> >> +}
> >> +
> >> +/* Return target interrupt mode */
> >> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> >> +{
> >> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> >> +    switch (mode) {
> >> +    case 0:
> >> +        return PRV_M;
> >> +    case 1:
> >> +        assert(clic->prv_s || clic->prv_u);
> >> +        return clic->prv_s ? PRV_S : PRV_U;
> >> +    case 2:
> >> +        assert(clic->prv_s && clic->prv_u);
> >> +        return PRV_U;
> >> +    default:
> >> +        g_assert_not_reached();
> >> +        break;
> >> +    }
> >> +}
> >> +
> >> +/* Return target hart id */
> >> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> >> +{
> >> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> >> +    int hart_unit = 4 * clic->num_sources;
> >> +
> >> +    return (addr % mode_unit) / hart_unit;
> >> +}
> >> +
> >> +/* Return target interrupt number */
> >> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> >> +{
> >> +    int hart_unit = 4 * clic->num_sources;
> >> +    return (addr % hart_unit) / 4;
> >> +}
> >> +
> >> +static void
> >> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    hwaddr clic_size = clic->clic_size;
> >> +    int hartid, mode, irq;
> >> +
> >> +    if (addr < clic_size) {
> >> +        if (addr < 0x1000) {
> >> +            assert(addr % 4 == 0);
> >> +            int index = addr / 4;
> >> +            switch (index) {
> >> +            case 0: /* cliccfg */
> >> +                {
> >> +                    uint8_t nlbits = extract32(value, 1, 4);
> >> +                    uint8_t nmbits = extract32(value, 5, 2);
> >> +
> >> +                    /*
> >> +                     * The 4-bit cliccfg.nlbits WARL field.
> >> +                     * Valid values are 0—8.
> >> +                     */
> >> +                    if (nlbits <= 8) {
> >> +                        clic->nlbits = nlbits;
> >> +                    }
> >> +                    /* Valid values are given by implemented priviledges */
> >> +                    if (clic->prv_s && clic->prv_u) {
> >> +                        if (nmbits <= 2) {
> >> +                            clic->nmbits = nmbits;
> >> +                        }
> >> +                    } else if (clic->prv_u) {
> >> +                        if (nmbits <= 1) {
> >> +                            clic->nmbits = nmbits;
> >> +                        }
> >> +                    } else {
> >> +                        assert(!clic->prv_s);
> >> +                        if (nmbits == 0) {
> >> +                            clic->nmbits = 0;
> >> +                        }
> >> +                    }
> >> +                    clic->nvbits = extract32(value, 0, 1);
> >> +                    break;
> >> +                }
> >> +            case 1: /* clicinfo, read-only register */
> >> +                qemu_log_mask(LOG_GUEST_ERROR,
> >> +                              "clic: write read-only clicinfo.\n");
> >> +                break;
> >> +            case 0x10 ... 0x2F: /* clicinttrig */
> >> +                {
> >> +                    uint32_t interrupt_number = value & MAKE_64BIT_MASK(0, 13);
> >> +                    if (interrupt_number <= clic->num_sources) {
> >> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> >> +                        clic->clicinttrig[index - 0x10] = value;
> >> +                    }
> >> +                    break;
> >> +                }
> >> +            case 2: /* mintthresh */
> >> +                if (!strcmp(clic->version, "v0.8")) {
> >> +                    clic->mintthresh = value;
> >> +                    break;
> >> +                }
> >> +                qemu_log_mask(LOG_GUEST_ERROR,
> >> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
> >> +                              addr);
> >> +                break;
> >> +            default:
> >> +                qemu_log_mask(LOG_GUEST_ERROR,
> >> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
> >> +                              addr);
> >> +                return;
> >> +            }
> >> +        } else {
> >> +            addr -= 0x1000;
> >> +            hartid = riscv_clic_get_hartid(clic, addr);
> >> +            mode = riscv_clic_get_mode(clic, addr);
> >> +            irq = riscv_clic_get_irq(clic, addr);
> >> +
> >> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> >> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> >> +                                      hartid, irq);
> >> +            }
> >> +        }
> >> +    } else {
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> >> +    }
> >> +}
> >> +
> >> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    hwaddr clic_size = clic->clic_size;
> >> +    int hartid, mode, irq;
> >> +
> >> +    if (addr < clic_size) {
> >> +        if (addr < 0x1000) {
> >> +            assert(addr % 4 == 0);
> >> +            int index = addr / 4;
> >> +            switch (index) {
> >> +            case 0: /* cliccfg */
> >> +                return clic->nvbits |
> >> +                       (clic->nlbits << 1) |
> >> +                       (clic->nmbits << 5);
> >> +            case 1: /* clicinfo */
> >> +                /*
> >> +                 * clicinfo register layout
> >> +                 *
> >> +                 * Bits Field
> >> +                 * 31 reserved (WARL 0)
> >> +                 * 30:25 num_trigger
> >> +                 * 24:21 CLICINTCTLBITS
> >> +                 * 20:13 version (for version control)
> >> +                 * 12:0 num_interrupt
> >> +                 */
> >> +                return clic->clicinfo & ~INT32_MAX;
> >> +            case 0x10 ... 0x2F: /* clicinttrig */
> >> +                /*
> >> +                 * clicinttrig register layout
> >> +                 *
> >> +                 * Bits Field
> >> +                 * 31 enable
> >> +                 * 30:13 reserved (WARL 0)
> >> +                 * 12:0 interrupt_number
> >> +                 */
> >> +                return clic->clicinttrig[index - 0x10] &
> >> +                       ~MAKE_64BIT_MASK(13, 18);
> >> +            case 2: /* mintthresh */
> >> +                if (!strcmp(clic->version, "v0.8")) {
> >> +                    return clic->mintthresh;
> >> +                    break;
> >> +                }
> >> +                qemu_log_mask(LOG_GUEST_ERROR,
> >> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> >> +                              addr);
> >> +                break;
> >> +            default:
> >> +                qemu_log_mask(LOG_GUEST_ERROR,
> >> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> >> +                              addr);
> >> +                break;
> >> +            }
> >> +        } else {
> >> +            addr -= 0x1000;
> >> +            hartid = riscv_clic_get_hartid(clic, addr);
> >> +            mode = riscv_clic_get_mode(clic, addr);
> >> +            irq = riscv_clic_get_irq(clic, addr);
> >> +
> >> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> >> +                return riscv_clic_hart_read(clic, addr, mode, hartid, irq);
> >> +            }
> >> +        }
> >> +    } else {
> >> +        qemu_log_mask(LOG_GUEST_ERROR,
> >> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> >> +    }
> >> +    return 0;
> >> +}
> >> +
> >> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    int irq, hartid, mode;
> >> +    hwaddr addr = 4 * id;
> >> +    TRIG_TYPE type;
> >> +
> >> +    hartid = riscv_clic_get_hartid(clic, addr);
> >> +    mode = riscv_clic_get_mode(clic, addr);
> >> +    irq = riscv_clic_get_irq(clic, addr);
> >> +    type = riscv_clic_get_trigger_type(clic, id);
> >> +
> >> +    /*
> >> +     * In general, the edge-triggered interrupt state should be kept in pending
> >> +     * bit, while the level-triggered interrupt should be kept in the level
> >> +     * state of the incoming wire.
> >> +     *
> >> +     * For CLIC, model the level-triggered interrupt by read-only pending bit.
> >> +     */
> >> +    if (level) {
> >> +        switch (type) {
> >> +        case POSITIVE_LEVEL:
> >> +        case POSITIVE_EDGE:
> >> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> >> +            break;
> >> +        case NEG_LEVEL:
> >> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> >> +            break;
> >> +        case NEG_EDGE:
> >> +            break;
> >> +        }
> >> +    } else {
> >> +        switch (type) {
> >> +        case POSITIVE_LEVEL:
> >> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> >> +            break;
> >> +        case POSITIVE_EDGE:
> >> +            break;
> >> +        case NEG_LEVEL:
> >> +        case NEG_EDGE:
> >> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> >> +            break;
> >> +        }
> >> +    }
> >> +}
> >> +
> >> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> >> +{
> >> +    CPURISCVState *env = (CPURISCVState *)opaque;
> >> +    RISCVCLICState *clic = env->clic;
> >> +    CPUState *cpu = env_cpu(env);
> >> +
> >> +    if (level) {
> >> +        env->exccode = clic->exccode[cpu->cpu_index];
> >> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> >> +    }
> >> +}
> >> +
> >> +static const MemoryRegionOps riscv_clic_ops = {
> >> +    .read = riscv_clic_read,
> >> +    .write = riscv_clic_write,
> >> +    .endianness = DEVICE_LITTLE_ENDIAN,
> >> +    .valid = {
> >> +        .min_access_size = 1,
> >> +        .max_access_size = 8
> >> +    }
> >> +};
> >> +
> >> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> >> +{
> >> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> >> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> >> +    int irqs, i;
> >> +
> >> +    if (clic->prv_s && clic->prv_u) {
> >> +        irqs = 3 * harts_x_sources;
> >> +    } else if (clic->prv_s || clic->prv_u) {
> >> +        irqs = 2 * harts_x_sources;
> >> +    } else {
> >> +        irqs = harts_x_sources;
> >> +    }
> >> +
> >> +    clic->clic_size = irqs * 4 + 0x1000;
> >> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> >> +                          TYPE_RISCV_CLIC, clic->clic_size);
> >> +
> >> +    clic->clicintip = g_new0(uint8_t, irqs);
> >> +    clic->clicintie = g_new0(uint8_t, irqs);
> >> +    clic->clicintattr = g_new0(uint8_t, irqs);
> >> +    clic->clicintctl = g_new0(uint8_t, irqs);
> >> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> >> +    clic->active_count = g_new0(size_t, clic->num_harts);
> >> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> >> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> >> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> >> +
> >> +    /* Allocate irq through gpio, so that we can use qtest */
> >> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> >> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> >> +
> >> +    for (i = 0; i < clic->num_harts; i++) {
> >> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> >> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> >> +                                         &cpu->env, 1);
> >> +        qdev_connect_gpio_out(dev, i, irq);
> >> +        cpu->env.clic = clic;
> >> +    }
> >> +}
> >> +
> >> +static Property riscv_clic_properties[] = {
> >> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> >> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> >> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> >> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> >> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits, 0),
> >> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> >> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> >> +    DEFINE_PROP_END_OF_LIST(),
> >> +};
> >> +
> >> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> >> +{
> >> +    DeviceClass *dc = DEVICE_CLASS(klass);
> >> +
> >> +    dc->realize = riscv_clic_realize;
> >> +    device_class_set_props(dc, riscv_clic_properties);
> >> +}
> >> +
> >> +static const TypeInfo riscv_clic_info = {
> >> +    .name          = TYPE_RISCV_CLIC,
> >> +    .parent        = TYPE_SYS_BUS_DEVICE,
> >> +    .instance_size = sizeof(RISCVCLICState),
> >> +    .class_init    = riscv_clic_class_init,
> >> +};
> >> +
> >> +static void riscv_clic_register_types(void)
> >> +{
> >> +    type_register_static(&riscv_clic_info);
> >> +}
> >> +
> >> +type_init(riscv_clic_register_types)
> >> +
> >> +/*
> >> + * riscv_clic_create:
> >> + *
> >> + * @addr: base address of M-Mode CLIC memory-mapped registers
> >> + * @prv_s: have smode region
> >> + * @prv_u: have umode region
> >> + * @num_harts: number of CPU harts
> >> + * @num_sources: number of interrupts supporting by each aperture
> >> + * @clicintctlbits: bits are actually implemented in the clicintctl registers
> >> + * @version: clic version, such as "v0.9"
> >> + *
> >> + * Returns: the device object
> >> + */
> >> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> >> +                               uint32_t num_harts, uint32_t num_sources,
> >> +                               uint8_t clicintctlbits,
> >> +                               const char *version)
> >> +{
> >> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> >> +
> >> +    assert(num_sources <= 4096);
> >> +    assert(num_harts <= 1024);
> >> +    assert(clicintctlbits <= 8);
> >> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> >> +
> >> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> >> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> >> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> >> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> >> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> >> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> >> +    qdev_prop_set_string(dev, "version", version);
> >> +
> >> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> >> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> >> +    return dev;
> >> +}
> >> +
> >> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    riscv_clic_next_interrupt(clic, hartid);
> >> +}
> >> +
> >> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> >> +}
> >> +
> >> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> >> +}
> >> +
> >> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> >> +{
> >> +    RISCVCLICState *clic = opaque;
> >> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
> >> +    clic->clicintip[irq_offset] = 0;
> >> +}
> >> +
> >> +/*
> >> + * The new CLIC interrupt-handling mode is encoded as a new state in
> >> + * the existing WARL xtvec register, where the low two bits of  are 11.
> >> + */
> >> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> >> +{
> >> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> >> +    return env->clic && ((xtvec & 0x3) == 3);
> >> +}
> >> +
> >> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> >> +                               int *il, int *irq)
> >> +{
> >> +    *irq = extract32(exccode, 0, 12);
> >> +    *mode = extract32(exccode, 12, 2);
> >> +    *il = extract32(exccode, 14, 8);
> >> +}
> >> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> >> new file mode 100644
> >> index 0000000000..e5f89672a6
> >> --- /dev/null
> >> +++ b/include/hw/intc/riscv_clic.h
> >> @@ -0,0 +1,103 @@
> >> +/*
> >> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> >> + *
> >> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> >> + *
> >> + * This program is free software; you can redistribute it and/or modify it
> >> + * under the terms and conditions of the GNU General Public License,
> >> + * version 2 or later, as published by the Free Software Foundation.
> >> + *
> >> + * This program is distributed in the hope it will be useful, but WITHOUT
> >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> >> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> >> + * more details.
> >> + *
> >> + * You should have received a copy of the GNU General Public License along with
> >> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> >> + */
> >> +
> >> +#ifndef RISCV_CLIC_H
> >> +#define RISCV_CLIC_H
> >> +
> >> +#include "hw/irq.h"
> >> +#include "hw/sysbus.h"
> >> +
> >> +#define TYPE_RISCV_CLIC "riscv_clic"
> >> +#define RISCV_CLIC(obj) \
> >> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> >> +
> >> +/*
> >> + * CLIC per hart active interrupts
> >> + *
> >> + * We maintain per hart lists of enabled interrupts sorted by
> >> + * mode+level+priority. The sorting is done on the configuration path
> >> + * so that the interrupt delivery fastpath can linear scan enabled
> >> + * interrupts in priority order.
> >> + */
> >> +typedef struct CLICActiveInterrupt {
> >> +    uint16_t intcfg;
> >> +    uint16_t irq;
> >> +} CLICActiveInterrupt;
> >> +
> >> +typedef enum TRIG_TYPE {
> >> +    POSITIVE_LEVEL,
> >> +    POSITIVE_EDGE,
> >> +    NEG_LEVEL,
> >> +    NEG_EDGE,
> >> +} TRIG_TYPE;
> >> +
> >> +typedef struct RISCVCLICState {
> >> +    /*< private >*/
> >> +    SysBusDevice parent_obj;
> >> +
> >> +    /*< public >*/
> >> +
> >> +    /* Implementaion parameters */
> >> +    bool prv_s;
> >> +    bool prv_u;
> >> +    uint32_t num_harts;
> >> +    uint32_t num_sources;
> >> +    uint32_t clic_size;
> >> +    uint32_t clic_mmode_base;
> >> +    uint32_t clicintctlbits;
> >> +    uint64_t mclicbase;
> >> +    char *version;
> >> +
> >> +    /* Global configuration */
> >> +    uint8_t nmbits;
> >> +    uint8_t nlbits;
> >> +    uint8_t nvbits;
> >> +    uint32_t clicinfo;
> >> +    uint32_t clicinttrig[32];
> >> +
> >> +    /* Aperture configuration */
> >> +    uint8_t *clicintip;
> >> +    uint8_t *clicintie;
> >> +    uint8_t *clicintattr;
> >> +    uint8_t *clicintctl;
> >> +
> >> +    /* Complatible with v0.8 */
> >> +    uint32_t mintthresh;
> >> +    uint32_t sintthresh;
> >> +    uint32_t uintthresh;
> >> +
> >> +    /* QEMU implementaion related fields */
> >> +    uint32_t *exccode;
> >> +    CLICActiveInterrupt *active_list;
> >> +    size_t *active_count;
> >> +    MemoryRegion mmio;
> >> +    qemu_irq *cpu_irqs;
> >> +} RISCVCLICState;
> >> +
> >> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> >> +                               uint32_t num_harts, uint32_t num_sources,
> >> +                               uint8_t clicintctlbits,
> >> +                               const char *version);
> >> +
> >> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int *irq);
> >> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq);
> >> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq);
> >> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq);
> >> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> >> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> >> +#endif
> >> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> >> index a5eab26a69..9e389d7bbf 100644
> >> --- a/target/riscv/cpu.h
> >> +++ b/target/riscv/cpu.h
> >> @@ -33,6 +33,7 @@
> >>   #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
> >>   #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
> >>   #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> >> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
> >>
> >>   #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
> >>   #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> >> @@ -247,6 +248,7 @@ struct CPURISCVState {
> >>       /* Fields from here on are preserved across CPU reset. */
> >>       QEMUTimer *timer; /* Internal timer */
> >>       void *clic;       /* clic interrupt controller */
> >> +    uint32_t exccode; /* clic irq encode */
> >>   };
> >>
> >>   OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> >> --
> >> 2.25.1
> >>
> >>
Frank Chang June 13, 2021, 10:10 a.m. UTC | #4
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>

As spec says:
  Smaller single-core systems might have only a CLIC,
  while multicore systems might have a CLIC per-core and a single shared
PLIC.
  The PLIC xeip signals are treated as hart-local interrupt sources by the
CLIC at each core.

It looks like it's possible to have one CLIC instance per core.
However if you try to bind CPU reference start from index i = 0.
It's not possible for each per-core CLIC to bind their own CPU instance in
multicore system
as they have to bind from CPU 0.

I'm not sure if we add a new "hartid-base" property just like what SiFive
PLIC is
implemented would be a good idea or not.


Regards,
Frank Chang


> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
LIU Zhiwei June 16, 2021, 2:56 a.m. UTC | #5
On 6/13/21 6:10 PM, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年4月9日 週五 下午3:57寫道:
>
>     The Core-Local Interrupt Controller (CLIC) provides low-latency,
>     vectored, pre-emptive interrupts for RISC-V systems.
>
>     The CLIC also supports a new Selective Hardware Vectoring feature
>     that allow users to optimize each interrupt for either faster
>     response or smaller code size.
>
>     Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com
>     <mailto:zhiwei_liu@c-sky.com>>
>     ---
>      default-configs/devices/riscv32-softmmu.mak |   1 +
>      default-configs/devices/riscv64-softmmu.mak |   1 +
>      hw/intc/Kconfig                             |   3 +
>      hw/intc/meson.build                         |   1 +
>      hw/intc/riscv_clic.c                        | 835
>     ++++++++++++++++++++
>      include/hw/intc/riscv_clic.h                | 103 +++
>      target/riscv/cpu.h                          |   2 +
>      7 files changed, 946 insertions(+)
>      create mode 100644 hw/intc/riscv_clic.c
>      create mode 100644 include/hw/intc/riscv_clic.h
>
>     diff --git a/default-configs/devices/riscv32-softmmu.mak
>     b/default-configs/devices/riscv32-softmmu.mak
>     index d847bd5692..1430c30588 100644
>     --- a/default-configs/devices/riscv32-softmmu.mak
>     +++ b/default-configs/devices/riscv32-softmmu.mak
>     @@ -5,6 +5,7 @@
>      #CONFIG_PCI_DEVICES=n
>      CONFIG_SEMIHOSTING=y
>      CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>     +CONFIG_RISCV_CLIC=y
>
>      # Boards:
>      #
>     diff --git a/default-configs/devices/riscv64-softmmu.mak
>     b/default-configs/devices/riscv64-softmmu.mak
>     index d5eec75f05..396800bbbd 100644
>     --- a/default-configs/devices/riscv64-softmmu.mak
>     +++ b/default-configs/devices/riscv64-softmmu.mak
>     @@ -5,6 +5,7 @@
>      #CONFIG_PCI_DEVICES=n
>      CONFIG_SEMIHOSTING=y
>      CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>     +CONFIG_RISCV_CLIC=y
>
>      # Boards:
>      #
>     diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>     index f4694088a4..5bf492b48f 100644
>     --- a/hw/intc/Kconfig
>     +++ b/hw/intc/Kconfig
>     @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>      config SIFIVE_PLIC
>          bool
>
>     +config RISCV_CLIC
>     +    bool
>     +
>      config GOLDFISH_PIC
>          bool
>
>     diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>     index 1c299039f6..2aa71b6738 100644
>     --- a/hw/intc/meson.build
>     +++ b/hw/intc/meson.build
>     @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM',
>     if_true: files('s390_flic_kvm.c'))
>      specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>      specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>     files('sifive_clint.c'))
>      specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>     files('sifive_plic.c'))
>     +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>     files('riscv_clic.c'))
>      specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>      specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                     if_true: files('xics_kvm.c'))
>     diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>     new file mode 100644
>     index 0000000000..8ad534c506
>     --- /dev/null
>     +++ b/hw/intc/riscv_clic.c
>     @@ -0,0 +1,835 @@
>     +/*
>     + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>     + *
>     + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights
>     reserved.
>     + *
>     + * This program is free software; you can redistribute it and/or
>     modify it
>     + * under the terms and conditions of the GNU General Public License,
>     + * version 2 or later, as published by the Free Software Foundation.
>     + *
>     + * This program is distributed in the hope it will be useful, but
>     WITHOUT
>     + * ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or
>     + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
>     License for
>     + * more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along with
>     + * this program.  If not, see <http://www.gnu.org/licenses/
>     <http://www.gnu.org/licenses/>>.
>     + */
>     +
>     +#include "qemu/osdep.h"
>     +#include "qapi/error.h"
>     +#include "qemu/log.h"
>     +#include "hw/sysbus.h"
>     +#include "sysemu/qtest.h"
>     +#include "target/riscv/cpu.h"
>     +#include "hw/qdev-properties.h"
>     +#include "hw/intc/riscv_clic.h"
>     +
>     +/*
>     + * The 2-bit trig WARL field specifies the trigger type and
>     polarity for each
>     + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>     + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1],
>     is defined as
>     + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section
>     3.6)
>     + */
>     +
>     +static inline TRIG_TYPE
>     +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>     +}
>     +
>     +static inline bool
>     +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>     +}
>     +
>     +static inline bool
>     +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>     +}
>     +
>     +static uint8_t
>     +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>     +{
>     +    int nlbits = clic->nlbits;
>     +
>     +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>     +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>     +    /* unused level bits are set to 1 */
>     +    return (intctl & mask_il) | mask_padding;
>     +}
>     +
>     +static uint8_t
>     +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t
>     intctl)
>     +{
>     +    int npbits = clic->clicintctlbits - clic->nlbits;
>     +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>     +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>     +
>     +    if (npbits < 0) {
>     +        return UINT8_MAX;
>     +    }
>     +    /* unused priority bits are set to 1 */
>     +    return (intctl & mask_priority) | mask_padding;
>     +}
>     +
>     +static void
>     +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>     +                         uint8_t *mode, uint8_t *level,
>     +                         uint8_t *priority)
>     +{
>     +    *mode = intcfg >> 8;
>     +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>     +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg &
>     0xff);
>     +}
>     +
>     +/*
>     + * In a system with multiple harts, the M-mode CLIC regions for
>     all the harts
>     + * are placed contiguously in the memory space, followed by the
>     S-mode CLIC
>     + * regions for all harts. (Section 3.11)
>     + */
>     +static size_t
>     +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t mode_offset = 0;
>     +    size_t unit = clic->num_harts * clic->num_sources;
>     +
>     +    switch (mode) {
>     +    case PRV_M:
>     +        mode_offset = 0;
>     +        break;
>     +    case PRV_S:
>     +        mode_offset = unit;
>     +        break;
>     +    case PRV_U:
>     +        mode_offset = clic->prv_s ? 2 * unit : unit;
>     +        break;
>     +    default:
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid mode %d\n", mode);
>     +        exit(1);
>     +    }
>     +    return mode_offset + hartid * clic->num_sources + irq;
>     +}
>     +
>     +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>     +{
>     +    /*
>     +     * Scan active list for highest priority pending interrupts
>     +     * comparing against this harts mintstatus register and interrupt
>     +     * the core if we have a higher priority interrupt to deliver
>     +     */
>     +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>     +    CPURISCVState *env = &cpu->env;
>     +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>     +
>     +    int il[4] = {
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>     +            clic->mintthresh), /* PRV_U */
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>     +            clic->sintthresh), /* PRV_S */
>     +        0,                     /* reserverd */
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>     +            clic->uintthresh)  /* PRV_M */
>     +    };
>     +
>     +    /* Get sorted list of enabled interrupts for this hart */
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>     +    size_t active_count = clic->active_count[hartid];
>     +    uint8_t mode, level, priority;
>     +
>     +    /* Loop through the enabled interrupts sorted by
>     mode+priority+level */
>     +    while (active_count) {
>     +        size_t irq_offset;
>     +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>     +                                 &priority);
>     +        if (mode < env->priv || (mode == env->priv && level <=
>     il[mode])) {
>     +            /*
>     +             * No pending interrupts with high enough
>     mode+priority+level
>     +             * break and clear pending interrupt for this hart
>     +             */
>     +            break;
>     +        }
>     +        irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, active->irq);
>     +        /* Check pending interrupt with high enough
>     mode+priority+level */
>     +        if (clic->clicintip[irq_offset]) {
>     +            /* Clean vector edge-triggered pending */
>     +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>     +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>     +                clic->clicintip[irq_offset] = 0;
>     +            }
>     +            /* Post pending interrupt for this hart */
>     +            clic->exccode[hartid] = active->irq | mode << 12 |
>     level << 14;
>     +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>     +            return;
>     +        }
>     +        /* Check next enabled interrupt */
>     +        active_count--;
>     +        active++;
>     +    }
>     +}
>     +
>     +/*
>     + * Any interrupt i that is not accessible to S-mode or U-Mode
>     + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>     + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>     + */
>     +static bool
>     +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    if (!clic->prv_s && !clic->prv_u) { /* M */
>     +        return mode == PRV_M;
>     +    } else if (!clic->prv_s) { /* M/U */
>     +        switch (clic->nmbits) {
>     +        case 0:
>     +            return mode == PRV_M;
>     +        case 1:
>     +            return clic->clicintattr[irq_offset] & 0x80 ? (mode
>     == PRV_M) :
>     + (mode == PRV_U);
>     +        default:
>     +            qemu_log_mask(LOG_GUEST_ERROR,
>     +                "clic: nmbits can only be 0 or 1 for M/U hart");
>     +            exit(1);
>     +        }
>     +    } else { /* M/S/U */
>     +        switch (clic->nmbits) {
>     +        case 0:
>     +            return mode == PRV_M;
>     +        case 1:
>     +            return clic->clicintattr[irq_offset] & 0x80 ? (mode
>     == PRV_M) :
>     + (mode == PRV_S);
>     +        case 2:
>     +            return mode == clic->clicintattr[irq_offset];
>     +        case 3:
>     +            qemu_log_mask(LOG_GUEST_ERROR,
>     +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U
>     hart");
>     +            exit(1);
>     +        }
>     +    }
>     +    return false;
>     +}
>     +
>     +/*
>     + * For level-triggered interrupts, software writes to pending
>     bits are
>     + * ignored completely. (Section 3.4)
>     + */
>     +static bool
>     +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>     +}
>     +
>     +static void
>     +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t value)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    clic->clicintip[irq_offset] = !!value;
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +/*
>     + * For security purpose, the field can only be set to a privilege
>     + * level that is equal mode to or lower than the currently running
>     + * privilege level.(Section 3.6)
>     + */
>     +
>     +static bool riscv_clic_validate_intattr(RISCVCLICState *clic,
>     uint64_t value)
>     +{
>     +    int mode = extract64(value, 6, 2);
>     +
>     +    if (!qtest_enabled()) {
>     +        CPURISCVState *env = current_cpu->env_ptr;
>     +        if (env->priv < mode) {
>     +            return false;
>     +        }
>     +    }
>     +    return true;
>     +}
>     +
>     +static inline int riscv_clic_encode_priority(const
>     CLICActiveInterrupt *i)
>     +{
>     +    return ((i->intcfg & 0x3ff) << 12) | /* Highest
>     mode+level+priority */
>     +           (i->irq & 0xfff);             /* Highest irq number */
>     +}
>     +
>     +static int riscv_clic_active_compare(const void *a, const void *b)
>     +{
>     +    return riscv_clic_encode_priority(b) -
>     riscv_clic_encode_priority(a);
>     +}
>     +
>     +static void
>     +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t new_intie)
>     +{
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    CLICActiveInterrupt *active_list =
>     &clic->active_list[hart_offset];
>     +    size_t *active_count = &clic->active_count[hartid];
>     +
>     +    uint8_t old_intie = clic->clicintie[irq_offset];
>     +    clic->clicintie[irq_offset] = !!new_intie;
>     +
>     +    /* Add to or remove from list of active interrupts */
>     +    if (new_intie && !old_intie) {
>     +        active_list[*active_count].intcfg = (mode << 8) |
>     + clic->clicintctl[irq_offset];
>     +        active_list[*active_count].irq = irq;
>     +        (*active_count)++;
>     +    } else if (!new_intie && old_intie) {
>     +        CLICActiveInterrupt key = {
>     +            (mode << 8) | clic->clicintctl[irq_offset], irq
>     +        };
>     +        CLICActiveInterrupt *result = bsearch(&key,
>     +                                              active_list,
>     *active_count,
>     + sizeof(CLICActiveInterrupt),
>     + riscv_clic_active_compare);
>     +        size_t elem = (result - active_list) /
>     sizeof(CLICActiveInterrupt);
>     +        size_t sz = (--(*active_count) - elem) *
>     sizeof(CLICActiveInterrupt);
>     +        assert(result);
>     +        memmove(&result[0], &result[1], sz);
>     +    }
>     +
>     +    /* Sort list of active interrupts */
>     +    qsort(active_list, *active_count,
>     +          sizeof(CLICActiveInterrupt),
>     +          riscv_clic_active_compare);
>     +
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +static void
>     +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>     +                      uint64_t value, unsigned size,
>     +                      int mode, int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>     +            /*
>     +             * The actual pending bit is located at bit 0 (i.e., the
>     +             * leastsignificant bit). In case future extensions
>     expand the bit
>     +             * field, from FW perspective clicintip[i]=zero means
>     no interrupt
>     +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>     +             * interrupt is pending. (Section 3.4)
>     +             */
>     +            if (value != clic->clicintip[irq_offset]) {
>     +                riscv_clic_update_intip(clic, mode, hartid, irq,
>     value);
>     +            }
>     +        }
>     +        break;
>     +    case 1: /* clicintie[i] */
>     +        if (clic->clicintie[irq_offset] != value) {
>     +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>     +        }
>     +        break;
>     +    case 2: /* clicintattr[i] */
>     +        if (riscv_clic_validate_intattr(clic, value)) {
>     +            if (clic->clicintattr[irq_offset] != value) {
>     +                /* When nmbits=2, check WARL */
>     +                bool invalid = (clic->nmbits == 2) &&
>     +                               (extract64(value, 6, 2) == 0b10);
>     +                if (invalid) {
>     +                    uint8_t old_mode =
>     extract32(clic->clicintattr[irq_offset],
>     +                                                 6, 2);
>     +                    value = deposit32(value, 6, 2, old_mode);
>     +                }
>     +                clic->clicintattr[irq_offset] = value;
>     +                riscv_clic_next_interrupt(clic, hartid);
>     +            }
>     +        }
>     +        break;
>     +    case 3: /* clicintctl[i] */
>     +        if (value != clic->clicintctl[irq_offset]) {
>     +            clic->clicintctl[irq_offset] = value;
>     +            riscv_clic_next_interrupt(clic, hartid);
>     +        }
>     +        break;
>     +    }
>     +}
>     +
>     +static uint64_t
>     +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>     +                     int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return 0;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return 0;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        return clic->clicintip[irq_offset];
>     +    case 1: /* clicintie[i] */
>     +        return clic->clicintie[irq_offset];
>     +    case 2: /* clicintattr[i] */
>     +        /*
>     +         * clicintattr register layout
>     +         * Bits Field
>     +         * 7:6 mode
>     +         * 5:3 reserved (WPRI 0)
>     +         * 2:1 trig
>     +         * 0 shv
>     +         */
>     +        return clic->clicintattr[irq_offset] & ~0x38;
>     +    case 3: /* clicintctrl */
>     +        /*
>     +         * The implemented bits are kept left-justified in the
>     most-significant
>     +         * bits of each 8-bit clicintctl[i] register, with the lower
>     +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>     +         */
>     +        return clic->clicintctl[irq_offset] |
>     +               ((1 << (8 - clic->clicintctlbits)) - 1);
>     +    }
>     +
>     +    return 0;
>     +}
>     +
>     +/* Return target interrupt mode */
>     +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>     +    switch (mode) {
>     +    case 0:
>     +        return PRV_M;
>     +    case 1:
>     +        assert(clic->prv_s || clic->prv_u);
>     +        return clic->prv_s ? PRV_S : PRV_U;
>     +    case 2:
>     +        assert(clic->prv_s && clic->prv_u);
>     +        return PRV_U;
>     +    default:
>     +        g_assert_not_reached();
>     +        break;
>     +    }
>     +}
>     +
>     +/* Return target hart id */
>     +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>     +    int hart_unit = 4 * clic->num_sources;
>     +
>     +    return (addr % mode_unit) / hart_unit;
>     +}
>     +
>     +/* Return target interrupt number */
>     +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int hart_unit = 4 * clic->num_sources;
>     +    return (addr % hart_unit) / 4;
>     +}
>     +
>     +static void
>     +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                {
>     +                    uint8_t nlbits = extract32(value, 1, 4);
>     +                    uint8_t nmbits = extract32(value, 5, 2);
>     +
>     +                    /*
>     +                     * The 4-bit cliccfg.nlbits WARL field.
>     +                     * Valid values are 0—8.
>     +                     */
>     +                    if (nlbits <= 8) {
>     +                        clic->nlbits = nlbits;
>     +                    }
>     +                    /* Valid values are given by implemented
>     priviledges */
>     +                    if (clic->prv_s && clic->prv_u) {
>     +                        if (nmbits <= 2) {
>     +                            clic->nmbits = nmbits;
>     +                        }
>     +                    } else if (clic->prv_u) {
>     +                        if (nmbits <= 1) {
>     +                            clic->nmbits = nmbits;
>     +                        }
>     +                    } else {
>     +                        assert(!clic->prv_s);
>     +                        if (nmbits == 0) {
>     +                            clic->nmbits = 0;
>     +                        }
>     +                    }
>     +                    clic->nvbits = extract32(value, 0, 1);
>     +                    break;
>     +                }
>     +            case 1: /* clicinfo, read-only register */
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: write read-only clicinfo.\n");
>     +                break;
>     +            case 0x10 ... 0x2F: /* clicinttrig */
>     +                {
>     +                    uint32_t interrupt_number = value &
>     MAKE_64BIT_MASK(0, 13);
>     +                    if (interrupt_number <= clic->num_sources) {
>     +                        value &= ~MAKE_64BIT_MASK(13, 18);
>     +                        clic->clicinttrig[index - 0x10] = value;
>     +                    }
>     +                    break;
>     +                }
>     +            case 2: /* mintthresh */
>     +                if (!strcmp(clic->version, "v0.8")) {
>     +                    clic->mintthresh = value;
>     +                    break;
>     +                }
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid write addr: 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            default:
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid write addr: 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                return;
>     +            }
>     +        } else {
>     +            addr -= 0x1000;
>     +            hartid = riscv_clic_get_hartid(clic, addr);
>     +            mode = riscv_clic_get_mode(clic, addr);
>     +            irq = riscv_clic_get_irq(clic, addr);
>     +
>     +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>     +                riscv_clic_hart_write(clic, addr, value, size, mode,
>     +                                      hartid, irq);
>     +            }
>     +        }
>     +    } else {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid write: 0x%" HWADDR_PRIx
>     "\n", addr);
>     +    }
>     +}
>     +
>     +static uint64_t riscv_clic_read(void *opaque, hwaddr addr,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                return clic->nvbits |
>     +                       (clic->nlbits << 1) |
>     +                       (clic->nmbits << 5);
>     +            case 1: /* clicinfo */
>     +                /*
>     +                 * clicinfo register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 reserved (WARL 0)
>     +                 * 30:25 num_trigger
>     +                 * 24:21 CLICINTCTLBITS
>     +                 * 20:13 version (for version control)
>     +                 * 12:0 num_interrupt
>     +                 */
>     +                return clic->clicinfo & ~INT32_MAX;
>     +            case 0x10 ... 0x2F: /* clicinttrig */
>     +                /*
>     +                 * clicinttrig register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 enable
>     +                 * 30:13 reserved (WARL 0)
>     +                 * 12:0 interrupt_number
>     +                 */
>     +                return clic->clicinttrig[index - 0x10] &
>     +                       ~MAKE_64BIT_MASK(13, 18);
>     +            case 2: /* mintthresh */
>     +                if (!strcmp(clic->version, "v0.8")) {
>     +                    return clic->mintthresh;
>     +                    break;
>     +                }
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid read : 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            default:
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid read : 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            }
>     +        } else {
>     +            addr -= 0x1000;
>     +            hartid = riscv_clic_get_hartid(clic, addr);
>     +            mode = riscv_clic_get_mode(clic, addr);
>     +            irq = riscv_clic_get_irq(clic, addr);
>     +
>     +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>     +                return riscv_clic_hart_read(clic, addr, mode,
>     hartid, irq);
>     +            }
>     +        }
>     +    } else {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n",
>     addr);
>     +    }
>     +    return 0;
>     +}
>     +
>     +static void riscv_clic_set_irq(void *opaque, int id, int level)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    int irq, hartid, mode;
>     +    hwaddr addr = 4 * id;
>     +    TRIG_TYPE type;
>     +
>     +    hartid = riscv_clic_get_hartid(clic, addr);
>     +    mode = riscv_clic_get_mode(clic, addr);
>     +    irq = riscv_clic_get_irq(clic, addr);
>     +    type = riscv_clic_get_trigger_type(clic, id);
>     +
>     +    /*
>     +     * In general, the edge-triggered interrupt state should be
>     kept in pending
>     +     * bit, while the level-triggered interrupt should be kept in
>     the level
>     +     * state of the incoming wire.
>     +     *
>     +     * For CLIC, model the level-triggered interrupt by read-only
>     pending bit.
>     +     */
>     +    if (level) {
>     +        switch (type) {
>     +        case POSITIVE_LEVEL:
>     +        case POSITIVE_EDGE:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>     +            break;
>     +        case NEG_LEVEL:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>     +            break;
>     +        case NEG_EDGE:
>     +            break;
>     +        }
>     +    } else {
>     +        switch (type) {
>     +        case POSITIVE_LEVEL:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>     +            break;
>     +        case POSITIVE_EDGE:
>     +            break;
>     +        case NEG_LEVEL:
>     +        case NEG_EDGE:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>     +            break;
>     +        }
>     +    }
>     +}
>     +
>     +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int
>     level)
>     +{
>     +    CPURISCVState *env = (CPURISCVState *)opaque;
>     +    RISCVCLICState *clic = env->clic;
>     +    CPUState *cpu = env_cpu(env);
>     +
>     +    if (level) {
>     +        env->exccode = clic->exccode[cpu->cpu_index];
>     +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>     +    }
>     +}
>     +
>     +static const MemoryRegionOps riscv_clic_ops = {
>     +    .read = riscv_clic_read,
>     +    .write = riscv_clic_write,
>     +    .endianness = DEVICE_LITTLE_ENDIAN,
>     +    .valid = {
>     +        .min_access_size = 1,
>     +        .max_access_size = 8
>     +    }
>     +};
>     +
>     +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>     +{
>     +    RISCVCLICState *clic = RISCV_CLIC(dev);
>     +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>     +    int irqs, i;
>     +
>     +    if (clic->prv_s && clic->prv_u) {
>     +        irqs = 3 * harts_x_sources;
>     +    } else if (clic->prv_s || clic->prv_u) {
>     +        irqs = 2 * harts_x_sources;
>     +    } else {
>     +        irqs = harts_x_sources;
>     +    }
>     +
>     +    clic->clic_size = irqs * 4 + 0x1000;
>     +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>     &riscv_clic_ops, clic,
>     +                          TYPE_RISCV_CLIC, clic->clic_size);
>     +
>     +    clic->clicintip = g_new0(uint8_t, irqs);
>     +    clic->clicintie = g_new0(uint8_t, irqs);
>     +    clic->clicintattr = g_new0(uint8_t, irqs);
>     +    clic->clicintctl = g_new0(uint8_t, irqs);
>     +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>     +    clic->active_count = g_new0(size_t, clic->num_harts);
>     +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>     +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>     +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>     +
>     +    /* Allocate irq through gpio, so that we can use qtest */
>     +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>     +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>     +
>     +    for (i = 0; i < clic->num_harts; i++) {
>     +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>
>
> As spec says:
>   Smaller single-core systems might have only a CLIC,
>   while multicore systems might have a CLIC per-core and a single 
> shared PLIC.
>   The PLIC xeip signals are treated as hart-local interrupt sources by 
> the CLIC at each core.
>
> It looks like it's possible to have one CLIC instance per core.

If you want to delivery an interrupt to one hart, you should encode the 
IRQ by the interrupt number
, the hart number and the interrupt target privilege, then set the irq.

I think how to calculate the hart number is the task of PLIC and it can 
make use of "hartid-base"
to calculate it.

Thanks,
Zhiwei

> However if you try to bind CPU reference start from index i = 0.
> It's not possible for each per-core CLIC to bind their own CPU 
> instance in multicore system
> as they have to bind from CPU 0.
>
> I'm not sure if we add a new "hartid-base" property just like what 
> SiFive PLIC is
> implemented would be a good idea or not.
>
>
> Regards,
> Frank Chang
>
>     +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>     +                                         &cpu->env, 1);
>     +        qdev_connect_gpio_out(dev, i, irq);
>     +        cpu->env.clic = clic;
>     +    }
>     +}
>     +
>     +static Property riscv_clic_properties[] = {
>     +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>     +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>     +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>     +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState,
>     num_sources, 0),
>     +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState,
>     clicintctlbits, 0),
>     +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>     +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>     +    DEFINE_PROP_END_OF_LIST(),
>     +};
>     +
>     +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>     +{
>     +    DeviceClass *dc = DEVICE_CLASS(klass);
>     +
>     +    dc->realize = riscv_clic_realize;
>     +    device_class_set_props(dc, riscv_clic_properties);
>     +}
>     +
>     +static const TypeInfo riscv_clic_info = {
>     +    .name          = TYPE_RISCV_CLIC,
>     +    .parent        = TYPE_SYS_BUS_DEVICE,
>     +    .instance_size = sizeof(RISCVCLICState),
>     +    .class_init    = riscv_clic_class_init,
>     +};
>     +
>     +static void riscv_clic_register_types(void)
>     +{
>     +    type_register_static(&riscv_clic_info);
>     +}
>     +
>     +type_init(riscv_clic_register_types)
>     +
>     +/*
>     + * riscv_clic_create:
>     + *
>     + * @addr: base address of M-Mode CLIC memory-mapped registers
>     + * @prv_s: have smode region
>     + * @prv_u: have umode region
>     + * @num_harts: number of CPU harts
>     + * @num_sources: number of interrupts supporting by each aperture
>     + * @clicintctlbits: bits are actually implemented in the
>     clicintctl registers
>     + * @version: clic version, such as "v0.9"
>     + *
>     + * Returns: the device object
>     + */
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version)
>     +{
>     +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>     +
>     +    assert(num_sources <= 4096);
>     +    assert(num_harts <= 1024);
>     +    assert(clicintctlbits <= 8);
>     +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>     +
>     +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>     +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>     +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>     +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>     +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>     +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>     +    qdev_prop_set_string(dev, "version", version);
>     +
>     +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>     +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>     +    return dev;
>     +}
>     +
>     +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid,
>     int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>     +}
>     +
>     +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>     hartid, int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>     +}
>     +
>     +void riscv_clic_clean_pending(void *opaque, int mode, int hartid,
>     int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    clic->clicintip[irq_offset] = 0;
>     +}
>     +
>     +/*
>     + * The new CLIC interrupt-handling mode is encoded as a new state in
>     + * the existing WARL xtvec register, where the low two bits of 
>     are 11.
>     + */
>     +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>     +{
>     +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec :
>     env->stvec;
>     +    return env->clic && ((xtvec & 0x3) == 3);
>     +}
>     +
>     +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>     +                               int *il, int *irq)
>     +{
>     +    *irq = extract32(exccode, 0, 12);
>     +    *mode = extract32(exccode, 12, 2);
>     +    *il = extract32(exccode, 14, 8);
>     +}
>     diff --git a/include/hw/intc/riscv_clic.h
>     b/include/hw/intc/riscv_clic.h
>     new file mode 100644
>     index 0000000000..e5f89672a6
>     --- /dev/null
>     +++ b/include/hw/intc/riscv_clic.h
>     @@ -0,0 +1,103 @@
>     +/*
>     + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>     + *
>     + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights
>     reserved.
>     + *
>     + * This program is free software; you can redistribute it and/or
>     modify it
>     + * under the terms and conditions of the GNU General Public License,
>     + * version 2 or later, as published by the Free Software Foundation.
>     + *
>     + * This program is distributed in the hope it will be useful, but
>     WITHOUT
>     + * ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or
>     + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
>     License for
>     + * more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along with
>     + * this program.  If not, see <http://www.gnu.org/licenses/
>     <http://www.gnu.org/licenses/>>.
>     + */
>     +
>     +#ifndef RISCV_CLIC_H
>     +#define RISCV_CLIC_H
>     +
>     +#include "hw/irq.h"
>     +#include "hw/sysbus.h"
>     +
>     +#define TYPE_RISCV_CLIC "riscv_clic"
>     +#define RISCV_CLIC(obj) \
>     +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>     +
>     +/*
>     + * CLIC per hart active interrupts
>     + *
>     + * We maintain per hart lists of enabled interrupts sorted by
>     + * mode+level+priority. The sorting is done on the configuration path
>     + * so that the interrupt delivery fastpath can linear scan enabled
>     + * interrupts in priority order.
>     + */
>     +typedef struct CLICActiveInterrupt {
>     +    uint16_t intcfg;
>     +    uint16_t irq;
>     +} CLICActiveInterrupt;
>     +
>     +typedef enum TRIG_TYPE {
>     +    POSITIVE_LEVEL,
>     +    POSITIVE_EDGE,
>     +    NEG_LEVEL,
>     +    NEG_EDGE,
>     +} TRIG_TYPE;
>     +
>     +typedef struct RISCVCLICState {
>     +    /*< private >*/
>     +    SysBusDevice parent_obj;
>     +
>     +    /*< public >*/
>     +
>     +    /* Implementaion parameters */
>     +    bool prv_s;
>     +    bool prv_u;
>     +    uint32_t num_harts;
>     +    uint32_t num_sources;
>     +    uint32_t clic_size;
>     +    uint32_t clic_mmode_base;
>     +    uint32_t clicintctlbits;
>     +    uint64_t mclicbase;
>     +    char *version;
>     +
>     +    /* Global configuration */
>     +    uint8_t nmbits;
>     +    uint8_t nlbits;
>     +    uint8_t nvbits;
>     +    uint32_t clicinfo;
>     +    uint32_t clicinttrig[32];
>     +
>     +    /* Aperture configuration */
>     +    uint8_t *clicintip;
>     +    uint8_t *clicintie;
>     +    uint8_t *clicintattr;
>     +    uint8_t *clicintctl;
>     +
>     +    /* Complatible with v0.8 */
>     +    uint32_t mintthresh;
>     +    uint32_t sintthresh;
>     +    uint32_t uintthresh;
>     +
>     +    /* QEMU implementaion related fields */
>     +    uint32_t *exccode;
>     +    CLICActiveInterrupt *active_list;
>     +    size_t *active_count;
>     +    MemoryRegion mmio;
>     +    qemu_irq *cpu_irqs;
>     +} RISCVCLICState;
>     +
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version);
>     +
>     +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int
>     *il, int *irq);
>     +void riscv_clic_clean_pending(void *opaque, int mode, int hartid,
>     int irq);
>     +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>     hartid, int irq);
>     +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid,
>     int irq);
>     +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>     +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>     +#endif
>     diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>     index a5eab26a69..9e389d7bbf 100644
>     --- a/target/riscv/cpu.h
>     +++ b/target/riscv/cpu.h
>     @@ -33,6 +33,7 @@
>      #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>      #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>      #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>     +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>      #define TYPE_RISCV_CPU_ANY RISCV_CPU_TYPE_NAME("any")
>      #define TYPE_RISCV_CPU_BASE32  RISCV_CPU_TYPE_NAME("rv32")
>     @@ -247,6 +248,7 @@ struct CPURISCVState {
>          /* Fields from here on are preserved across CPU reset. */
>          QEMUTimer *timer; /* Internal timer */
>          void *clic;       /* clic interrupt controller */
>     +    uint32_t exccode; /* clic irq encode */
>      };
>
>      OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>     -- 
>     2.25.1
>
>
Frank Chang June 26, 2021, 12:56 p.m. UTC | #6
On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:

>
> On 6/13/21 6:10 PM, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>
>> The Core-Local Interrupt Controller (CLIC) provides low-latency,
>> vectored, pre-emptive interrupts for RISC-V systems.
>>
>> The CLIC also supports a new Selective Hardware Vectoring feature
>> that allow users to optimize each interrupt for either faster
>> response or smaller code size.
>>
>> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
>> ---
>>  default-configs/devices/riscv32-softmmu.mak |   1 +
>>  default-configs/devices/riscv64-softmmu.mak |   1 +
>>  hw/intc/Kconfig                             |   3 +
>>  hw/intc/meson.build                         |   1 +
>>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>>  include/hw/intc/riscv_clic.h                | 103 +++
>>  target/riscv/cpu.h                          |   2 +
>>  7 files changed, 946 insertions(+)
>>  create mode 100644 hw/intc/riscv_clic.c
>>  create mode 100644 include/hw/intc/riscv_clic.h
>>
>> diff --git a/default-configs/devices/riscv32-softmmu.mak
>> b/default-configs/devices/riscv32-softmmu.mak
>> index d847bd5692..1430c30588 100644
>> --- a/default-configs/devices/riscv32-softmmu.mak
>> +++ b/default-configs/devices/riscv32-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/default-configs/devices/riscv64-softmmu.mak
>> b/default-configs/devices/riscv64-softmmu.mak
>> index d5eec75f05..396800bbbd 100644
>> --- a/default-configs/devices/riscv64-softmmu.mak
>> +++ b/default-configs/devices/riscv64-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>> index f4694088a4..5bf492b48f 100644
>> --- a/hw/intc/Kconfig
>> +++ b/hw/intc/Kconfig
>> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>  config SIFIVE_PLIC
>>      bool
>>
>> +config RISCV_CLIC
>> +    bool
>> +
>>  config GOLDFISH_PIC
>>      bool
>>
>> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>> index 1c299039f6..2aa71b6738 100644
>> --- a/hw/intc/meson.build
>> +++ b/hw/intc/meson.build
>> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
>> files('s390_flic_kvm.c'))
>>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>> files('sifive_clint.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>> files('sifive_plic.c'))
>> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>> files('riscv_clic.c'))
>>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                 if_true: files('xics_kvm.c'))
>> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>> new file mode 100644
>> index 0000000000..8ad534c506
>> --- /dev/null
>> +++ b/hw/intc/riscv_clic.c
>> @@ -0,0 +1,835 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "sysemu/qtest.h"
>> +#include "target/riscv/cpu.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/intc/riscv_clic.h"
>> +
>> +/*
>> + * The 2-bit trig WARL field specifies the trigger type and polarity for
>> each
>> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
>> defined as
>> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
>> + */
>> +
>> +static inline TRIG_TYPE
>> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int npbits = clic->clicintctlbits - clic->nlbits;
>> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>> +
>> +    if (npbits < 0) {
>> +        return UINT8_MAX;
>> +    }
>> +    /* unused priority bits are set to 1 */
>> +    return (intctl & mask_priority) | mask_padding;
>> +}
>> +
>> +static void
>> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>> +                         uint8_t *mode, uint8_t *level,
>> +                         uint8_t *priority)
>> +{
>> +    *mode = intcfg >> 8;
>> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
>> +}
>> +
>> +/*
>> + * In a system with multiple harts, the M-mode CLIC regions for all the
>> harts
>> + * are placed contiguously in the memory space, followed by the S-mode
>> CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>> +static size_t
>> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t mode_offset = 0;
>> +    size_t unit = clic->num_harts * clic->num_sources;
>> +
>> +    switch (mode) {
>> +    case PRV_M:
>> +        mode_offset = 0;
>> +        break;
>> +    case PRV_S:
>> +        mode_offset = unit;
>> +        break;
>> +    case PRV_U:
>> +        mode_offset = clic->prv_s ? 2 * unit : unit;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid mode %d\n", mode);
>> +        exit(1);
>> +    }
>> +    return mode_offset + hartid * clic->num_sources + irq;
>> +}
>> +
>> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>> +{
>> +    /*
>> +     * Scan active list for highest priority pending interrupts
>> +     * comparing against this harts mintstatus register and interrupt
>> +     * the core if we have a higher priority interrupt to deliver
>> +     */
>> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>> +    CPURISCVState *env = &cpu->env;
>> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>> +
>> +    int il[4] = {
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>> +            clic->mintthresh), /* PRV_U */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>> +            clic->sintthresh), /* PRV_S */
>> +        0,                     /* reserverd */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>> +            clic->uintthresh)  /* PRV_M */
>> +    };
>> +
>> +    /* Get sorted list of enabled interrupts for this hart */
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>> +    size_t active_count = clic->active_count[hartid];
>> +    uint8_t mode, level, priority;
>> +
>> +    /* Loop through the enabled interrupts sorted by mode+priority+level
>> */
>> +    while (active_count) {
>> +        size_t irq_offset;
>> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>> +                                 &priority);
>> +        if (mode < env->priv || (mode == env->priv && level <=
>> il[mode])) {
>> +            /*
>> +             * No pending interrupts with high enough mode+priority+level
>> +             * break and clear pending interrupt for this hart
>> +             */
>> +            break;
>> +        }
>> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> active->irq);
>> +        /* Check pending interrupt with high enough mode+priority+level
>> */
>> +        if (clic->clicintip[irq_offset]) {
>> +            /* Clean vector edge-triggered pending */
>> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>> +                clic->clicintip[irq_offset] = 0;
>> +            }
>> +            /* Post pending interrupt for this hart */
>> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
>> 14;
>> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>> +            return;
>> +        }
>> +        /* Check next enabled interrupt */
>> +        active_count--;
>> +        active++;
>> +    }
>> +}
>> +
>> +/*
>> + * Any interrupt i that is not accessible to S-mode or U-Mode
>> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>> + */
>> +static bool
>> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
>> irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    if (!clic->prv_s && !clic->prv_u) { /* M */
>> +        return mode == PRV_M;
>> +    } else if (!clic->prv_s) { /* M/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_U);
>> +        default:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 for M/U hart");
>> +            exit(1);
>> +        }
>> +    } else { /* M/S/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_S);
>> +        case 2:
>> +            return mode == clic->clicintattr[irq_offset];
>> +        case 3:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
>> +            exit(1);
>> +        }
>> +    }
>> +    return false;
>> +}
>> +
>> +/*
>> + * For level-triggered interrupts, software writes to pending bits are
>> + * ignored completely. (Section 3.4)
>> + */
>> +static bool
>> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t value)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = !!value;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +/*
>> + * For security purpose, the field can only be set to a privilege
>> + * level that is equal mode to or lower than the currently running
>> + * privilege level.(Section 3.6)
>> + */
>> +
>> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
>> value)
>> +{
>> +    int mode = extract64(value, 6, 2);
>> +
>> +    if (!qtest_enabled()) {
>> +        CPURISCVState *env = current_cpu->env_ptr;
>> +        if (env->priv < mode) {
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt
>> *i)
>> +{
>> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority
>> */
>> +           (i->irq & 0xfff);             /* Highest irq number */
>> +}
>> +
>> +static int riscv_clic_active_compare(const void *a, const void *b)
>> +{
>> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +
>> sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) /
>> sizeof(CLICActiveInterrupt);
>> +        size_t sz = (--(*active_count) - elem) *
>> sizeof(CLICActiveInterrupt);
>> +        assert(result);
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand
>> the bit
>> +             * field, from FW perspective clicintip[i]=zero means no
>> interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode =
>> extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>> +        /*
>> +         * The implemented bits are kept left-justified in the
>> most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/* Return target interrupt mode */
>> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>> +    switch (mode) {
>> +    case 0:
>> +        return PRV_M;
>> +    case 1:
>> +        assert(clic->prv_s || clic->prv_u);
>> +        return clic->prv_s ? PRV_S : PRV_U;
>> +    case 2:
>> +        assert(clic->prv_s && clic->prv_u);
>> +        return PRV_U;
>> +    default:
>> +        g_assert_not_reached();
>> +        break;
>> +    }
>> +}
>> +
>> +/* Return target hart id */
>> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>> +    int hart_unit = 4 * clic->num_sources;
>> +
>> +    return (addr % mode_unit) / hart_unit;
>> +}
>> +
>> +/* Return target interrupt number */
>> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int hart_unit = 4 * clic->num_sources;
>> +    return (addr % hart_unit) / 4;
>> +}
>> +
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned
>> size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                {
>> +                    uint8_t nlbits = extract32(value, 1, 4);
>> +                    uint8_t nmbits = extract32(value, 5, 2);
>> +
>> +                    /*
>> +                     * The 4-bit cliccfg.nlbits WARL field.
>> +                     * Valid values are 0—8.
>> +                     */
>> +                    if (nlbits <= 8) {
>> +                        clic->nlbits = nlbits;
>> +                    }
>> +                    /* Valid values are given by implemented priviledges
>> */
>> +                    if (clic->prv_s && clic->prv_u) {
>> +                        if (nmbits <= 2) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else if (clic->prv_u) {
>> +                        if (nmbits <= 1) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else {
>> +                        assert(!clic->prv_s);
>> +                        if (nmbits == 0) {
>> +                            clic->nmbits = 0;
>> +                        }
>> +                    }
>> +                    clic->nvbits = extract32(value, 0, 1);
>> +                    break;
>> +                }
>> +            case 1: /* clicinfo, read-only register */
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: write read-only clicinfo.\n");
>> +                break;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                {
>> +                    uint32_t interrupt_number = value &
>> MAKE_64BIT_MASK(0, 13);
>> +                    if (interrupt_number <= clic->num_sources) {
>> +                        value &= ~MAKE_64BIT_MASK(13, 18);
>> +                        clic->clicinttrig[index - 0x10] = value;
>> +                    }
>> +                    break;
>> +                }
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    clic->mintthresh = value;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                return;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                riscv_clic_hart_write(clic, addr, value, size, mode,
>> +                                      hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +}
>> +
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                /*
>> +                 * clicinttrig register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 enable
>> +                 * 30:13 reserved (WARL 0)
>> +                 * 12:0 interrupt_number
>> +                 */
>> +                return clic->clicinttrig[index - 0x10] &
>> +                       ~MAKE_64BIT_MASK(13, 18);
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    return clic->mintthresh;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
>> irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void riscv_clic_set_irq(void *opaque, int id, int level)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    int irq, hartid, mode;
>> +    hwaddr addr = 4 * id;
>> +    TRIG_TYPE type;
>> +
>> +    hartid = riscv_clic_get_hartid(clic, addr);
>> +    mode = riscv_clic_get_mode(clic, addr);
>> +    irq = riscv_clic_get_irq(clic, addr);
>> +    type = riscv_clic_get_trigger_type(clic, id);
>> +
>> +    /*
>> +     * In general, the edge-triggered interrupt state should be kept in
>> pending
>> +     * bit, while the level-triggered interrupt should be kept in the
>> level
>> +     * state of the incoming wire.
>> +     *
>> +     * For CLIC, model the level-triggered interrupt by read-only
>> pending bit.
>> +     */
>> +    if (level) {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +        case POSITIVE_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case NEG_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        case NEG_EDGE:
>> +            break;
>> +        }
>> +    } else {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case POSITIVE_EDGE:
>> +            break;
>> +        case NEG_LEVEL:
>> +        case NEG_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
>> +{
>> +    CPURISCVState *env = (CPURISCVState *)opaque;
>> +    RISCVCLICState *clic = env->clic;
>> +    CPUState *cpu = env_cpu(env);
>> +
>> +    if (level) {
>> +        env->exccode = clic->exccode[cpu->cpu_index];
>> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps riscv_clic_ops = {
>> +    .read = riscv_clic_read,
>> +    .write = riscv_clic_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 8
>> +    }
>> +};
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>> clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>> +
>> +    /* Allocate irq through gpio, so that we can use qtest */
>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>> +
>> +    for (i = 0; i < clic->num_harts; i++) {
>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>
>
> As spec says:
>   Smaller single-core systems might have only a CLIC,
>   while multicore systems might have a CLIC per-core and a single shared
> PLIC.
>   The PLIC xeip signals are treated as hart-local interrupt sources by the
> CLIC at each core.
>
> It looks like it's possible to have one CLIC instance per core.
>
> If you want to delivery an interrupt to one hart, you should encode the
> IRQ by the interrupt number
> , the hart number and the interrupt target privilege, then set the irq.
>
> I think how to calculate the hart number is the task of PLIC and it can
> make use of "hartid-base"
> to calculate it.
>
> Thanks,
> Zhiwei
>

Hi Zhiwei,

What I mean is if there are multiple CLIC instances, each per core (CLIC
spec allows that).
If you try to bind CLIC with CPU index start from 0,
it will be impossible for CLIC instance to bind CPU from index other than 0.

For example, for 4 cores system, it's possible to have 4 CLIC instances:
  * CLIC 0 binds to CPU 0
  * CLIC 1 binds to CPU 1
  * CLIC 2 binds to CPU 2
  * CLIC 3 binds to CPU 3

and that's why I said it's possible to pass an extra "hartid-base" just
like PLIC.
I know most of hardid are calculated by the requesing address, so most
hartid usages should be fine.
But I saw two places using qemu_get_cpu(),
which may cause the problem for the scenario I describe above:
i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my original
reply.

Regards,
Frank Chang


> However if you try to bind CPU reference start from index i = 0.
> It's not possible for each per-core CLIC to bind their own CPU instance in
> multicore system
> as they have to bind from CPU 0.
>
> I'm not sure if we add a new "hartid-base" property just like what SiFive
> PLIC is
> implemented would be a good idea or not.
>
>
> Regards,
> Frank Chang
>
>
>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>> +                                         &cpu->env, 1);
>> +        qdev_connect_gpio_out(dev, i, irq);
>> +        cpu->env.clic = clic;
>> +    }
>> +}
>> +
>> +static Property riscv_clic_properties[] = {
>> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
>> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
>> 0),
>> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = riscv_clic_realize;
>> +    device_class_set_props(dc, riscv_clic_properties);
>> +}
>> +
>> +static const TypeInfo riscv_clic_info = {
>> +    .name          = TYPE_RISCV_CLIC,
>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(RISCVCLICState),
>> +    .class_init    = riscv_clic_class_init,
>> +};
>> +
>> +static void riscv_clic_register_types(void)
>> +{
>> +    type_register_static(&riscv_clic_info);
>> +}
>> +
>> +type_init(riscv_clic_register_types)
>> +
>> +/*
>> + * riscv_clic_create:
>> + *
>> + * @addr: base address of M-Mode CLIC memory-mapped registers
>> + * @prv_s: have smode region
>> + * @prv_u: have umode region
>> + * @num_harts: number of CPU harts
>> + * @num_sources: number of interrupts supporting by each aperture
>> + * @clicintctlbits: bits are actually implemented in the clicintctl
>> registers
>> + * @version: clic version, such as "v0.9"
>> + *
>> + * Returns: the device object
>> + */
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version)
>> +{
>> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>> +
>> +    assert(num_sources <= 4096);
>> +    assert(num_harts <= 1024);
>> +    assert(clicintctlbits <= 8);
>> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>> +
>> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>> +    qdev_prop_set_string(dev, "version", version);
>> +
>> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>> +    return dev;
>> +}
>> +
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>> +}
>> +
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = 0;
>> +}
>> +
>> +/*
>> + * The new CLIC interrupt-handling mode is encoded as a new state in
>> + * the existing WARL xtvec register, where the low two bits of  are 11.
>> + */
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>> +{
>> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
>> +    return env->clic && ((xtvec & 0x3) == 3);
>> +}
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>> +                               int *il, int *irq)
>> +{
>> +    *irq = extract32(exccode, 0, 12);
>> +    *mode = extract32(exccode, 12, 2);
>> +    *il = extract32(exccode, 14, 8);
>> +}
>> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
>> new file mode 100644
>> index 0000000000..e5f89672a6
>> --- /dev/null
>> +++ b/include/hw/intc/riscv_clic.h
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef RISCV_CLIC_H
>> +#define RISCV_CLIC_H
>> +
>> +#include "hw/irq.h"
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_RISCV_CLIC "riscv_clic"
>> +#define RISCV_CLIC(obj) \
>> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>> +
>> +/*
>> + * CLIC per hart active interrupts
>> + *
>> + * We maintain per hart lists of enabled interrupts sorted by
>> + * mode+level+priority. The sorting is done on the configuration path
>> + * so that the interrupt delivery fastpath can linear scan enabled
>> + * interrupts in priority order.
>> + */
>> +typedef struct CLICActiveInterrupt {
>> +    uint16_t intcfg;
>> +    uint16_t irq;
>> +} CLICActiveInterrupt;
>> +
>> +typedef enum TRIG_TYPE {
>> +    POSITIVE_LEVEL,
>> +    POSITIVE_EDGE,
>> +    NEG_LEVEL,
>> +    NEG_EDGE,
>> +} TRIG_TYPE;
>> +
>> +typedef struct RISCVCLICState {
>> +    /*< private >*/
>> +    SysBusDevice parent_obj;
>> +
>> +    /*< public >*/
>> +
>> +    /* Implementaion parameters */
>> +    bool prv_s;
>> +    bool prv_u;
>> +    uint32_t num_harts;
>> +    uint32_t num_sources;
>> +    uint32_t clic_size;
>> +    uint32_t clic_mmode_base;
>> +    uint32_t clicintctlbits;
>> +    uint64_t mclicbase;
>> +    char *version;
>> +
>> +    /* Global configuration */
>> +    uint8_t nmbits;
>> +    uint8_t nlbits;
>> +    uint8_t nvbits;
>> +    uint32_t clicinfo;
>> +    uint32_t clicinttrig[32];
>> +
>> +    /* Aperture configuration */
>> +    uint8_t *clicintip;
>> +    uint8_t *clicintie;
>> +    uint8_t *clicintattr;
>> +    uint8_t *clicintctl;
>> +
>> +    /* Complatible with v0.8 */
>> +    uint32_t mintthresh;
>> +    uint32_t sintthresh;
>> +    uint32_t uintthresh;
>> +
>> +    /* QEMU implementaion related fields */
>> +    uint32_t *exccode;
>> +    CLICActiveInterrupt *active_list;
>> +    size_t *active_count;
>> +    MemoryRegion mmio;
>> +    qemu_irq *cpu_irqs;
>> +} RISCVCLICState;
>> +
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version);
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
>> *irq);
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq);
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>> +#endif
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index a5eab26a69..9e389d7bbf 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -33,6 +33,7 @@
>>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
>> @@ -247,6 +248,7 @@ struct CPURISCVState {
>>      /* Fields from here on are preserved across CPU reset. */
>>      QEMUTimer *timer; /* Internal timer */
>>      void *clic;       /* clic interrupt controller */
>> +    uint32_t exccode; /* clic irq encode */
>>  };
>>
>>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>> --
>> 2.25.1
>>
>>
>>
Frank Chang June 26, 2021, 3:03 p.m. UTC | #7
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
>

According to spec:
  if the nlbits > CLICINTCTLBITS, then the lower bits of the 8-bit
  interrupt level are assumed to be all 1s.

That is, the valid nlbits should be: min(clic->nlbits, CLICINTCTLBITS);
The cliccfg example in spec also shows that:

CLICINTCTLBITS  nlbits  clicintctl[i]  interrupt levels
      0                       2         ........         255
      1                       2         l.......         127,255
      2                       2         ll......         63,127,191,255
      3                       3         lll.....
 31,63,95,127,159,191,223,255
      4                       1         lppp....      127,255


> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
>

The above description is not true any more in the latest spec:
  The CLIC specification does not dictate how CLIC memory-mapped registers
are
  split between M/S/U regions as well as the layout of multiple harts as
this is generally
  a platform issue and each platform needs to define a discovery mechanism
to determine
  the memory map locations.

But I think we can just keep the current design for now anyway, as it's
also one of legal memory layout.
Otherwise, the design would be more complicated I think.

+static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
>

I think what you are trying to do here is to get the number of elements
right after the active interrupt to be deleted in order to calculate the
size of
active interrupts to be memmoved.

However, according to C spec:
  When two pointers are subtracted, both shall point to elements of the
same array object,
  or one past the last element of the array object; the result is the
difference of the
  subscripts of the two array elements.

So I think: (result - active_list) is already the number of elements you
want.
You don't have to divide it with sizeof(CLICActiveInterrupt) again.


> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
>

Nit: assert(result) can be moved above size_t elem statement.


> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
>

Spec. says that it's legal to write 32-bit value to set
{clicintctl[i], clicintattr[i], clicintie[i] ,clicintip[i]} at the same
time:
  A 32-bit write to {clicintctl,clicintattr,clicintie,clicintip} is legal.
  However, there is no specified order in which the effects of
  the individual byte updates take effect.


> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
>

Typo: leastsignificant => least significant


> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
>

If irq i is already in the active_list, when will its intcfg been synced?


> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
>

Typo: clicintctl


> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
>

Is this necessary?
I think memory region size already limits the request address to be within
the range of clic_size.


> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
>

Same to riscv_clic_write().

Thanks,
Frank Chang


> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
>

clic->clicinfo should represent the CLIC setting information.
I think it's better to add clic reset function or in riscv_clic_realize()
to initialize clic->clicinfo.


> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>

Should the size of clic->active_list be: harts_x_sources?


> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
Frank Chang June 26, 2021, 3:20 p.m. UTC | #8
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>

According to spec:
  Since the CLIC memory map must be aligned at a 4KiB boundary,
  the mclicbase CSR has its 12 least-significant bits hardwired to zero.
  It is used to inform software about the location of CLIC memory mappped
registers.

I think it's better to add another addr check to ensure it's 4KiB aligned.

Thanks,
Frank Chang


> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
Frank Chang June 26, 2021, 3:26 p.m. UTC | #9
Frank Chang <frank.chang@sifive.com> 於 2021年6月26日 週六 下午11:03寫道:

> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>
>> The Core-Local Interrupt Controller (CLIC) provides low-latency,
>> vectored, pre-emptive interrupts for RISC-V systems.
>>
>> The CLIC also supports a new Selective Hardware Vectoring feature
>> that allow users to optimize each interrupt for either faster
>> response or smaller code size.
>>
>> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
>> ---
>>  default-configs/devices/riscv32-softmmu.mak |   1 +
>>  default-configs/devices/riscv64-softmmu.mak |   1 +
>>  hw/intc/Kconfig                             |   3 +
>>  hw/intc/meson.build                         |   1 +
>>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>>  include/hw/intc/riscv_clic.h                | 103 +++
>>  target/riscv/cpu.h                          |   2 +
>>  7 files changed, 946 insertions(+)
>>  create mode 100644 hw/intc/riscv_clic.c
>>  create mode 100644 include/hw/intc/riscv_clic.h
>>
>> diff --git a/default-configs/devices/riscv32-softmmu.mak
>> b/default-configs/devices/riscv32-softmmu.mak
>> index d847bd5692..1430c30588 100644
>> --- a/default-configs/devices/riscv32-softmmu.mak
>> +++ b/default-configs/devices/riscv32-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/default-configs/devices/riscv64-softmmu.mak
>> b/default-configs/devices/riscv64-softmmu.mak
>> index d5eec75f05..396800bbbd 100644
>> --- a/default-configs/devices/riscv64-softmmu.mak
>> +++ b/default-configs/devices/riscv64-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>> index f4694088a4..5bf492b48f 100644
>> --- a/hw/intc/Kconfig
>> +++ b/hw/intc/Kconfig
>> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>  config SIFIVE_PLIC
>>      bool
>>
>> +config RISCV_CLIC
>> +    bool
>> +
>>  config GOLDFISH_PIC
>>      bool
>>
>> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>> index 1c299039f6..2aa71b6738 100644
>> --- a/hw/intc/meson.build
>> +++ b/hw/intc/meson.build
>> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
>> files('s390_flic_kvm.c'))
>>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>> files('sifive_clint.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>> files('sifive_plic.c'))
>> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>> files('riscv_clic.c'))
>>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                 if_true: files('xics_kvm.c'))
>> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>> new file mode 100644
>> index 0000000000..8ad534c506
>> --- /dev/null
>> +++ b/hw/intc/riscv_clic.c
>> @@ -0,0 +1,835 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "sysemu/qtest.h"
>> +#include "target/riscv/cpu.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/intc/riscv_clic.h"
>> +
>> +/*
>> + * The 2-bit trig WARL field specifies the trigger type and polarity for
>> each
>> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
>> defined as
>> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
>> + */
>> +
>> +static inline TRIG_TYPE
>> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>>
>
> According to spec:
>   if the nlbits > CLICINTCTLBITS, then the lower bits of the 8-bit
>   interrupt level are assumed to be all 1s.
>
> That is, the valid nlbits should be: min(clic->nlbits, CLICINTCTLBITS);
> The cliccfg example in spec also shows that:
>
> CLICINTCTLBITS  nlbits  clicintctl[i]  interrupt levels
>       0                       2         ........         255
>       1                       2         l.......         127,255
>       2                       2         ll......         63,127,191,255
>       3                       3         lll.....
>  31,63,95,127,159,191,223,255
>       4                       1         lppp....      127,255
>
>
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int npbits = clic->clicintctlbits - clic->nlbits;
>> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>> +
>> +    if (npbits < 0) {
>> +        return UINT8_MAX;
>> +    }
>> +    /* unused priority bits are set to 1 */
>> +    return (intctl & mask_priority) | mask_padding;
>> +}
>> +
>> +static void
>> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>> +                         uint8_t *mode, uint8_t *level,
>> +                         uint8_t *priority)
>> +{
>> +    *mode = intcfg >> 8;
>> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
>> +}
>> +
>> +/*
>> + * In a system with multiple harts, the M-mode CLIC regions for all the
>> harts
>> + * are placed contiguously in the memory space, followed by the S-mode
>> CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>>
>
> The above description is not true any more in the latest spec:
>   The CLIC specification does not dictate how CLIC memory-mapped registers
> are
>   split between M/S/U regions as well as the layout of multiple harts as
> this is generally
>   a platform issue and each platform needs to define a discovery mechanism
> to determine
>   the memory map locations.
>

Comparing to 20210217 spec, the original description you wrote is correct.
But it's sad that the latest change is still in v0.9-draft, no version is
bumped.

Minor:
I think most of the description in comments may need to be updated
if you want to update to the latest v0.9-draft spec.
Do you have any plans to follow the latest changes?

Regards,
Frank Chang


>
> But I think we can just keep the current design for now anyway, as it's
> also one of legal memory layout.
> Otherwise, the design would be more complicated I think.
>

> +static size_t
>> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t mode_offset = 0;
>> +    size_t unit = clic->num_harts * clic->num_sources;
>> +
>> +    switch (mode) {
>> +    case PRV_M:
>> +        mode_offset = 0;
>> +        break;
>> +    case PRV_S:
>> +        mode_offset = unit;
>> +        break;
>> +    case PRV_U:
>> +        mode_offset = clic->prv_s ? 2 * unit : unit;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid mode %d\n", mode);
>> +        exit(1);
>> +    }
>> +    return mode_offset + hartid * clic->num_sources + irq;
>> +}
>> +
>> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>> +{
>> +    /*
>> +     * Scan active list for highest priority pending interrupts
>> +     * comparing against this harts mintstatus register and interrupt
>> +     * the core if we have a higher priority interrupt to deliver
>> +     */
>> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>> +    CPURISCVState *env = &cpu->env;
>> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>> +
>> +    int il[4] = {
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>> +            clic->mintthresh), /* PRV_U */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>> +            clic->sintthresh), /* PRV_S */
>> +        0,                     /* reserverd */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>> +            clic->uintthresh)  /* PRV_M */
>> +    };
>> +
>> +    /* Get sorted list of enabled interrupts for this hart */
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>> +    size_t active_count = clic->active_count[hartid];
>> +    uint8_t mode, level, priority;
>> +
>> +    /* Loop through the enabled interrupts sorted by mode+priority+level
>> */
>> +    while (active_count) {
>> +        size_t irq_offset;
>> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>> +                                 &priority);
>> +        if (mode < env->priv || (mode == env->priv && level <=
>> il[mode])) {
>> +            /*
>> +             * No pending interrupts with high enough mode+priority+level
>> +             * break and clear pending interrupt for this hart
>> +             */
>> +            break;
>> +        }
>> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> active->irq);
>> +        /* Check pending interrupt with high enough mode+priority+level
>> */
>> +        if (clic->clicintip[irq_offset]) {
>> +            /* Clean vector edge-triggered pending */
>> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>> +                clic->clicintip[irq_offset] = 0;
>> +            }
>> +            /* Post pending interrupt for this hart */
>> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
>> 14;
>> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>> +            return;
>> +        }
>> +        /* Check next enabled interrupt */
>> +        active_count--;
>> +        active++;
>> +    }
>> +}
>> +
>> +/*
>> + * Any interrupt i that is not accessible to S-mode or U-Mode
>> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>> + */
>> +static bool
>> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
>> irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    if (!clic->prv_s && !clic->prv_u) { /* M */
>> +        return mode == PRV_M;
>> +    } else if (!clic->prv_s) { /* M/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_U);
>> +        default:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 for M/U hart");
>> +            exit(1);
>> +        }
>> +    } else { /* M/S/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_S);
>> +        case 2:
>> +            return mode == clic->clicintattr[irq_offset];
>> +        case 3:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
>> +            exit(1);
>> +        }
>> +    }
>> +    return false;
>> +}
>> +
>> +/*
>> + * For level-triggered interrupts, software writes to pending bits are
>> + * ignored completely. (Section 3.4)
>> + */
>> +static bool
>> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t value)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = !!value;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +/*
>> + * For security purpose, the field can only be set to a privilege
>> + * level that is equal mode to or lower than the currently running
>> + * privilege level.(Section 3.6)
>> + */
>> +
>> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
>> value)
>> +{
>> +    int mode = extract64(value, 6, 2);
>> +
>> +    if (!qtest_enabled()) {
>> +        CPURISCVState *env = current_cpu->env_ptr;
>> +        if (env->priv < mode) {
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt
>> *i)
>> +{
>> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority
>> */
>> +           (i->irq & 0xfff);             /* Highest irq number */
>> +}
>> +
>> +static int riscv_clic_active_compare(const void *a, const void *b)
>> +{
>> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +
>> sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) /
>> sizeof(CLICActiveInterrupt);
>>
>
> I think what you are trying to do here is to get the number of elements
> right after the active interrupt to be deleted in order to calculate the
> size of
> active interrupts to be memmoved.
>
> However, according to C spec:
>   When two pointers are subtracted, both shall point to elements of the
> same array object,
>   or one past the last element of the array object; the result is the
> difference of the
>   subscripts of the two array elements.
>
> So I think: (result - active_list) is already the number of elements you
> want.
> You don't have to divide it with sizeof(CLICActiveInterrupt) again.
>
>
>> +        size_t sz = (--(*active_count) - elem) *
>> sizeof(CLICActiveInterrupt);
>> +        assert(result);
>>
>
> Nit: assert(result) can be moved above size_t elem statement.
>
>
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>>
>
> Spec. says that it's legal to write 32-bit value to set
> {clicintctl[i], clicintattr[i], clicintie[i] ,clicintip[i]} at the same
> time:
>   A 32-bit write to {clicintctl,clicintattr,clicintie,clicintip} is legal.
>   However, there is no specified order in which the effects of
>   the individual byte updates take effect.
>
>
>> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand
>> the bit
>>
>
> Typo: leastsignificant => least significant
>
>
>> +             * field, from FW perspective clicintip[i]=zero means no
>> interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode =
>> extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>>
>
> If irq i is already in the active_list, when will its intcfg been synced?
>
>
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>>
>
> Typo: clicintctl
>
>
>> +        /*
>> +         * The implemented bits are kept left-justified in the
>> most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/* Return target interrupt mode */
>> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>> +    switch (mode) {
>> +    case 0:
>> +        return PRV_M;
>> +    case 1:
>> +        assert(clic->prv_s || clic->prv_u);
>> +        return clic->prv_s ? PRV_S : PRV_U;
>> +    case 2:
>> +        assert(clic->prv_s && clic->prv_u);
>> +        return PRV_U;
>> +    default:
>> +        g_assert_not_reached();
>> +        break;
>> +    }
>> +}
>> +
>> +/* Return target hart id */
>> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>> +    int hart_unit = 4 * clic->num_sources;
>> +
>> +    return (addr % mode_unit) / hart_unit;
>> +}
>> +
>> +/* Return target interrupt number */
>> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int hart_unit = 4 * clic->num_sources;
>> +    return (addr % hart_unit) / 4;
>> +}
>> +
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned
>> size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>>
>
> Is this necessary?
> I think memory region size already limits the request address to be within
> the range of clic_size.
>
>
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                {
>> +                    uint8_t nlbits = extract32(value, 1, 4);
>> +                    uint8_t nmbits = extract32(value, 5, 2);
>> +
>> +                    /*
>> +                     * The 4-bit cliccfg.nlbits WARL field.
>> +                     * Valid values are 0—8.
>> +                     */
>> +                    if (nlbits <= 8) {
>> +                        clic->nlbits = nlbits;
>> +                    }
>> +                    /* Valid values are given by implemented priviledges
>> */
>> +                    if (clic->prv_s && clic->prv_u) {
>> +                        if (nmbits <= 2) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else if (clic->prv_u) {
>> +                        if (nmbits <= 1) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else {
>> +                        assert(!clic->prv_s);
>> +                        if (nmbits == 0) {
>> +                            clic->nmbits = 0;
>> +                        }
>> +                    }
>> +                    clic->nvbits = extract32(value, 0, 1);
>> +                    break;
>> +                }
>> +            case 1: /* clicinfo, read-only register */
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: write read-only clicinfo.\n");
>> +                break;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                {
>> +                    uint32_t interrupt_number = value &
>> MAKE_64BIT_MASK(0, 13);
>> +                    if (interrupt_number <= clic->num_sources) {
>> +                        value &= ~MAKE_64BIT_MASK(13, 18);
>> +                        clic->clicinttrig[index - 0x10] = value;
>> +                    }
>> +                    break;
>> +                }
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    clic->mintthresh = value;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                return;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                riscv_clic_hart_write(clic, addr, value, size, mode,
>> +                                      hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +}
>> +
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>>
>
> Same to riscv_clic_write().
>
> Thanks,
> Frank Chang
>
>
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>>
>
> clic->clicinfo should represent the CLIC setting information.
> I think it's better to add clic reset function or in riscv_clic_realize()
> to initialize clic->clicinfo.
>
>
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                /*
>> +                 * clicinttrig register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 enable
>> +                 * 30:13 reserved (WARL 0)
>> +                 * 12:0 interrupt_number
>> +                 */
>> +                return clic->clicinttrig[index - 0x10] &
>> +                       ~MAKE_64BIT_MASK(13, 18);
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    return clic->mintthresh;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
>> irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void riscv_clic_set_irq(void *opaque, int id, int level)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    int irq, hartid, mode;
>> +    hwaddr addr = 4 * id;
>> +    TRIG_TYPE type;
>> +
>> +    hartid = riscv_clic_get_hartid(clic, addr);
>> +    mode = riscv_clic_get_mode(clic, addr);
>> +    irq = riscv_clic_get_irq(clic, addr);
>> +    type = riscv_clic_get_trigger_type(clic, id);
>> +
>> +    /*
>> +     * In general, the edge-triggered interrupt state should be kept in
>> pending
>> +     * bit, while the level-triggered interrupt should be kept in the
>> level
>> +     * state of the incoming wire.
>> +     *
>> +     * For CLIC, model the level-triggered interrupt by read-only
>> pending bit.
>> +     */
>> +    if (level) {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +        case POSITIVE_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case NEG_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        case NEG_EDGE:
>> +            break;
>> +        }
>> +    } else {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case POSITIVE_EDGE:
>> +            break;
>> +        case NEG_LEVEL:
>> +        case NEG_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
>> +{
>> +    CPURISCVState *env = (CPURISCVState *)opaque;
>> +    RISCVCLICState *clic = env->clic;
>> +    CPUState *cpu = env_cpu(env);
>> +
>> +    if (level) {
>> +        env->exccode = clic->exccode[cpu->cpu_index];
>> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps riscv_clic_ops = {
>> +    .read = riscv_clic_read,
>> +    .write = riscv_clic_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 8
>> +    }
>> +};
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>> clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>
>
> Should the size of clic->active_list be: harts_x_sources?
>
>
>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>> +
>> +    /* Allocate irq through gpio, so that we can use qtest */
>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>> +
>> +    for (i = 0; i < clic->num_harts; i++) {
>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>> +                                         &cpu->env, 1);
>> +        qdev_connect_gpio_out(dev, i, irq);
>> +        cpu->env.clic = clic;
>> +    }
>> +}
>> +
>> +static Property riscv_clic_properties[] = {
>> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
>> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
>> 0),
>> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = riscv_clic_realize;
>> +    device_class_set_props(dc, riscv_clic_properties);
>> +}
>> +
>> +static const TypeInfo riscv_clic_info = {
>> +    .name          = TYPE_RISCV_CLIC,
>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(RISCVCLICState),
>> +    .class_init    = riscv_clic_class_init,
>> +};
>> +
>> +static void riscv_clic_register_types(void)
>> +{
>> +    type_register_static(&riscv_clic_info);
>> +}
>> +
>> +type_init(riscv_clic_register_types)
>> +
>> +/*
>> + * riscv_clic_create:
>> + *
>> + * @addr: base address of M-Mode CLIC memory-mapped registers
>> + * @prv_s: have smode region
>> + * @prv_u: have umode region
>> + * @num_harts: number of CPU harts
>> + * @num_sources: number of interrupts supporting by each aperture
>> + * @clicintctlbits: bits are actually implemented in the clicintctl
>> registers
>> + * @version: clic version, such as "v0.9"
>> + *
>> + * Returns: the device object
>> + */
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version)
>> +{
>> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>> +
>> +    assert(num_sources <= 4096);
>> +    assert(num_harts <= 1024);
>> +    assert(clicintctlbits <= 8);
>> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>> +
>> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>> +    qdev_prop_set_string(dev, "version", version);
>> +
>> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>> +    return dev;
>> +}
>> +
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>> +}
>> +
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = 0;
>> +}
>> +
>> +/*
>> + * The new CLIC interrupt-handling mode is encoded as a new state in
>> + * the existing WARL xtvec register, where the low two bits of  are 11.
>> + */
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>> +{
>> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
>> +    return env->clic && ((xtvec & 0x3) == 3);
>> +}
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>> +                               int *il, int *irq)
>> +{
>> +    *irq = extract32(exccode, 0, 12);
>> +    *mode = extract32(exccode, 12, 2);
>> +    *il = extract32(exccode, 14, 8);
>> +}
>> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
>> new file mode 100644
>> index 0000000000..e5f89672a6
>> --- /dev/null
>> +++ b/include/hw/intc/riscv_clic.h
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef RISCV_CLIC_H
>> +#define RISCV_CLIC_H
>> +
>> +#include "hw/irq.h"
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_RISCV_CLIC "riscv_clic"
>> +#define RISCV_CLIC(obj) \
>> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>> +
>> +/*
>> + * CLIC per hart active interrupts
>> + *
>> + * We maintain per hart lists of enabled interrupts sorted by
>> + * mode+level+priority. The sorting is done on the configuration path
>> + * so that the interrupt delivery fastpath can linear scan enabled
>> + * interrupts in priority order.
>> + */
>> +typedef struct CLICActiveInterrupt {
>> +    uint16_t intcfg;
>> +    uint16_t irq;
>> +} CLICActiveInterrupt;
>> +
>> +typedef enum TRIG_TYPE {
>> +    POSITIVE_LEVEL,
>> +    POSITIVE_EDGE,
>> +    NEG_LEVEL,
>> +    NEG_EDGE,
>> +} TRIG_TYPE;
>> +
>> +typedef struct RISCVCLICState {
>> +    /*< private >*/
>> +    SysBusDevice parent_obj;
>> +
>> +    /*< public >*/
>> +
>> +    /* Implementaion parameters */
>> +    bool prv_s;
>> +    bool prv_u;
>> +    uint32_t num_harts;
>> +    uint32_t num_sources;
>> +    uint32_t clic_size;
>> +    uint32_t clic_mmode_base;
>> +    uint32_t clicintctlbits;
>> +    uint64_t mclicbase;
>> +    char *version;
>> +
>> +    /* Global configuration */
>> +    uint8_t nmbits;
>> +    uint8_t nlbits;
>> +    uint8_t nvbits;
>> +    uint32_t clicinfo;
>> +    uint32_t clicinttrig[32];
>> +
>> +    /* Aperture configuration */
>> +    uint8_t *clicintip;
>> +    uint8_t *clicintie;
>> +    uint8_t *clicintattr;
>> +    uint8_t *clicintctl;
>> +
>> +    /* Complatible with v0.8 */
>> +    uint32_t mintthresh;
>> +    uint32_t sintthresh;
>> +    uint32_t uintthresh;
>> +
>> +    /* QEMU implementaion related fields */
>> +    uint32_t *exccode;
>> +    CLICActiveInterrupt *active_list;
>> +    size_t *active_count;
>> +    MemoryRegion mmio;
>> +    qemu_irq *cpu_irqs;
>> +} RISCVCLICState;
>> +
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version);
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
>> *irq);
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq);
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>> +#endif
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index a5eab26a69..9e389d7bbf 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -33,6 +33,7 @@
>>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
>> @@ -247,6 +248,7 @@ struct CPURISCVState {
>>      /* Fields from here on are preserved across CPU reset. */
>>      QEMUTimer *timer; /* Internal timer */
>>      void *clic;       /* clic interrupt controller */
>> +    uint32_t exccode; /* clic irq encode */
>>  };
>>
>>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>> --
>> 2.25.1
>>
>>
>>
Frank Chang June 26, 2021, 5:15 p.m. UTC | #10
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
>

The mintthresh register layout is not cleared in v0.8 spec[1].
I think mintthresh holds the thresholds for four different privileges (mth,
hth, sth, uth)?
Each of them occupies 8-bits?

If you want to support v0.8 CLIC as well, then I think you have to
extract threshold values from mintthresh for v0.8 CLIC,
or assign clic->sintthresh and clic->uintthresh when user writes to
mintthresh memory-mapped register.
Otherwise, sintthresh and uintthresh should be valid in v0.9 CLIC only.

v0.8 spec is also vague on whether mintthresh is a CLIC memory-mapped
register or a CSR:
It does have the corresponding memory-mapped address,
but spec. also describes it as a CSR register:
  The interrupt-level threshold (mintthresh) is a new read-write CSR,
  which holds an 8-bit field for each privilege mode (i.e., mth, hth, sth,
uth).

But as long as there's no CSR number for mintthresh,
so I guess it should be a memory-mapped register.

Also, for v0.9 CLIC, I think it's better to AND xintthresh with 8-bit mask
because xintthresh is defined as target_ulong CSRs in cpu.h.
User might write something larger than 8 bits.

[1] https://github.com/riscv/riscv-fast-interrupt/blob/74f86c3858/clic.adoc

Regards,
Frank Chang

+
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
Frank Chang June 26, 2021, 5:19 p.m. UTC | #11
Frank Chang <frank.chang@sifive.com> 於 2021年6月27日 週日 上午1:15寫道:

> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>
>> The Core-Local Interrupt Controller (CLIC) provides low-latency,
>> vectored, pre-emptive interrupts for RISC-V systems.
>>
>> The CLIC also supports a new Selective Hardware Vectoring feature
>> that allow users to optimize each interrupt for either faster
>> response or smaller code size.
>>
>> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
>> ---
>>  default-configs/devices/riscv32-softmmu.mak |   1 +
>>  default-configs/devices/riscv64-softmmu.mak |   1 +
>>  hw/intc/Kconfig                             |   3 +
>>  hw/intc/meson.build                         |   1 +
>>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>>  include/hw/intc/riscv_clic.h                | 103 +++
>>  target/riscv/cpu.h                          |   2 +
>>  7 files changed, 946 insertions(+)
>>  create mode 100644 hw/intc/riscv_clic.c
>>  create mode 100644 include/hw/intc/riscv_clic.h
>>
>> diff --git a/default-configs/devices/riscv32-softmmu.mak
>> b/default-configs/devices/riscv32-softmmu.mak
>> index d847bd5692..1430c30588 100644
>> --- a/default-configs/devices/riscv32-softmmu.mak
>> +++ b/default-configs/devices/riscv32-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/default-configs/devices/riscv64-softmmu.mak
>> b/default-configs/devices/riscv64-softmmu.mak
>> index d5eec75f05..396800bbbd 100644
>> --- a/default-configs/devices/riscv64-softmmu.mak
>> +++ b/default-configs/devices/riscv64-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>> index f4694088a4..5bf492b48f 100644
>> --- a/hw/intc/Kconfig
>> +++ b/hw/intc/Kconfig
>> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>  config SIFIVE_PLIC
>>      bool
>>
>> +config RISCV_CLIC
>> +    bool
>> +
>>  config GOLDFISH_PIC
>>      bool
>>
>> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>> index 1c299039f6..2aa71b6738 100644
>> --- a/hw/intc/meson.build
>> +++ b/hw/intc/meson.build
>> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
>> files('s390_flic_kvm.c'))
>>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>> files('sifive_clint.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>> files('sifive_plic.c'))
>> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>> files('riscv_clic.c'))
>>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                 if_true: files('xics_kvm.c'))
>> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>> new file mode 100644
>> index 0000000000..8ad534c506
>> --- /dev/null
>> +++ b/hw/intc/riscv_clic.c
>> @@ -0,0 +1,835 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "sysemu/qtest.h"
>> +#include "target/riscv/cpu.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/intc/riscv_clic.h"
>> +
>> +/*
>> + * The 2-bit trig WARL field specifies the trigger type and polarity for
>> each
>> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
>> defined as
>> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
>> + */
>> +
>> +static inline TRIG_TYPE
>> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int npbits = clic->clicintctlbits - clic->nlbits;
>> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>> +
>> +    if (npbits < 0) {
>> +        return UINT8_MAX;
>> +    }
>> +    /* unused priority bits are set to 1 */
>> +    return (intctl & mask_priority) | mask_padding;
>> +}
>> +
>> +static void
>> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>> +                         uint8_t *mode, uint8_t *level,
>> +                         uint8_t *priority)
>> +{
>> +    *mode = intcfg >> 8;
>> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
>> +}
>> +
>> +/*
>> + * In a system with multiple harts, the M-mode CLIC regions for all the
>> harts
>> + * are placed contiguously in the memory space, followed by the S-mode
>> CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>> +static size_t
>> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t mode_offset = 0;
>> +    size_t unit = clic->num_harts * clic->num_sources;
>> +
>> +    switch (mode) {
>> +    case PRV_M:
>> +        mode_offset = 0;
>> +        break;
>> +    case PRV_S:
>> +        mode_offset = unit;
>> +        break;
>> +    case PRV_U:
>> +        mode_offset = clic->prv_s ? 2 * unit : unit;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid mode %d\n", mode);
>> +        exit(1);
>> +    }
>> +    return mode_offset + hartid * clic->num_sources + irq;
>> +}
>> +
>> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>> +{
>> +    /*
>> +     * Scan active list for highest priority pending interrupts
>> +     * comparing against this harts mintstatus register and interrupt
>> +     * the core if we have a higher priority interrupt to deliver
>> +     */
>> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>> +    CPURISCVState *env = &cpu->env;
>> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>> +
>> +    int il[4] = {
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>> +            clic->mintthresh), /* PRV_U */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>> +            clic->sintthresh), /* PRV_S */
>> +        0,                     /* reserverd */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>> +            clic->uintthresh)  /* PRV_M */
>> +    };
>>
>
> The mintthresh register layout is not cleared in v0.8 spec[1].
> I think mintthresh holds the thresholds for four different privileges
> (mth, hth, sth, uth)?
> Each of them occupies 8-bits?
>
> If you want to support v0.8 CLIC as well, then I think you have to
> extract threshold values from mintthresh for v0.8 CLIC,
> or assign clic->sintthresh and clic->uintthresh when user writes to
> mintthresh memory-mapped register.
> Otherwise, sintthresh and uintthresh should be valid in v0.9 CLIC only.
>
> v0.8 spec is also vague on whether mintthresh is a CLIC memory-mapped
> register or a CSR:
> It does have the corresponding memory-mapped address,
> but spec. also describes it as a CSR register:
>   The interrupt-level threshold (mintthresh) is a new read-write CSR,
>   which holds an 8-bit field for each privilege mode (i.e., mth, hth, sth,
> uth).
>
> But as long as there's no CSR number for mintthresh,
> so I guess it should be a memory-mapped register.
>
> Also, for v0.9 CLIC, I think it's better to AND xintthresh with 8-bit mask
> because xintthresh is defined as target_ulong CSRs in cpu.h.
> User might write something larger than 8 bits.
>

Sorry, I forgot to mention.
Shouldn't xintthresh values retrieved from env->xintthresh for v0.9 CLIC?
(xintthresh are CSRs, instead of CLIC memory-mapped register in v0.9 CLIC.)

Regards,
Frank Chang


>
> [1]
> https://github.com/riscv/riscv-fast-interrupt/blob/74f86c3858/clic.adoc
>
> Regards,
> Frank Chang
>
> +
>> +    /* Get sorted list of enabled interrupts for this hart */
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>> +    size_t active_count = clic->active_count[hartid];
>> +    uint8_t mode, level, priority;
>> +
>> +    /* Loop through the enabled interrupts sorted by mode+priority+level
>> */
>> +    while (active_count) {
>> +        size_t irq_offset;
>> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>> +                                 &priority);
>> +        if (mode < env->priv || (mode == env->priv && level <=
>> il[mode])) {
>> +            /*
>> +             * No pending interrupts with high enough mode+priority+level
>> +             * break and clear pending interrupt for this hart
>> +             */
>> +            break;
>> +        }
>> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> active->irq);
>> +        /* Check pending interrupt with high enough mode+priority+level
>> */
>> +        if (clic->clicintip[irq_offset]) {
>> +            /* Clean vector edge-triggered pending */
>> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>> +                clic->clicintip[irq_offset] = 0;
>> +            }
>> +            /* Post pending interrupt for this hart */
>> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
>> 14;
>> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>> +            return;
>> +        }
>> +        /* Check next enabled interrupt */
>> +        active_count--;
>> +        active++;
>> +    }
>> +}
>> +
>> +/*
>> + * Any interrupt i that is not accessible to S-mode or U-Mode
>> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>> + */
>> +static bool
>> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
>> irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    if (!clic->prv_s && !clic->prv_u) { /* M */
>> +        return mode == PRV_M;
>> +    } else if (!clic->prv_s) { /* M/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_U);
>> +        default:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 for M/U hart");
>> +            exit(1);
>> +        }
>> +    } else { /* M/S/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_S);
>> +        case 2:
>> +            return mode == clic->clicintattr[irq_offset];
>> +        case 3:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
>> +            exit(1);
>> +        }
>> +    }
>> +    return false;
>> +}
>> +
>> +/*
>> + * For level-triggered interrupts, software writes to pending bits are
>> + * ignored completely. (Section 3.4)
>> + */
>> +static bool
>> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t value)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = !!value;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +/*
>> + * For security purpose, the field can only be set to a privilege
>> + * level that is equal mode to or lower than the currently running
>> + * privilege level.(Section 3.6)
>> + */
>> +
>> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
>> value)
>> +{
>> +    int mode = extract64(value, 6, 2);
>> +
>> +    if (!qtest_enabled()) {
>> +        CPURISCVState *env = current_cpu->env_ptr;
>> +        if (env->priv < mode) {
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt
>> *i)
>> +{
>> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority
>> */
>> +           (i->irq & 0xfff);             /* Highest irq number */
>> +}
>> +
>> +static int riscv_clic_active_compare(const void *a, const void *b)
>> +{
>> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +
>> sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) /
>> sizeof(CLICActiveInterrupt);
>> +        size_t sz = (--(*active_count) - elem) *
>> sizeof(CLICActiveInterrupt);
>> +        assert(result);
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand
>> the bit
>> +             * field, from FW perspective clicintip[i]=zero means no
>> interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode =
>> extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>> +        /*
>> +         * The implemented bits are kept left-justified in the
>> most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/* Return target interrupt mode */
>> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>> +    switch (mode) {
>> +    case 0:
>> +        return PRV_M;
>> +    case 1:
>> +        assert(clic->prv_s || clic->prv_u);
>> +        return clic->prv_s ? PRV_S : PRV_U;
>> +    case 2:
>> +        assert(clic->prv_s && clic->prv_u);
>> +        return PRV_U;
>> +    default:
>> +        g_assert_not_reached();
>> +        break;
>> +    }
>> +}
>> +
>> +/* Return target hart id */
>> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>> +    int hart_unit = 4 * clic->num_sources;
>> +
>> +    return (addr % mode_unit) / hart_unit;
>> +}
>> +
>> +/* Return target interrupt number */
>> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int hart_unit = 4 * clic->num_sources;
>> +    return (addr % hart_unit) / 4;
>> +}
>> +
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned
>> size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                {
>> +                    uint8_t nlbits = extract32(value, 1, 4);
>> +                    uint8_t nmbits = extract32(value, 5, 2);
>> +
>> +                    /*
>> +                     * The 4-bit cliccfg.nlbits WARL field.
>> +                     * Valid values are 0—8.
>> +                     */
>> +                    if (nlbits <= 8) {
>> +                        clic->nlbits = nlbits;
>> +                    }
>> +                    /* Valid values are given by implemented priviledges
>> */
>> +                    if (clic->prv_s && clic->prv_u) {
>> +                        if (nmbits <= 2) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else if (clic->prv_u) {
>> +                        if (nmbits <= 1) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else {
>> +                        assert(!clic->prv_s);
>> +                        if (nmbits == 0) {
>> +                            clic->nmbits = 0;
>> +                        }
>> +                    }
>> +                    clic->nvbits = extract32(value, 0, 1);
>> +                    break;
>> +                }
>> +            case 1: /* clicinfo, read-only register */
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: write read-only clicinfo.\n");
>> +                break;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                {
>> +                    uint32_t interrupt_number = value &
>> MAKE_64BIT_MASK(0, 13);
>> +                    if (interrupt_number <= clic->num_sources) {
>> +                        value &= ~MAKE_64BIT_MASK(13, 18);
>> +                        clic->clicinttrig[index - 0x10] = value;
>> +                    }
>> +                    break;
>> +                }
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    clic->mintthresh = value;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                return;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                riscv_clic_hart_write(clic, addr, value, size, mode,
>> +                                      hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +}
>> +
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                /*
>> +                 * clicinttrig register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 enable
>> +                 * 30:13 reserved (WARL 0)
>> +                 * 12:0 interrupt_number
>> +                 */
>> +                return clic->clicinttrig[index - 0x10] &
>> +                       ~MAKE_64BIT_MASK(13, 18);
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    return clic->mintthresh;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
>> irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void riscv_clic_set_irq(void *opaque, int id, int level)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    int irq, hartid, mode;
>> +    hwaddr addr = 4 * id;
>> +    TRIG_TYPE type;
>> +
>> +    hartid = riscv_clic_get_hartid(clic, addr);
>> +    mode = riscv_clic_get_mode(clic, addr);
>> +    irq = riscv_clic_get_irq(clic, addr);
>> +    type = riscv_clic_get_trigger_type(clic, id);
>> +
>> +    /*
>> +     * In general, the edge-triggered interrupt state should be kept in
>> pending
>> +     * bit, while the level-triggered interrupt should be kept in the
>> level
>> +     * state of the incoming wire.
>> +     *
>> +     * For CLIC, model the level-triggered interrupt by read-only
>> pending bit.
>> +     */
>> +    if (level) {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +        case POSITIVE_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case NEG_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        case NEG_EDGE:
>> +            break;
>> +        }
>> +    } else {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case POSITIVE_EDGE:
>> +            break;
>> +        case NEG_LEVEL:
>> +        case NEG_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
>> +{
>> +    CPURISCVState *env = (CPURISCVState *)opaque;
>> +    RISCVCLICState *clic = env->clic;
>> +    CPUState *cpu = env_cpu(env);
>> +
>> +    if (level) {
>> +        env->exccode = clic->exccode[cpu->cpu_index];
>> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps riscv_clic_ops = {
>> +    .read = riscv_clic_read,
>> +    .write = riscv_clic_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 8
>> +    }
>> +};
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>> clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>> +
>> +    /* Allocate irq through gpio, so that we can use qtest */
>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>> +
>> +    for (i = 0; i < clic->num_harts; i++) {
>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>> +                                         &cpu->env, 1);
>> +        qdev_connect_gpio_out(dev, i, irq);
>> +        cpu->env.clic = clic;
>> +    }
>> +}
>> +
>> +static Property riscv_clic_properties[] = {
>> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
>> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
>> 0),
>> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = riscv_clic_realize;
>> +    device_class_set_props(dc, riscv_clic_properties);
>> +}
>> +
>> +static const TypeInfo riscv_clic_info = {
>> +    .name          = TYPE_RISCV_CLIC,
>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(RISCVCLICState),
>> +    .class_init    = riscv_clic_class_init,
>> +};
>> +
>> +static void riscv_clic_register_types(void)
>> +{
>> +    type_register_static(&riscv_clic_info);
>> +}
>> +
>> +type_init(riscv_clic_register_types)
>> +
>> +/*
>> + * riscv_clic_create:
>> + *
>> + * @addr: base address of M-Mode CLIC memory-mapped registers
>> + * @prv_s: have smode region
>> + * @prv_u: have umode region
>> + * @num_harts: number of CPU harts
>> + * @num_sources: number of interrupts supporting by each aperture
>> + * @clicintctlbits: bits are actually implemented in the clicintctl
>> registers
>> + * @version: clic version, such as "v0.9"
>> + *
>> + * Returns: the device object
>> + */
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version)
>> +{
>> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>> +
>> +    assert(num_sources <= 4096);
>> +    assert(num_harts <= 1024);
>> +    assert(clicintctlbits <= 8);
>> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>> +
>> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>> +    qdev_prop_set_string(dev, "version", version);
>> +
>> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>> +    return dev;
>> +}
>> +
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>> +}
>> +
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = 0;
>> +}
>> +
>> +/*
>> + * The new CLIC interrupt-handling mode is encoded as a new state in
>> + * the existing WARL xtvec register, where the low two bits of  are 11.
>> + */
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>> +{
>> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
>> +    return env->clic && ((xtvec & 0x3) == 3);
>> +}
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>> +                               int *il, int *irq)
>> +{
>> +    *irq = extract32(exccode, 0, 12);
>> +    *mode = extract32(exccode, 12, 2);
>> +    *il = extract32(exccode, 14, 8);
>> +}
>> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
>> new file mode 100644
>> index 0000000000..e5f89672a6
>> --- /dev/null
>> +++ b/include/hw/intc/riscv_clic.h
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef RISCV_CLIC_H
>> +#define RISCV_CLIC_H
>> +
>> +#include "hw/irq.h"
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_RISCV_CLIC "riscv_clic"
>> +#define RISCV_CLIC(obj) \
>> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>> +
>> +/*
>> + * CLIC per hart active interrupts
>> + *
>> + * We maintain per hart lists of enabled interrupts sorted by
>> + * mode+level+priority. The sorting is done on the configuration path
>> + * so that the interrupt delivery fastpath can linear scan enabled
>> + * interrupts in priority order.
>> + */
>> +typedef struct CLICActiveInterrupt {
>> +    uint16_t intcfg;
>> +    uint16_t irq;
>> +} CLICActiveInterrupt;
>> +
>> +typedef enum TRIG_TYPE {
>> +    POSITIVE_LEVEL,
>> +    POSITIVE_EDGE,
>> +    NEG_LEVEL,
>> +    NEG_EDGE,
>> +} TRIG_TYPE;
>> +
>> +typedef struct RISCVCLICState {
>> +    /*< private >*/
>> +    SysBusDevice parent_obj;
>> +
>> +    /*< public >*/
>> +
>> +    /* Implementaion parameters */
>> +    bool prv_s;
>> +    bool prv_u;
>> +    uint32_t num_harts;
>> +    uint32_t num_sources;
>> +    uint32_t clic_size;
>> +    uint32_t clic_mmode_base;
>> +    uint32_t clicintctlbits;
>> +    uint64_t mclicbase;
>> +    char *version;
>> +
>> +    /* Global configuration */
>> +    uint8_t nmbits;
>> +    uint8_t nlbits;
>> +    uint8_t nvbits;
>> +    uint32_t clicinfo;
>> +    uint32_t clicinttrig[32];
>> +
>> +    /* Aperture configuration */
>> +    uint8_t *clicintip;
>> +    uint8_t *clicintie;
>> +    uint8_t *clicintattr;
>> +    uint8_t *clicintctl;
>> +
>> +    /* Complatible with v0.8 */
>> +    uint32_t mintthresh;
>> +    uint32_t sintthresh;
>> +    uint32_t uintthresh;
>> +
>> +    /* QEMU implementaion related fields */
>> +    uint32_t *exccode;
>> +    CLICActiveInterrupt *active_list;
>> +    size_t *active_count;
>> +    MemoryRegion mmio;
>> +    qemu_irq *cpu_irqs;
>> +} RISCVCLICState;
>> +
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version);
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
>> *irq);
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq);
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>> +#endif
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index a5eab26a69..9e389d7bbf 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -33,6 +33,7 @@
>>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
>> @@ -247,6 +248,7 @@ struct CPURISCVState {
>>      /* Fields from here on are preserved across CPU reset. */
>>      QEMUTimer *timer; /* Internal timer */
>>      void *clic;       /* clic interrupt controller */
>> +    uint32_t exccode; /* clic irq encode */
>>  };
>>
>>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>> --
>> 2.25.1
>>
>>
>>
LIU Zhiwei June 28, 2021, 7:15 a.m. UTC | #12
On 2021/6/26 下午8:56, Frank Chang wrote:
> On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com 
> <mailto:zhiwei_liu@c-sky.com>> wrote:
>
>
>     On 6/13/21 6:10 PM, Frank Chang wrote:
>>     LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於
>>     2021年4月9日 週五 下午3:57寫道:
>>
>>         +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>         +{
>>         +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>         +    size_t harts_x_sources = clic->num_harts *
>>         clic->num_sources;
>>         +    int irqs, i;
>>         +
>>         +    if (clic->prv_s && clic->prv_u) {
>>         +        irqs = 3 * harts_x_sources;
>>         +    } else if (clic->prv_s || clic->prv_u) {
>>         +        irqs = 2 * harts_x_sources;
>>         +    } else {
>>         +        irqs = harts_x_sources;
>>         +    }
>>         +
>>         +    clic->clic_size = irqs * 4 + 0x1000;
>>         +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>>         &riscv_clic_ops, clic,
>>         +                          TYPE_RISCV_CLIC, clic->clic_size);
>>         +
>>         +    clic->clicintip = g_new0(uint8_t, irqs);
>>         +    clic->clicintie = g_new0(uint8_t, irqs);
>>         +    clic->clicintattr = g_new0(uint8_t, irqs);
>>         +    clic->clicintctl = g_new0(uint8_t, irqs);
>>         +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>         +    clic->active_count = g_new0(size_t, clic->num_harts);
>>         +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>         +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>         +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>         +
>>         +    /* Allocate irq through gpio, so that we can use qtest */
>>         +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>         +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>         +
>>         +    for (i = 0; i < clic->num_harts; i++) {
>>         +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>
>>
>>     As spec says:
>>       Smaller single-core systems might have only a CLIC,
>>       while multicore systems might have a CLIC per-core and a single
>>     shared PLIC.
>>       The PLIC xeip signals are treated as hart-local interrupt
>>     sources by the CLIC at each core.
>>
>>     It looks like it's possible to have one CLIC instance per core.
>
>     If you want to delivery an interrupt to one hart, you should
>     encode the IRQ by the interrupt number
>     , the hart number and the interrupt target privilege, then set the
>     irq.
>
>     I think how to calculate the hart number is the task of PLIC and
>     it can make use of "hartid-base"
>     to calculate it.
>
>     Thanks,
>     Zhiwei
>
>
> Hi Zhiwei,
>
> What I mean is if there are multiple CLIC instances, each per core 
> (CLIC spec allows that).
> If you try to bind CLIC with CPU index start from 0,
> it will be impossible for CLIC instance to bind CPU from index other 
> than 0.
>
> For example, for 4 cores system, it's possible to have 4 CLIC instances:
>   * CLIC 0 binds to CPU 0
>   * CLIC 1 binds to CPU 1
>   * CLIC 2 binds to CPU 2
>   * CLIC 3 binds to CPU 3
>
> and that's why I said it's possible to pass an extra "hartid-base" 
> just like PLIC.
> I know most of hardid are calculated by the requesing address, so most 
> hartid usages should be fine.
> But I saw two places using qemu_get_cpu(),
> which may cause the problem for the scenario I describe above:
> i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my 
> original reply.

So what's the problem here?

Currently all cores share the same CLIC instance. Do you want to give 
each core  a CLIC instance?

Thanks,
Zhiwei

>
> Regards,
> Frank Chang
>
>>     However if you try to bind CPU reference start from index i = 0.
>>     It's not possible for each per-core CLIC to bind their own CPU
>>     instance in multicore system
>>     as they have to bind from CPU 0.
>>
>>     I'm not sure if we add a new "hartid-base" property just like
>>     what SiFive PLIC is
>>     implemented would be a good idea or not.
>>
>>
>>     Regards,
>>     Frank Chang
>>
>>         + qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>         +  &cpu->env, 1);
>>         +        qdev_connect_gpio_out(dev, i, irq);
>>         +        cpu->env.clic = clic;
>>         +    }
>>         +}
>>         +
>>
>>
Frank Chang June 28, 2021, 7:23 a.m. UTC | #13
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:17寫道:

>
> On 2021/6/26 下午8:56, Frank Chang wrote:
>
> On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:
>
>>
>> On 6/13/21 6:10 PM, Frank Chang wrote:
>>
>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>>
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>> +{
>>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>>> +    int irqs, i;
>>> +
>>> +    if (clic->prv_s && clic->prv_u) {
>>> +        irqs = 3 * harts_x_sources;
>>> +    } else if (clic->prv_s || clic->prv_u) {
>>> +        irqs = 2 * harts_x_sources;
>>> +    } else {
>>> +        irqs = harts_x_sources;
>>> +    }
>>> +
>>> +    clic->clic_size = irqs * 4 + 0x1000;
>>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>>> clic,
>>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>>> +
>>> +    clic->clicintip = g_new0(uint8_t, irqs);
>>> +    clic->clicintie = g_new0(uint8_t, irqs);
>>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>> +
>>> +    /* Allocate irq through gpio, so that we can use qtest */
>>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>> +
>>> +    for (i = 0; i < clic->num_harts; i++) {
>>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>
>>
>> As spec says:
>>   Smaller single-core systems might have only a CLIC,
>>   while multicore systems might have a CLIC per-core and a single shared
>> PLIC.
>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>> the CLIC at each core.
>>
>> It looks like it's possible to have one CLIC instance per core.
>>
>> If you want to delivery an interrupt to one hart, you should encode the
>> IRQ by the interrupt number
>> , the hart number and the interrupt target privilege, then set the irq.
>>
>> I think how to calculate the hart number is the task of PLIC and it can
>> make use of "hartid-base"
>> to calculate it.
>>
>> Thanks,
>> Zhiwei
>>
>
> Hi Zhiwei,
>
> What I mean is if there are multiple CLIC instances, each per core (CLIC
> spec allows that).
> If you try to bind CLIC with CPU index start from 0,
> it will be impossible for CLIC instance to bind CPU from index other than
> 0.
>
> For example, for 4 cores system, it's possible to have 4 CLIC instances:
>   * CLIC 0 binds to CPU 0
>   * CLIC 1 binds to CPU 1
>   * CLIC 2 binds to CPU 2
>   * CLIC 3 binds to CPU 3
>
> and that's why I said it's possible to pass an extra "hartid-base" just
> like PLIC.
> I know most of hardid are calculated by the requesing address, so most
> hartid usages should be fine.
> But I saw two places using qemu_get_cpu(),
>
> which may cause the problem for the scenario I describe above:
> i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my original
> reply.
>
> So what's the problem here?
>
> Currently all cores share the same CLIC instance. Do you want to give each
> core  a CLIC instance?
>
Yes, that's what I mean, which should be supported as what spec says[1]:
  The CLIC complements the PLIC. Smaller single-core systems might have
only a CLIC,
  while multicore systems might have *a CLIC per-core* and a single shared
PLIC.
  The PLIC xeip signals are treated as hart-local interrupt sources by the
CLIC at each core.

[1]
https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic

Thanks,
Frank Chang


>
> Thanks,
> Zhiwei
>
Regards,
> Frank Chang
>
>
>> However if you try to bind CPU reference start from index i = 0.
>> It's not possible for each per-core CLIC to bind their own CPU instance
>> in multicore system
>> as they have to bind from CPU 0.
>>
>> I'm not sure if we add a new "hartid-base" property just like what SiFive
>> PLIC is
>> implemented would be a good idea or not.
>>
>>
>> Regards,
>> Frank Chang
>>
>>
>>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>> +                                         &cpu->env, 1);
>>> +        qdev_connect_gpio_out(dev, i, irq);
>>> +        cpu->env.clic = clic;
>>> +    }
>>> +}
>>> +
>>>
>>>
>>>
LIU Zhiwei June 28, 2021, 7:39 a.m. UTC | #14
On 2021/6/28 下午3:23, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年6月28日 週一 下午3:17寫道:
>
>
>     On 2021/6/26 下午8:56, Frank Chang wrote:
>>     On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com
>>     <mailto:zhiwei_liu@c-sky.com>> wrote:
>>
>>
>>         On 6/13/21 6:10 PM, Frank Chang wrote:
>>>         LIU Zhiwei <zhiwei_liu@c-sky.com
>>>         <mailto:zhiwei_liu@c-sky.com>> 於 2021年4月9日 週五
>>>         下午3:57寫道:
>>>
>>>             +static void riscv_clic_realize(DeviceState *dev, Error
>>>             **errp)
>>>             +{
>>>             +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>             +    size_t harts_x_sources = clic->num_harts *
>>>             clic->num_sources;
>>>             +    int irqs, i;
>>>             +
>>>             +    if (clic->prv_s && clic->prv_u) {
>>>             +        irqs = 3 * harts_x_sources;
>>>             +    } else if (clic->prv_s || clic->prv_u) {
>>>             +        irqs = 2 * harts_x_sources;
>>>             +    } else {
>>>             +        irqs = harts_x_sources;
>>>             +    }
>>>             +
>>>             +    clic->clic_size = irqs * 4 + 0x1000;
>>>             + memory_region_init_io(&clic->mmio, OBJECT(dev),
>>>             &riscv_clic_ops, clic,
>>>             + TYPE_RISCV_CLIC, clic->clic_size);
>>>             +
>>>             +    clic->clicintip = g_new0(uint8_t, irqs);
>>>             +    clic->clicintie = g_new0(uint8_t, irqs);
>>>             +    clic->clicintattr = g_new0(uint8_t, irqs);
>>>             +    clic->clicintctl = g_new0(uint8_t, irqs);
>>>             +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>>             +    clic->active_count = g_new0(size_t, clic->num_harts);
>>>             +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>>             +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>>             + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>>             +
>>>             +    /* Allocate irq through gpio, so that we can use
>>>             qtest */
>>>             +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>>             +    qdev_init_gpio_out(dev, clic->cpu_irqs,
>>>             clic->num_harts);
>>>             +
>>>             +    for (i = 0; i < clic->num_harts; i++) {
>>>             +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>
>>>
>>>         As spec says:
>>>           Smaller single-core systems might have only a CLIC,
>>>           while multicore systems might have a CLIC per-core and a
>>>         single shared PLIC.
>>>           The PLIC xeip signals are treated as hart-local interrupt
>>>         sources by the CLIC at each core.
>>>
>>>         It looks like it's possible to have one CLIC instance per core.
>>
>>         If you want to delivery an interrupt to one hart, you should
>>         encode the IRQ by the interrupt number
>>         , the hart number and the interrupt target privilege, then
>>         set the irq.
>>
>>         I think how to calculate the hart number is the task of PLIC
>>         and it can make use of "hartid-base"
>>         to calculate it.
>>
>>         Thanks,
>>         Zhiwei
>>
>>
>>     Hi Zhiwei,
>>
>>     What I mean is if there are multiple CLIC instances, each per
>>     core (CLIC spec allows that).
>>     If you try to bind CLIC with CPU index start from 0,
>>     it will be impossible for CLIC instance to bind CPU from index
>>     other than 0.
>>
>>     For example, for 4 cores system, it's possible to have 4 CLIC
>>     instances:
>>       * CLIC 0 binds to CPU 0
>>       * CLIC 1 binds to CPU 1
>>       * CLIC 2 binds to CPU 2
>>       * CLIC 3 binds to CPU 3
>>
>>     and that's why I said it's possible to pass an extra
>>     "hartid-base" just like PLIC.
>>     I know most of hardid are calculated by the requesing address, so
>>     most hartid usages should be fine.
>>     But I saw two places using qemu_get_cpu(),
>>     which may cause the problem for the scenario I describe above:
>>     i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my
>>     original reply.
>
>     So what's the problem here?
>
>     Currently all cores share the same CLIC instance. Do you want to
>     give each core  a CLIC instance?
>
> Yes, that's what I mean, which should be supported as what spec says[1]:
>   The CLIC complements the PLIC. Smaller single-core systems might 
> have only a CLIC,
>   while multicore systems might have *a CLIC per-core* and a single 
> shared PLIC.
>   The PLIC xeip signals are treated as hart-local interrupt sources by 
> the CLIC at each core.
>
> [1] 
> https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic 
> <https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic>
>
> Thanks,
> Frank Chang

If we give each core a CLIC instance, it is not convenient to access the 
shared memory, such as 0x0-0x1000.
Which CLIC instance should contain this memory region?

Thanks,
Zhiwei

>
>     Thanks,
>     Zhiwei
>
>>     Regards,
>>     Frank Chang
>>
>>>         However if you try to bind CPU reference start from index i = 0.
>>>         It's not possible for each per-core CLIC to bind their own
>>>         CPU instance in multicore system
>>>         as they have to bind from CPU 0.
>>>
>>>         I'm not sure if we add a new "hartid-base" property just
>>>         like what SiFive PLIC is
>>>         implemented would be a good idea or not.
>>>
>>>
>>>         Regards,
>>>         Frank Chang
>>>
>>>             +     qemu_irq irq =
>>>             qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>             +  &cpu->env, 1);
>>>             +        qdev_connect_gpio_out(dev, i, irq);
>>>             +        cpu->env.clic = clic;
>>>             +    }
>>>             +}
>>>             +
>>>
>>>
Frank Chang June 28, 2021, 7:49 a.m. UTC | #15
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:40寫道:

>
> On 2021/6/28 下午3:23, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:17寫道:
>
>>
>> On 2021/6/26 下午8:56, Frank Chang wrote:
>>
>> On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com> wrote:
>>
>>>
>>> On 6/13/21 6:10 PM, Frank Chang wrote:
>>>
>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>>>
>>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>>> +{
>>>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>>>> +    int irqs, i;
>>>> +
>>>> +    if (clic->prv_s && clic->prv_u) {
>>>> +        irqs = 3 * harts_x_sources;
>>>> +    } else if (clic->prv_s || clic->prv_u) {
>>>> +        irqs = 2 * harts_x_sources;
>>>> +    } else {
>>>> +        irqs = harts_x_sources;
>>>> +    }
>>>> +
>>>> +    clic->clic_size = irqs * 4 + 0x1000;
>>>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>>>> clic,
>>>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>>>> +
>>>> +    clic->clicintip = g_new0(uint8_t, irqs);
>>>> +    clic->clicintie = g_new0(uint8_t, irqs);
>>>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>>>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>>>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>>>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>>> +
>>>> +    /* Allocate irq through gpio, so that we can use qtest */
>>>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>>> +
>>>> +    for (i = 0; i < clic->num_harts; i++) {
>>>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>>
>>>
>>> As spec says:
>>>   Smaller single-core systems might have only a CLIC,
>>>   while multicore systems might have a CLIC per-core and a single shared
>>> PLIC.
>>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>>> the CLIC at each core.
>>>
>>> It looks like it's possible to have one CLIC instance per core.
>>>
>>> If you want to delivery an interrupt to one hart, you should encode the
>>> IRQ by the interrupt number
>>> , the hart number and the interrupt target privilege, then set the irq.
>>>
>>> I think how to calculate the hart number is the task of PLIC and it can
>>> make use of "hartid-base"
>>> to calculate it.
>>>
>>> Thanks,
>>> Zhiwei
>>>
>>
>> Hi Zhiwei,
>>
>> What I mean is if there are multiple CLIC instances, each per core (CLIC
>> spec allows that).
>> If you try to bind CLIC with CPU index start from 0,
>> it will be impossible for CLIC instance to bind CPU from index other than
>> 0.
>>
>> For example, for 4 cores system, it's possible to have 4 CLIC instances:
>>   * CLIC 0 binds to CPU 0
>>   * CLIC 1 binds to CPU 1
>>   * CLIC 2 binds to CPU 2
>>   * CLIC 3 binds to CPU 3
>>
>> and that's why I said it's possible to pass an extra "hartid-base" just
>> like PLIC.
>> I know most of hardid are calculated by the requesing address, so most
>> hartid usages should be fine.
>> But I saw two places using qemu_get_cpu(),
>>
>> which may cause the problem for the scenario I describe above:
>> i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my original
>> reply.
>>
>> So what's the problem here?
>>
>> Currently all cores share the same CLIC instance. Do you want to give
>> each core  a CLIC instance?
>>
> Yes, that's what I mean, which should be supported as what spec says[1]:
>   The CLIC complements the PLIC. Smaller single-core systems might have
> only a CLIC,
>   while multicore systems might have *a CLIC per-core* and a single
> shared PLIC.
>   The PLIC xeip signals are treated as hart-local interrupt sources by the
> CLIC at each core.
>
> [1]
> https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>
> Thanks,
> Frank Chang
>
>
> If we give each core a CLIC instance, it is not convenient to access the
> shared memory, such as 0x0-0x1000.
> Which CLIC instance should contain this memory region?
>
What do you mean by: "access the shared memory" here?
I thought the memory region is defined during CLIC's creation?
So it should depend on the platform that creates CLIC instances.

Thanks,
Frank Chang


> Thanks,
> Zhiwei
>
>
>> Thanks,
>> Zhiwei
>>
> Regards,
>> Frank Chang
>>
>>
>>> However if you try to bind CPU reference start from index i = 0.
>>> It's not possible for each per-core CLIC to bind their own CPU instance
>>> in multicore system
>>> as they have to bind from CPU 0.
>>>
>>> I'm not sure if we add a new "hartid-base" property just like what
>>> SiFive PLIC is
>>> implemented would be a good idea or not.
>>>
>>>
>>> Regards,
>>> Frank Chang
>>>
>>>
>>>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>> +                                         &cpu->env, 1);
>>>> +        qdev_connect_gpio_out(dev, i, irq);
>>>> +        cpu->env.clic = clic;
>>>> +    }
>>>> +}
>>>> +
>>>>
>>>>
>>>>
LIU Zhiwei June 28, 2021, 8:01 a.m. UTC | #16
On 2021/6/28 下午3:49, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年6月28日 週一 下午3:40寫道:
>
>
>     On 2021/6/28 下午3:23, Frank Chang wrote:
>>     LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於
>>     2021年6月28日 週一 下午3:17寫道:
>>
>>
>>         On 2021/6/26 下午8:56, Frank Chang wrote:
>>>         On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei
>>>         <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> wrote:
>>>
>>>
>>>             On 6/13/21 6:10 PM, Frank Chang wrote:
>>>>             LIU Zhiwei <zhiwei_liu@c-sky.com
>>>>             <mailto:zhiwei_liu@c-sky.com>> 於 2021年4月9日 週五
>>>>             下午3:57寫道:
>>>>
>>>>                 +static void riscv_clic_realize(DeviceState *dev,
>>>>                 Error **errp)
>>>>                 +{
>>>>                 +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>>                 +    size_t harts_x_sources = clic->num_harts *
>>>>                 clic->num_sources;
>>>>                 +    int irqs, i;
>>>>                 +
>>>>                 +    if (clic->prv_s && clic->prv_u) {
>>>>                 +        irqs = 3 * harts_x_sources;
>>>>                 +    } else if (clic->prv_s || clic->prv_u) {
>>>>                 +        irqs = 2 * harts_x_sources;
>>>>                 +    } else {
>>>>                 +        irqs = harts_x_sources;
>>>>                 +    }
>>>>                 +
>>>>                 +    clic->clic_size = irqs * 4 + 0x1000;
>>>>                 + memory_region_init_io(&clic->mmio, OBJECT(dev),
>>>>                 &riscv_clic_ops, clic,
>>>>                 + TYPE_RISCV_CLIC, clic->clic_size);
>>>>                 +
>>>>                 +    clic->clicintip = g_new0(uint8_t, irqs);
>>>>                 +    clic->clicintie = g_new0(uint8_t, irqs);
>>>>                 +    clic->clicintattr = g_new0(uint8_t, irqs);
>>>>                 +    clic->clicintctl = g_new0(uint8_t, irqs);
>>>>                 +    clic->active_list =
>>>>                 g_new0(CLICActiveInterrupt, irqs);
>>>>                 +    clic->active_count = g_new0(size_t,
>>>>                 clic->num_harts);
>>>>                 +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>>>                 +    clic->cpu_irqs = g_new0(qemu_irq,
>>>>                 clic->num_harts);
>>>>                 + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>>>                 +
>>>>                 +    /* Allocate irq through gpio, so that we can
>>>>                 use qtest */
>>>>                 +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>>>                 +    qdev_init_gpio_out(dev, clic->cpu_irqs,
>>>>                 clic->num_harts);
>>>>                 +
>>>>                 +    for (i = 0; i < clic->num_harts; i++) {
>>>>                 +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>>
>>>>
>>>>             As spec says:
>>>>               Smaller single-core systems might have only a CLIC,
>>>>               while multicore systems might have a CLIC per-core
>>>>             and a single shared PLIC.
>>>>               The PLIC xeip signals are treated as hart-local
>>>>             interrupt sources by the CLIC at each core.
>>>>
>>>>             It looks like it's possible to have one CLIC instance
>>>>             per core.
>>>
>>>             If you want to delivery an interrupt to one hart, you
>>>             should encode the IRQ by the interrupt number
>>>             , the hart number and the interrupt target privilege,
>>>             then set the irq.
>>>
>>>             I think how to calculate the hart number is the task of
>>>             PLIC and it can make use of "hartid-base"
>>>             to calculate it.
>>>
>>>             Thanks,
>>>             Zhiwei
>>>
>>>
>>>         Hi Zhiwei,
>>>
>>>         What I mean is if there are multiple CLIC instances, each
>>>         per core (CLIC spec allows that).
>>>         If you try to bind CLIC with CPU index start from 0,
>>>         it will be impossible for CLIC instance to bind CPU from
>>>         index other than 0.
>>>
>>>         For example, for 4 cores system, it's possible to have 4
>>>         CLIC instances:
>>>           * CLIC 0 binds to CPU 0
>>>           * CLIC 1 binds to CPU 1
>>>           * CLIC 2 binds to CPU 2
>>>           * CLIC 3 binds to CPU 3
>>>
>>>         and that's why I said it's possible to pass an extra
>>>         "hartid-base" just like PLIC.
>>>         I know most of hardid are calculated by the requesing
>>>         address, so most hartid usages should be fine.
>>>         But I saw two places using qemu_get_cpu(),
>>>         which may cause the problem for the scenario I describe above:
>>>         i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as
>>>         my original reply.
>>
>>         So what's the problem here?
>>
>>         Currently all cores share the same CLIC instance. Do you want
>>         to give each core  a CLIC instance?
>>
>>     Yes, that's what I mean, which should be supported as what spec
>>     says[1]:
>>       The CLIC complements the PLIC. Smaller single-core systems
>>     might have only a CLIC,
>>       while multicore systems might have *a CLIC per-core* and a
>>     single shared PLIC.
>>       The PLIC xeip signals are treated as hart-local interrupt
>>     sources by the CLIC at each core.
>>
>>     [1]
>>     https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>>     <https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic>
>>
>>     Thanks,
>>     Frank Chang
>
>     If we give each core a CLIC instance, it is not convenient to
>     access the shared memory, such as 0x0-0x1000.
>     Which CLIC instance should contain this memory region?
>
> What do you mean by: "access the shared memory" here?

It means the cliccfg or clicinfo which  should be shared by all CLIC 
instances.

Thanks,
Zhiwei

> I thought the memory region is defined during CLIC's creation?
> So it should depend on the platform that creates CLIC instances.
>
> Thanks,
> Frank Chang
>
>     Thanks,
>     Zhiwei
>
>>
>>         Thanks,
>>         Zhiwei
>>
>>>         Regards,
>>>         Frank Chang
>>>
>>>>             However if you try to bind CPU reference start from
>>>>             index i = 0.
>>>>             It's not possible for each per-core CLIC to bind their
>>>>             own CPU instance in multicore system
>>>>             as they have to bind from CPU 0.
>>>>
>>>>             I'm not sure if we add a new "hartid-base" property
>>>>             just like what SiFive PLIC is
>>>>             implemented would be a good idea or not.
>>>>
>>>>
>>>>             Regards,
>>>>             Frank Chang
>>>>
>>>>                 +        qemu_irq irq =
>>>>                 qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>>                 +            &cpu->env, 1);
>>>>                 + qdev_connect_gpio_out(dev, i, irq);
>>>>                 +        cpu->env.clic = clic;
>>>>                 +    }
>>>>                 +}
>>>>                 +
>>>>
>>>>
Frank Chang June 28, 2021, 8:07 a.m. UTC | #17
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午4:03寫道:

>
> On 2021/6/28 下午3:49, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:40寫道:
>
>>
>> On 2021/6/28 下午3:23, Frank Chang wrote:
>>
>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:17寫道:
>>
>>>
>>> On 2021/6/26 下午8:56, Frank Chang wrote:
>>>
>>> On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com>
>>> wrote:
>>>
>>>>
>>>> On 6/13/21 6:10 PM, Frank Chang wrote:
>>>>
>>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>>>>
>>>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>>>> +{
>>>>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>>>>> +    int irqs, i;
>>>>> +
>>>>> +    if (clic->prv_s && clic->prv_u) {
>>>>> +        irqs = 3 * harts_x_sources;
>>>>> +    } else if (clic->prv_s || clic->prv_u) {
>>>>> +        irqs = 2 * harts_x_sources;
>>>>> +    } else {
>>>>> +        irqs = harts_x_sources;
>>>>> +    }
>>>>> +
>>>>> +    clic->clic_size = irqs * 4 + 0x1000;
>>>>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>>>>> clic,
>>>>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>>>>> +
>>>>> +    clic->clicintip = g_new0(uint8_t, irqs);
>>>>> +    clic->clicintie = g_new0(uint8_t, irqs);
>>>>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>>>>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>>>>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>>>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>>>>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>>>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>>>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>>>> +
>>>>> +    /* Allocate irq through gpio, so that we can use qtest */
>>>>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>>>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>>>> +
>>>>> +    for (i = 0; i < clic->num_harts; i++) {
>>>>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>>>
>>>>
>>>> As spec says:
>>>>   Smaller single-core systems might have only a CLIC,
>>>>   while multicore systems might have a CLIC per-core and a single
>>>> shared PLIC.
>>>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>>>> the CLIC at each core.
>>>>
>>>> It looks like it's possible to have one CLIC instance per core.
>>>>
>>>> If you want to delivery an interrupt to one hart, you should encode the
>>>> IRQ by the interrupt number
>>>> , the hart number and the interrupt target privilege, then set the irq.
>>>>
>>>> I think how to calculate the hart number is the task of PLIC and it can
>>>> make use of "hartid-base"
>>>> to calculate it.
>>>>
>>>> Thanks,
>>>> Zhiwei
>>>>
>>>
>>> Hi Zhiwei,
>>>
>>> What I mean is if there are multiple CLIC instances, each per core (CLIC
>>> spec allows that).
>>> If you try to bind CLIC with CPU index start from 0,
>>> it will be impossible for CLIC instance to bind CPU from index other
>>> than 0.
>>>
>>> For example, for 4 cores system, it's possible to have 4 CLIC instances:
>>>   * CLIC 0 binds to CPU 0
>>>   * CLIC 1 binds to CPU 1
>>>   * CLIC 2 binds to CPU 2
>>>   * CLIC 3 binds to CPU 3
>>>
>>> and that's why I said it's possible to pass an extra "hartid-base" just
>>> like PLIC.
>>> I know most of hardid are calculated by the requesing address, so most
>>> hartid usages should be fine.
>>> But I saw two places using qemu_get_cpu(),
>>>
>>> which may cause the problem for the scenario I describe above:
>>> i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my original
>>> reply.
>>>
>>> So what's the problem here?
>>>
>>> Currently all cores share the same CLIC instance. Do you want to give
>>> each core  a CLIC instance?
>>>
>> Yes, that's what I mean, which should be supported as what spec says[1]:
>>   The CLIC complements the PLIC. Smaller single-core systems might have
>> only a CLIC,
>>   while multicore systems might have *a CLIC per-core* and a single
>> shared PLIC.
>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>> the CLIC at each core.
>>
>> [1]
>> https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>>
>> Thanks,
>> Frank Chang
>>
>>
>> If we give each core a CLIC instance, it is not convenient to access the
>> shared memory, such as 0x0-0x1000.
>> Which CLIC instance should contain this memory region?
>>
> What do you mean by: "access the shared memory" here?
>
> It means the cliccfg or clicinfo which  should be shared by all CLIC
> instances.
>
If there are multiple CLIC instances, shouldn't they have their own base
addresses?
So I do not understand how cliccfg and clicinfo would be shared by all CLIC
instances. (Or they should?)
Each CLIC instance will manage its own cliccfg and clicinfo.

Thanks,
Frank Chang

Thanks,
> Zhiwei
>
> I thought the memory region is defined during CLIC's creation?
> So it should depend on the platform that creates CLIC instances.
>
> Thanks,
> Frank Chang
>
>
>> Thanks,
>> Zhiwei
>>
>>
>>> Thanks,
>>> Zhiwei
>>>
>> Regards,
>>> Frank Chang
>>>
>>>
>>>> However if you try to bind CPU reference start from index i = 0.
>>>> It's not possible for each per-core CLIC to bind their own CPU instance
>>>> in multicore system
>>>> as they have to bind from CPU 0.
>>>>
>>>> I'm not sure if we add a new "hartid-base" property just like what
>>>> SiFive PLIC is
>>>> implemented would be a good idea or not.
>>>>
>>>>
>>>> Regards,
>>>> Frank Chang
>>>>
>>>>
>>>>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>>> +                                         &cpu->env, 1);
>>>>> +        qdev_connect_gpio_out(dev, i, irq);
>>>>> +        cpu->env.clic = clic;
>>>>> +    }
>>>>> +}
>>>>> +
>>>>>
>>>>>
>>>>>
LIU Zhiwei June 28, 2021, 8:11 a.m. UTC | #18
On 2021/6/28 下午4:07, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年6月28日 週一 下午4:03寫道:
>
>
>     On 2021/6/28 下午3:49, Frank Chang wrote:
>>     LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於
>>     2021年6月28日 週一 下午3:40寫道:
>>
>>
>>         On 2021/6/28 下午3:23, Frank Chang wrote:
>>>         LIU Zhiwei <zhiwei_liu@c-sky.com
>>>         <mailto:zhiwei_liu@c-sky.com>> 於 2021年6月28日 週一
>>>         下午3:17寫道:
>>>
>>>
>>>             On 2021/6/26 下午8:56, Frank Chang wrote:
>>>>             On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei
>>>>             <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> wrote:
>>>>
>>>>
>>>>                 On 6/13/21 6:10 PM, Frank Chang wrote:
>>>>>                 LIU Zhiwei <zhiwei_liu@c-sky.com
>>>>>                 <mailto:zhiwei_liu@c-sky.com>> 於 2021年4月9日 週五
>>>>>                 下午3:57寫道:
>>>>>
>>>>>                     +static void riscv_clic_realize(DeviceState
>>>>>                     *dev, Error **errp)
>>>>>                     +{
>>>>>                     +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>>>                     +    size_t harts_x_sources = clic->num_harts
>>>>>                     * clic->num_sources;
>>>>>                     +    int irqs, i;
>>>>>                     +
>>>>>                     +    if (clic->prv_s && clic->prv_u) {
>>>>>                     +        irqs = 3 * harts_x_sources;
>>>>>                     +    } else if (clic->prv_s || clic->prv_u) {
>>>>>                     +        irqs = 2 * harts_x_sources;
>>>>>                     +    } else {
>>>>>                     +        irqs = harts_x_sources;
>>>>>                     +    }
>>>>>                     +
>>>>>                     + clic->clic_size = irqs * 4 + 0x1000;
>>>>>                     + memory_region_init_io(&clic->mmio,
>>>>>                     OBJECT(dev), &riscv_clic_ops, clic,
>>>>>                     + TYPE_RISCV_CLIC, clic->clic_size);
>>>>>                     +
>>>>>                     + clic->clicintip = g_new0(uint8_t, irqs);
>>>>>                     + clic->clicintie = g_new0(uint8_t, irqs);
>>>>>                     + clic->clicintattr = g_new0(uint8_t, irqs);
>>>>>                     + clic->clicintctl = g_new0(uint8_t, irqs);
>>>>>                     + clic->active_list =
>>>>>                     g_new0(CLICActiveInterrupt, irqs);
>>>>>                     + clic->active_count = g_new0(size_t,
>>>>>                     clic->num_harts);
>>>>>                     + clic->exccode = g_new0(uint32_t,
>>>>>                     clic->num_harts);
>>>>>                     + clic->cpu_irqs = g_new0(qemu_irq,
>>>>>                     clic->num_harts);
>>>>>                     + sysbus_init_mmio(SYS_BUS_DEVICE(dev),
>>>>>                     &clic->mmio);
>>>>>                     +
>>>>>                     +    /* Allocate irq through gpio, so that we
>>>>>                     can use qtest */
>>>>>                     + qdev_init_gpio_in(dev, riscv_clic_set_irq,
>>>>>                     irqs);
>>>>>                     + qdev_init_gpio_out(dev, clic->cpu_irqs,
>>>>>                     clic->num_harts);
>>>>>                     +
>>>>>                     +    for (i = 0; i < clic->num_harts; i++) {
>>>>>                     +        RISCVCPU *cpu =
>>>>>                     RISCV_CPU(qemu_get_cpu(i));
>>>>>
>>>>>
>>>>>                 As spec says:
>>>>>                   Smaller single-core systems might have only a CLIC,
>>>>>                   while multicore systems might have a CLIC
>>>>>                 per-core and a single shared PLIC.
>>>>>                   The PLIC xeip signals are treated as hart-local
>>>>>                 interrupt sources by the CLIC at each core.
>>>>>
>>>>>                 It looks like it's possible to have one CLIC
>>>>>                 instance per core.
>>>>
>>>>                 If you want to delivery an interrupt to one hart,
>>>>                 you should encode the IRQ by the interrupt number
>>>>                 , the hart number and the interrupt target
>>>>                 privilege, then set the irq.
>>>>
>>>>                 I think how to calculate the hart number is the
>>>>                 task of PLIC and it can make use of "hartid-base"
>>>>                 to calculate it.
>>>>
>>>>                 Thanks,
>>>>                 Zhiwei
>>>>
>>>>
>>>>             Hi Zhiwei,
>>>>
>>>>             What I mean is if there are multiple CLIC instances,
>>>>             each per core (CLIC spec allows that).
>>>>             If you try to bind CLIC with CPU index start from 0,
>>>>             it will be impossible for CLIC instance to bind CPU
>>>>             from index other than 0.
>>>>
>>>>             For example, for 4 cores system, it's possible to have
>>>>             4 CLIC instances:
>>>>               * CLIC 0 binds to CPU 0
>>>>               * CLIC 1 binds to CPU 1
>>>>               * CLIC 2 binds to CPU 2
>>>>               * CLIC 3 binds to CPU 3
>>>>
>>>>             and that's why I said it's possible to pass an extra
>>>>             "hartid-base" just like PLIC.
>>>>             I know most of hardid are calculated by the requesing
>>>>             address, so most hartid usages should be fine.
>>>>             But I saw two places using qemu_get_cpu(),
>>>>             which may cause the problem for the scenario I describe
>>>>             above:
>>>>             i.e. riscv_clic_next_interrupt() and
>>>>             riscv_clic_realize() as my original reply.
>>>
>>>             So what's the problem here?
>>>
>>>             Currently all cores share the same CLIC instance. Do you
>>>             want to give each core  a CLIC instance?
>>>
>>>         Yes, that's what I mean, which should be supported as what
>>>         spec says[1]:
>>>           The CLIC complements the PLIC. Smaller single-core systems
>>>         might have only a CLIC,
>>>           while multicore systems might have *a CLIC per-core* and a
>>>         single shared PLIC.
>>>           The PLIC xeip signals are treated as hart-local interrupt
>>>         sources by the CLIC at each core.
>>>
>>>         [1]
>>>         https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>>>         <https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic>
>>>
>>>         Thanks,
>>>         Frank Chang
>>
>>         If we give each core a CLIC instance, it is not convenient to
>>         access the shared memory, such as 0x0-0x1000.
>>         Which CLIC instance should contain this memory region?
>>
>>     What do you mean by: "access the shared memory" here?
>
>     It means the cliccfg or clicinfo which  should be shared by all
>     CLIC instances.
>
> If there are multiple CLIC instances, shouldn't they have their own 
> base addresses?
> So I do not understand how cliccfg and clicinfo would be shared by all 
> CLIC instances. (Or they should?)

Once we have a talk on tech-fast-interrupt. The chair of fast interrupt 
reply is:

/"The first part (address 0x0000-0x0FFF) which contains 
cliccfg/clicinfo/clicinttrig should be global since only one copy of the 
configuration setting is enough.//
//On the other hand, the latter part (0x1000-0x4FFF) which contains 
control bits for individual interrupt should be one copy per hart"/

Thanks,
Zhiwei

> Each CLIC instance will manage its own cliccfg and clicinfo.
>
> Thanks,
> Frank Chang
>
>     Thanks,
>     Zhiwei
>
>>     I thought the memory region is defined during CLIC's creation?
>>     So it should depend on the platform that creates CLIC instances.
>>
>>     Thanks,
>>     Frank Chang
>>
>>         Thanks,
>>         Zhiwei
>>
>>>
>>>             Thanks,
>>>             Zhiwei
>>>
>>>>             Regards,
>>>>             Frank Chang
>>>>
>>>>>                 However if you try to bind CPU reference start
>>>>>                 from index i = 0.
>>>>>                 It's not possible for each per-core CLIC to bind
>>>>>                 their own CPU instance in multicore system
>>>>>                 as they have to bind from CPU 0.
>>>>>
>>>>>                 I'm not sure if we add a new "hartid-base"
>>>>>                 property just like what SiFive PLIC is
>>>>>                 implemented would be a good idea or not.
>>>>>
>>>>>
>>>>>                 Regards,
>>>>>                 Frank Chang
>>>>>
>>>>>                     +        qemu_irq irq =
>>>>>                     qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>>>                     +  &cpu->env, 1);
>>>>>                     + qdev_connect_gpio_out(dev, i, irq);
>>>>>                     + cpu->env.clic = clic;
>>>>>                     +    }
>>>>>                     +}
>>>>>                     +
>>>>>
>>>>>
Frank Chang June 28, 2021, 8:19 a.m. UTC | #19
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午4:12寫道:

>
> On 2021/6/28 下午4:07, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午4:03寫道:
>
>>
>> On 2021/6/28 下午3:49, Frank Chang wrote:
>>
>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:40寫道:
>>
>>>
>>> On 2021/6/28 下午3:23, Frank Chang wrote:
>>>
>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:17寫道:
>>>
>>>>
>>>> On 2021/6/26 下午8:56, Frank Chang wrote:
>>>>
>>>> On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com>
>>>> wrote:
>>>>
>>>>>
>>>>> On 6/13/21 6:10 PM, Frank Chang wrote:
>>>>>
>>>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>>>>>
>>>>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>>>>> +{
>>>>>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>>>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>>>>>> +    int irqs, i;
>>>>>> +
>>>>>> +    if (clic->prv_s && clic->prv_u) {
>>>>>> +        irqs = 3 * harts_x_sources;
>>>>>> +    } else if (clic->prv_s || clic->prv_u) {
>>>>>> +        irqs = 2 * harts_x_sources;
>>>>>> +    } else {
>>>>>> +        irqs = harts_x_sources;
>>>>>> +    }
>>>>>> +
>>>>>> +    clic->clic_size = irqs * 4 + 0x1000;
>>>>>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>>>>>> clic,
>>>>>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>>>>>> +
>>>>>> +    clic->clicintip = g_new0(uint8_t, irqs);
>>>>>> +    clic->clicintie = g_new0(uint8_t, irqs);
>>>>>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>>>>>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>>>>>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>>>>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>>>>>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>>>>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>>>>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>>>>> +
>>>>>> +    /* Allocate irq through gpio, so that we can use qtest */
>>>>>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>>>>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>>>>> +
>>>>>> +    for (i = 0; i < clic->num_harts; i++) {
>>>>>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>>>>
>>>>>
>>>>> As spec says:
>>>>>   Smaller single-core systems might have only a CLIC,
>>>>>   while multicore systems might have a CLIC per-core and a single
>>>>> shared PLIC.
>>>>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>>>>> the CLIC at each core.
>>>>>
>>>>> It looks like it's possible to have one CLIC instance per core.
>>>>>
>>>>> If you want to delivery an interrupt to one hart, you should encode
>>>>> the IRQ by the interrupt number
>>>>> , the hart number and the interrupt target privilege, then set the irq.
>>>>>
>>>>> I think how to calculate the hart number is the task of PLIC and it
>>>>> can make use of "hartid-base"
>>>>> to calculate it.
>>>>>
>>>>> Thanks,
>>>>> Zhiwei
>>>>>
>>>>
>>>> Hi Zhiwei,
>>>>
>>>> What I mean is if there are multiple CLIC instances, each per core
>>>> (CLIC spec allows that).
>>>> If you try to bind CLIC with CPU index start from 0,
>>>> it will be impossible for CLIC instance to bind CPU from index other
>>>> than 0.
>>>>
>>>> For example, for 4 cores system, it's possible to have 4 CLIC instances:
>>>>   * CLIC 0 binds to CPU 0
>>>>   * CLIC 1 binds to CPU 1
>>>>   * CLIC 2 binds to CPU 2
>>>>   * CLIC 3 binds to CPU 3
>>>>
>>>> and that's why I said it's possible to pass an extra "hartid-base" just
>>>> like PLIC.
>>>> I know most of hardid are calculated by the requesing address, so most
>>>> hartid usages should be fine.
>>>> But I saw two places using qemu_get_cpu(),
>>>>
>>>> which may cause the problem for the scenario I describe above:
>>>> i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my
>>>> original reply.
>>>>
>>>> So what's the problem here?
>>>>
>>>> Currently all cores share the same CLIC instance. Do you want to give
>>>> each core  a CLIC instance?
>>>>
>>> Yes, that's what I mean, which should be supported as what spec says[1]:
>>>   The CLIC complements the PLIC. Smaller single-core systems might have
>>> only a CLIC,
>>>   while multicore systems might have *a CLIC per-core* and a single
>>> shared PLIC.
>>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>>> the CLIC at each core.
>>>
>>> [1]
>>> https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>>>
>>> Thanks,
>>> Frank Chang
>>>
>>>
>>> If we give each core a CLIC instance, it is not convenient to access the
>>> shared memory, such as 0x0-0x1000.
>>> Which CLIC instance should contain this memory region?
>>>
>> What do you mean by: "access the shared memory" here?
>>
>> It means the cliccfg or clicinfo which  should be shared by all CLIC
>> instances.
>>
> If there are multiple CLIC instances, shouldn't they have their own base
> addresses?
> So I do not understand how cliccfg and clicinfo would be shared by all
> CLIC instances. (Or they should?)
>
> Once we have a talk on tech-fast-interrupt. The chair of fast interrupt
> reply is:
>
> *"The first part (address 0x0000-0x0FFF) which contains
> cliccfg/clicinfo/clicinttrig should be global since only one copy of the
> configuration setting is enough.*
> *On the other hand, the latter part (0x1000-0x4FFF) which contains control
> bits for individual interrupt should be one copy per hart"*
>
Hmm... interesting, that's probably something I have missed.
and they didn't document this statement in the spec :(

But I think this statement has a contradiction against the system with
multi-CLIC instances described in spec.
Does it imply that either:
  * I can only have one CLIC in the system, or
  * All CLIC instances must have the same configuration in the system.

Do you have the link to this statement? I would like to take a look.

Thanks,
Frank Chang


> Thanks,
> Zhiwei
>
> Each CLIC instance will manage its own cliccfg and clicinfo.
>
> Thanks,
> Frank Chang
>
> Thanks,
>> Zhiwei
>>
>> I thought the memory region is defined during CLIC's creation?
>> So it should depend on the platform that creates CLIC instances.
>>
>> Thanks,
>> Frank Chang
>>
>>
>>> Thanks,
>>> Zhiwei
>>>
>>>
>>>> Thanks,
>>>> Zhiwei
>>>>
>>> Regards,
>>>> Frank Chang
>>>>
>>>>
>>>>> However if you try to bind CPU reference start from index i = 0.
>>>>> It's not possible for each per-core CLIC to bind their own CPU
>>>>> instance in multicore system
>>>>> as they have to bind from CPU 0.
>>>>>
>>>>> I'm not sure if we add a new "hartid-base" property just like what
>>>>> SiFive PLIC is
>>>>> implemented would be a good idea or not.
>>>>>
>>>>>
>>>>> Regards,
>>>>> Frank Chang
>>>>>
>>>>>
>>>>>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>>>> +                                         &cpu->env, 1);
>>>>>> +        qdev_connect_gpio_out(dev, i, irq);
>>>>>> +        cpu->env.clic = clic;
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>>
>>>>>>
>>>>>>
LIU Zhiwei June 28, 2021, 8:43 a.m. UTC | #20
On 2021/6/28 下午4:19, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年6月28日 週一 下午4:12寫道:
>
>
>     On 2021/6/28 下午4:07, Frank Chang wrote:
>>     LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於
>>     2021年6月28日 週一 下午4:03寫道:
>>
>>
>>         On 2021/6/28 下午3:49, Frank Chang wrote:
>>>         LIU Zhiwei <zhiwei_liu@c-sky.com
>>>         <mailto:zhiwei_liu@c-sky.com>> 於 2021年6月28日 週一
>>>         下午3:40寫道:
>>>
>>>
>>>             On 2021/6/28 下午3:23, Frank Chang wrote:
>>>>             LIU Zhiwei <zhiwei_liu@c-sky.com
>>>>             <mailto:zhiwei_liu@c-sky.com>> 於 2021年6月28日 週一
>>>>             下午3:17寫道:
>>>>
>>>>
>>>>                 On 2021/6/26 下午8:56, Frank Chang wrote:
>>>>>                 On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei
>>>>>                 <zhiwei_liu@c-sky.com
>>>>>                 <mailto:zhiwei_liu@c-sky.com>> wrote:
>>>>>
>>>>>
>>>>>                     On 6/13/21 6:10 PM, Frank Chang wrote:
>>>>>>                     LIU Zhiwei <zhiwei_liu@c-sky.com
>>>>>>                     <mailto:zhiwei_liu@c-sky.com>> 於 2021年4月9日
>>>>>>                     週五 下午3:57寫道:
>>>>>>
>>>>>>                         +static void
>>>>>>                         riscv_clic_realize(DeviceState *dev,
>>>>>>                         Error **errp)
>>>>>>                         +{
>>>>>>                         + RISCVCLICState *clic = RISCV_CLIC(dev);
>>>>>>                         +    size_t harts_x_sources =
>>>>>>                         clic->num_harts * clic->num_sources;
>>>>>>                         +    int irqs, i;
>>>>>>                         +
>>>>>>                         +    if (clic->prv_s && clic->prv_u) {
>>>>>>                         +        irqs = 3 * harts_x_sources;
>>>>>>                         +    } else if (clic->prv_s || clic->prv_u) {
>>>>>>                         +        irqs = 2 * harts_x_sources;
>>>>>>                         +    } else {
>>>>>>                         +        irqs = harts_x_sources;
>>>>>>                         +    }
>>>>>>                         +
>>>>>>                         + clic->clic_size = irqs * 4 + 0x1000;
>>>>>>                         + memory_region_init_io(&clic->mmio,
>>>>>>                         OBJECT(dev), &riscv_clic_ops, clic,
>>>>>>                         + TYPE_RISCV_CLIC, clic->clic_size);
>>>>>>                         +
>>>>>>                         + clic->clicintip = g_new0(uint8_t, irqs);
>>>>>>                         + clic->clicintie = g_new0(uint8_t, irqs);
>>>>>>                         + clic->clicintattr = g_new0(uint8_t, irqs);
>>>>>>                         + clic->clicintctl = g_new0(uint8_t, irqs);
>>>>>>                         + clic->active_list =
>>>>>>                         g_new0(CLICActiveInterrupt, irqs);
>>>>>>                         + clic->active_count = g_new0(size_t,
>>>>>>                         clic->num_harts);
>>>>>>                         + clic->exccode = g_new0(uint32_t,
>>>>>>                         clic->num_harts);
>>>>>>                         + clic->cpu_irqs = g_new0(qemu_irq,
>>>>>>                         clic->num_harts);
>>>>>>                         + sysbus_init_mmio(SYS_BUS_DEVICE(dev),
>>>>>>                         &clic->mmio);
>>>>>>                         +
>>>>>>                         +    /* Allocate irq through gpio, so
>>>>>>                         that we can use qtest */
>>>>>>                         + qdev_init_gpio_in(dev,
>>>>>>                         riscv_clic_set_irq, irqs);
>>>>>>                         + qdev_init_gpio_out(dev, clic->cpu_irqs,
>>>>>>                         clic->num_harts);
>>>>>>                         +
>>>>>>                         +    for (i = 0; i < clic->num_harts; i++) {
>>>>>>                         + RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>>>>
>>>>>>
>>>>>>                     As spec says:
>>>>>>                       Smaller single-core systems might have only
>>>>>>                     a CLIC,
>>>>>>                       while multicore systems might have a CLIC
>>>>>>                     per-core and a single shared PLIC.
>>>>>>                       The PLIC xeip signals are treated as
>>>>>>                     hart-local interrupt sources by the CLIC at
>>>>>>                     each core.
>>>>>>
>>>>>>                     It looks like it's possible to have one CLIC
>>>>>>                     instance per core.
>>>>>
>>>>>                     If you want to delivery an interrupt to one
>>>>>                     hart, you should encode the IRQ by the
>>>>>                     interrupt number
>>>>>                     , the hart number and the interrupt target
>>>>>                     privilege, then set the irq.
>>>>>
>>>>>                     I think how to calculate the hart number is
>>>>>                     the task of PLIC and it can make use of
>>>>>                     "hartid-base"
>>>>>                     to calculate it.
>>>>>
>>>>>                     Thanks,
>>>>>                     Zhiwei
>>>>>
>>>>>
>>>>>                 Hi Zhiwei,
>>>>>
>>>>>                 What I mean is if there are multiple CLIC
>>>>>                 instances, each per core (CLIC spec allows that).
>>>>>                 If you try to bind CLIC with CPU index start from 0,
>>>>>                 it will be impossible for CLIC instance to bind
>>>>>                 CPU from index other than 0.
>>>>>
>>>>>                 For example, for 4 cores system, it's possible to
>>>>>                 have 4 CLIC instances:
>>>>>                   * CLIC 0 binds to CPU 0
>>>>>                   * CLIC 1 binds to CPU 1
>>>>>                   * CLIC 2 binds to CPU 2
>>>>>                   * CLIC 3 binds to CPU 3
>>>>>
>>>>>                 and that's why I said it's possible to pass an
>>>>>                 extra "hartid-base" just like PLIC.
>>>>>                 I know most of hardid are calculated by the
>>>>>                 requesing address, so most hartid usages should be
>>>>>                 fine.
>>>>>                 But I saw two places using qemu_get_cpu(),
>>>>>                 which may cause the problem for the scenario I
>>>>>                 describe above:
>>>>>                 i.e. riscv_clic_next_interrupt() and
>>>>>                 riscv_clic_realize() as my original reply.
>>>>
>>>>                 So what's the problem here?
>>>>
>>>>                 Currently all cores share the same CLIC instance.
>>>>                 Do you want to give each core  a CLIC instance?
>>>>
>>>>             Yes, that's what I mean, which should be supported as
>>>>             what spec says[1]:
>>>>               The CLIC complements the PLIC. Smaller single-core
>>>>             systems might have only a CLIC,
>>>>               while multicore systems might have *a CLIC per-core*
>>>>             and a single shared PLIC.
>>>>               The PLIC xeip signals are treated as hart-local
>>>>             interrupt sources by the CLIC at each core.
>>>>
>>>>             [1]
>>>>             https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>>>>             <https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic>
>>>>
>>>>             Thanks,
>>>>             Frank Chang
>>>
>>>             If we give each core a CLIC instance, it is not
>>>             convenient to access the shared memory, such as 0x0-0x1000.
>>>             Which CLIC instance should contain this memory region?
>>>
>>>         What do you mean by: "access the shared memory" here?
>>
>>         It means the cliccfg or clicinfo which should be shared by
>>         all CLIC instances.
>>
>>     If there are multiple CLIC instances, shouldn't they have their
>>     own base addresses?
>>     So I do not understand how cliccfg and clicinfo would be shared
>>     by all CLIC instances. (Or they should?)
>
>     Once we have a talk on tech-fast-interrupt. The chair of fast
>     interrupt reply is:
>
>     /"The first part (address 0x0000-0x0FFF) which contains
>     cliccfg/clicinfo/clicinttrig should be global since only one copy
>     of the configuration setting is enough.//
>     //On the other hand, the latter part (0x1000-0x4FFF) which
>     contains control bits for individual interrupt should be one copy
>     per hart"/
>
> Hmm... interesting, that's probably something I have missed.
> and they didn't document this statement in the spec :(
>
> But I think this statement has a contradiction against the system with 
> multi-CLIC instances described in spec.
> Does it imply that either:
>   * I can only have one CLIC in the system, or
>   * All CLIC instances must have the same configuration in the system.

The second one.

I think the CLIC instance here is just on the concept of logic, like the 
current implementation. Furthermore, we can give
every logic CLIC instance a configurable memory region from the machine 
board in the near future.

Thanks,
Zhiwei

> Do you have the link to this statement? I would like to take a look.
>
> Thanks,
> Frank Chang
>
>
>     Thanks,
>     Zhiwei
>
>>     Each CLIC instance will manage its own cliccfg and clicinfo.
>>
>>     Thanks,
>>     Frank Chang
>>
>>         Thanks,
>>         Zhiwei
>>
>>>         I thought the memory region is defined during CLIC's creation?
>>>         So it should depend on the platform that creates CLIC instances.
>>>
>>>         Thanks,
>>>         Frank Chang
>>>
>>>             Thanks,
>>>             Zhiwei
>>>
>>>>
>>>>                 Thanks,
>>>>                 Zhiwei
>>>>
>>>>>                 Regards,
>>>>>                 Frank Chang
>>>>>
>>>>>>                     However if you try to bind CPU reference
>>>>>>                     start from index i = 0.
>>>>>>                     It's not possible for each per-core CLIC to
>>>>>>                     bind their own CPU instance in multicore system
>>>>>>                     as they have to bind from CPU 0.
>>>>>>
>>>>>>                     I'm not sure if we add a new "hartid-base"
>>>>>>                     property just like what SiFive PLIC is
>>>>>>                     implemented would be a good idea or not.
>>>>>>
>>>>>>
>>>>>>                     Regards,
>>>>>>                     Frank Chang
>>>>>>
>>>>>>                         + qemu_irq irq =
>>>>>>                         qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>>>>                         +  &cpu->env, 1);
>>>>>>                         + qdev_connect_gpio_out(dev, i, irq);
>>>>>>                         + cpu->env.clic = clic;
>>>>>>                         +    }
>>>>>>                         +}
>>>>>>                         +
>>>>>>
>>>>>>
Frank Chang June 28, 2021, 9:11 a.m. UTC | #21
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午4:47寫道:

>
> On 2021/6/28 下午4:19, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午4:12寫道:
>
>>
>> On 2021/6/28 下午4:07, Frank Chang wrote:
>>
>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午4:03寫道:
>>
>>>
>>> On 2021/6/28 下午3:49, Frank Chang wrote:
>>>
>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:40寫道:
>>>
>>>>
>>>> On 2021/6/28 下午3:23, Frank Chang wrote:
>>>>
>>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午3:17寫道:
>>>>
>>>>>
>>>>> On 2021/6/26 下午8:56, Frank Chang wrote:
>>>>>
>>>>> On Wed, Jun 16, 2021 at 10:56 AM LIU Zhiwei <zhiwei_liu@c-sky.com>
>>>>> wrote:
>>>>>
>>>>>>
>>>>>> On 6/13/21 6:10 PM, Frank Chang wrote:
>>>>>>
>>>>>> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>>>>>>
>>>>>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>>>>>> +{
>>>>>>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>>>>>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>>>>>>> +    int irqs, i;
>>>>>>> +
>>>>>>> +    if (clic->prv_s && clic->prv_u) {
>>>>>>> +        irqs = 3 * harts_x_sources;
>>>>>>> +    } else if (clic->prv_s || clic->prv_u) {
>>>>>>> +        irqs = 2 * harts_x_sources;
>>>>>>> +    } else {
>>>>>>> +        irqs = harts_x_sources;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    clic->clic_size = irqs * 4 + 0x1000;
>>>>>>> +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>>>>>>> &riscv_clic_ops, clic,
>>>>>>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>>>>>>> +
>>>>>>> +    clic->clicintip = g_new0(uint8_t, irqs);
>>>>>>> +    clic->clicintie = g_new0(uint8_t, irqs);
>>>>>>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>>>>>>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>>>>>>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>>>>>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>>>>>>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>>>>>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>>>>>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>>>>>> +
>>>>>>> +    /* Allocate irq through gpio, so that we can use qtest */
>>>>>>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>>>>>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>>>>>> +
>>>>>>> +    for (i = 0; i < clic->num_harts; i++) {
>>>>>>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>>>>>>
>>>>>>
>>>>>> As spec says:
>>>>>>   Smaller single-core systems might have only a CLIC,
>>>>>>   while multicore systems might have a CLIC per-core and a single
>>>>>> shared PLIC.
>>>>>>   The PLIC xeip signals are treated as hart-local interrupt sources
>>>>>> by the CLIC at each core.
>>>>>>
>>>>>> It looks like it's possible to have one CLIC instance per core.
>>>>>>
>>>>>> If you want to delivery an interrupt to one hart, you should encode
>>>>>> the IRQ by the interrupt number
>>>>>> , the hart number and the interrupt target privilege, then set the
>>>>>> irq.
>>>>>>
>>>>>> I think how to calculate the hart number is the task of PLIC and it
>>>>>> can make use of "hartid-base"
>>>>>> to calculate it.
>>>>>>
>>>>>> Thanks,
>>>>>> Zhiwei
>>>>>>
>>>>>
>>>>> Hi Zhiwei,
>>>>>
>>>>> What I mean is if there are multiple CLIC instances, each per core
>>>>> (CLIC spec allows that).
>>>>> If you try to bind CLIC with CPU index start from 0,
>>>>> it will be impossible for CLIC instance to bind CPU from index other
>>>>> than 0.
>>>>>
>>>>> For example, for 4 cores system, it's possible to have 4 CLIC
>>>>> instances:
>>>>>   * CLIC 0 binds to CPU 0
>>>>>   * CLIC 1 binds to CPU 1
>>>>>   * CLIC 2 binds to CPU 2
>>>>>   * CLIC 3 binds to CPU 3
>>>>>
>>>>> and that's why I said it's possible to pass an extra "hartid-base"
>>>>> just like PLIC.
>>>>> I know most of hardid are calculated by the requesing address, so most
>>>>> hartid usages should be fine.
>>>>> But I saw two places using qemu_get_cpu(),
>>>>>
>>>>> which may cause the problem for the scenario I describe above:
>>>>> i.e. riscv_clic_next_interrupt() and riscv_clic_realize() as my
>>>>> original reply.
>>>>>
>>>>> So what's the problem here?
>>>>>
>>>>> Currently all cores share the same CLIC instance. Do you want to give
>>>>> each core  a CLIC instance?
>>>>>
>>>> Yes, that's what I mean, which should be supported as what spec says[1]:
>>>>   The CLIC complements the PLIC. Smaller single-core systems might have
>>>> only a CLIC,
>>>>   while multicore systems might have *a CLIC per-core* and a single
>>>> shared PLIC.
>>>>   The PLIC xeip signals are treated as hart-local interrupt sources by
>>>> the CLIC at each core.
>>>>
>>>> [1]
>>>> https://github.com/riscv/riscv-fast-interrupt/blob/646310a5e4ae055964b4680f12c1c04a7cc0dd56/clic.adoc#12-clic-versus-plic
>>>>
>>>> Thanks,
>>>> Frank Chang
>>>>
>>>>
>>>> If we give each core a CLIC instance, it is not convenient to access
>>>> the shared memory, such as 0x0-0x1000.
>>>> Which CLIC instance should contain this memory region?
>>>>
>>> What do you mean by: "access the shared memory" here?
>>>
>>> It means the cliccfg or clicinfo which  should be shared by all CLIC
>>> instances.
>>>
>> If there are multiple CLIC instances, shouldn't they have their own base
>> addresses?
>> So I do not understand how cliccfg and clicinfo would be shared by all
>> CLIC instances. (Or they should?)
>>
>> Once we have a talk on tech-fast-interrupt. The chair of fast interrupt
>> reply is:
>>
>> *"The first part (address 0x0000-0x0FFF) which contains
>> cliccfg/clicinfo/clicinttrig should be global since only one copy of the
>> configuration setting is enough.*
>> *On the other hand, the latter part (0x1000-0x4FFF) which contains
>> control bits for individual interrupt should be one copy per hart"*
>>
> Hmm... interesting, that's probably something I have missed.
> and they didn't document this statement in the spec :(
>
> But I think this statement has a contradiction against the system with
> multi-CLIC instances described in spec.
> Does it imply that either:
>   * I can only have one CLIC in the system, or
>   * All CLIC instances must have the same configuration in the system.
>
> The second one.
>
> I think the CLIC instance here is just on the concept of logic,  like the
> current implementation. Furthermore, we can give
> every logic CLIC instance a configurable memory region from the machine
> board in the near future.
>
That's possible, but if the CLIC instance is just the concept of logic.
It also implies that we can only instantiate one CLIC device in the system
at a time.
Which this CLIC instance represents the multi-CLIC instances logically.

Maybe just let us put this aside and leave what the current implementation
is for now.
We can update the implementation in the future when the spec is clearer on
this topic :(

Thanks,
Frank Chang


> Thanks,
> Zhiwei
>
> Do you have the link to this statement? I would like to take a look.
>
> Thanks,
> Frank Chang
>
>
>> Thanks,
>> Zhiwei
>>
>> Each CLIC instance will manage its own cliccfg and clicinfo.
>>
>> Thanks,
>> Frank Chang
>>
>> Thanks,
>>> Zhiwei
>>>
>>> I thought the memory region is defined during CLIC's creation?
>>> So it should depend on the platform that creates CLIC instances.
>>>
>>> Thanks,
>>> Frank Chang
>>>
>>>
>>>> Thanks,
>>>> Zhiwei
>>>>
>>>>
>>>>> Thanks,
>>>>> Zhiwei
>>>>>
>>>> Regards,
>>>>> Frank Chang
>>>>>
>>>>>
>>>>>> However if you try to bind CPU reference start from index i = 0.
>>>>>> It's not possible for each per-core CLIC to bind their own CPU
>>>>>> instance in multicore system
>>>>>> as they have to bind from CPU 0.
>>>>>>
>>>>>> I'm not sure if we add a new "hartid-base" property just like what
>>>>>> SiFive PLIC is
>>>>>> implemented would be a good idea or not.
>>>>>>
>>>>>>
>>>>>> Regards,
>>>>>> Frank Chang
>>>>>>
>>>>>>
>>>>>>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>>>>>> +                                         &cpu->env, 1);
>>>>>>> +        qdev_connect_gpio_out(dev, i, irq);
>>>>>>> +        cpu->env.clic = clic;
>>>>>>> +    }
>>>>>>> +}
>>>>>>> +
>>>>>>>
>>>>>>>
>>>>>>>
Frank Chang June 28, 2021, 10:16 a.m. UTC | #22
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
>

Does the mode here to be checked should be the current hart's privilege mode
at the time register is been accessed?

Regards,
Frank Chang


> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
Frank Chang June 28, 2021, 10:24 a.m. UTC | #23
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
>

Should be: return mode == (clic->clicintattr[irq_offset] >> 6);

Regards,
Frank Chang


> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
LIU Zhiwei June 28, 2021, 10:48 a.m. UTC | #24
On 2021/6/28 下午6:24, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年4月9日 週五 下午3:57寫道:
>
>     The Core-Local Interrupt Controller (CLIC) provides low-latency,
>     vectored, pre-emptive interrupts for RISC-V systems.
>
>     The CLIC also supports a new Selective Hardware Vectoring feature
>     that allow users to optimize each interrupt for either faster
>     response or smaller code size.
>
>     Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com
>     <mailto:zhiwei_liu@c-sky.com>>
>     ---
>      default-configs/devices/riscv32-softmmu.mak |   1 +
>      default-configs/devices/riscv64-softmmu.mak |   1 +
>      hw/intc/Kconfig                             |   3 +
>      hw/intc/meson.build                         |   1 +
>      hw/intc/riscv_clic.c                        | 835
>     ++++++++++++++++++++
>      include/hw/intc/riscv_clic.h                | 103 +++
>      target/riscv/cpu.h                          |   2 +
>      7 files changed, 946 insertions(+)
>      create mode 100644 hw/intc/riscv_clic.c
>      create mode 100644 include/hw/intc/riscv_clic.h
>
>     diff --git a/default-configs/devices/riscv32-softmmu.mak
>     b/default-configs/devices/riscv32-softmmu.mak
>     index d847bd5692..1430c30588 100644
>     --- a/default-configs/devices/riscv32-softmmu.mak
>     +++ b/default-configs/devices/riscv32-softmmu.mak
>     @@ -5,6 +5,7 @@
>      #CONFIG_PCI_DEVICES=n
>      CONFIG_SEMIHOSTING=y
>      CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>     +CONFIG_RISCV_CLIC=y
>
>      # Boards:
>      #
>     diff --git a/default-configs/devices/riscv64-softmmu.mak
>     b/default-configs/devices/riscv64-softmmu.mak
>     index d5eec75f05..396800bbbd 100644
>     --- a/default-configs/devices/riscv64-softmmu.mak
>     +++ b/default-configs/devices/riscv64-softmmu.mak
>     @@ -5,6 +5,7 @@
>      #CONFIG_PCI_DEVICES=n
>      CONFIG_SEMIHOSTING=y
>      CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>     +CONFIG_RISCV_CLIC=y
>
>      # Boards:
>      #
>     diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>     index f4694088a4..5bf492b48f 100644
>     --- a/hw/intc/Kconfig
>     +++ b/hw/intc/Kconfig
>     @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>      config SIFIVE_PLIC
>          bool
>
>     +config RISCV_CLIC
>     +    bool
>     +
>      config GOLDFISH_PIC
>          bool
>
>     diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>     index 1c299039f6..2aa71b6738 100644
>     --- a/hw/intc/meson.build
>     +++ b/hw/intc/meson.build
>     @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM',
>     if_true: files('s390_flic_kvm.c'))
>      specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>      specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>     files('sifive_clint.c'))
>      specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>     files('sifive_plic.c'))
>     +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>     files('riscv_clic.c'))
>      specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>      specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                     if_true: files('xics_kvm.c'))
>     diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>     new file mode 100644
>     index 0000000000..8ad534c506
>     --- /dev/null
>     +++ b/hw/intc/riscv_clic.c
>     @@ -0,0 +1,835 @@
>     +/*
>     + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>     + *
>     + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights
>     reserved.
>     + *
>     + * This program is free software; you can redistribute it and/or
>     modify it
>     + * under the terms and conditions of the GNU General Public License,
>     + * version 2 or later, as published by the Free Software Foundation.
>     + *
>     + * This program is distributed in the hope it will be useful, but
>     WITHOUT
>     + * ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or
>     + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
>     License for
>     + * more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along with
>     + * this program.  If not, see <http://www.gnu.org/licenses/
>     <http://www.gnu.org/licenses/>>.
>     + */
>     +
>     +#include "qemu/osdep.h"
>     +#include "qapi/error.h"
>     +#include "qemu/log.h"
>     +#include "hw/sysbus.h"
>     +#include "sysemu/qtest.h"
>     +#include "target/riscv/cpu.h"
>     +#include "hw/qdev-properties.h"
>     +#include "hw/intc/riscv_clic.h"
>     +
>     +/*
>     + * The 2-bit trig WARL field specifies the trigger type and
>     polarity for each
>     + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>     + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1],
>     is defined as
>     + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section
>     3.6)
>     + */
>     +
>     +static inline TRIG_TYPE
>     +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>     +}
>     +
>     +static inline bool
>     +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>     +}
>     +
>     +static inline bool
>     +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>     +}
>     +
>     +static uint8_t
>     +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>     +{
>     +    int nlbits = clic->nlbits;
>     +
>     +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>     +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>     +    /* unused level bits are set to 1 */
>     +    return (intctl & mask_il) | mask_padding;
>     +}
>     +
>     +static uint8_t
>     +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t
>     intctl)
>     +{
>     +    int npbits = clic->clicintctlbits - clic->nlbits;
>     +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>     +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>     +
>     +    if (npbits < 0) {
>     +        return UINT8_MAX;
>     +    }
>     +    /* unused priority bits are set to 1 */
>     +    return (intctl & mask_priority) | mask_padding;
>     +}
>     +
>     +static void
>     +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>     +                         uint8_t *mode, uint8_t *level,
>     +                         uint8_t *priority)
>     +{
>     +    *mode = intcfg >> 8;
>     +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>     +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg &
>     0xff);
>     +}
>     +
>     +/*
>     + * In a system with multiple harts, the M-mode CLIC regions for
>     all the harts
>     + * are placed contiguously in the memory space, followed by the
>     S-mode CLIC
>     + * regions for all harts. (Section 3.11)
>     + */
>     +static size_t
>     +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t mode_offset = 0;
>     +    size_t unit = clic->num_harts * clic->num_sources;
>     +
>     +    switch (mode) {
>     +    case PRV_M:
>     +        mode_offset = 0;
>     +        break;
>     +    case PRV_S:
>     +        mode_offset = unit;
>     +        break;
>     +    case PRV_U:
>     +        mode_offset = clic->prv_s ? 2 * unit : unit;
>     +        break;
>     +    default:
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid mode %d\n", mode);
>     +        exit(1);
>     +    }
>     +    return mode_offset + hartid * clic->num_sources + irq;
>     +}
>     +
>     +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>     +{
>     +    /*
>     +     * Scan active list for highest priority pending interrupts
>     +     * comparing against this harts mintstatus register and interrupt
>     +     * the core if we have a higher priority interrupt to deliver
>     +     */
>     +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>     +    CPURISCVState *env = &cpu->env;
>     +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>     +
>     +    int il[4] = {
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>     +            clic->mintthresh), /* PRV_U */
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>     +            clic->sintthresh), /* PRV_S */
>     +        0,                     /* reserverd */
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>     +            clic->uintthresh)  /* PRV_M */
>     +    };
>     +
>     +    /* Get sorted list of enabled interrupts for this hart */
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>     +    size_t active_count = clic->active_count[hartid];
>     +    uint8_t mode, level, priority;
>     +
>     +    /* Loop through the enabled interrupts sorted by
>     mode+priority+level */
>     +    while (active_count) {
>     +        size_t irq_offset;
>     +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>     +                                 &priority);
>     +        if (mode < env->priv || (mode == env->priv && level <=
>     il[mode])) {
>     +            /*
>     +             * No pending interrupts with high enough
>     mode+priority+level
>     +             * break and clear pending interrupt for this hart
>     +             */
>     +            break;
>     +        }
>     +        irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, active->irq);
>     +        /* Check pending interrupt with high enough
>     mode+priority+level */
>     +        if (clic->clicintip[irq_offset]) {
>     +            /* Clean vector edge-triggered pending */
>     +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>     +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>     +                clic->clicintip[irq_offset] = 0;
>     +            }
>     +            /* Post pending interrupt for this hart */
>     +            clic->exccode[hartid] = active->irq | mode << 12 |
>     level << 14;
>     +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>     +            return;
>     +        }
>     +        /* Check next enabled interrupt */
>     +        active_count--;
>     +        active++;
>     +    }
>     +}
>     +
>     +/*
>     + * Any interrupt i that is not accessible to S-mode or U-Mode
>     + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>     + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>     + */
>     +static bool
>     +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    if (!clic->prv_s && !clic->prv_u) { /* M */
>     +        return mode == PRV_M;
>     +    } else if (!clic->prv_s) { /* M/U */
>     +        switch (clic->nmbits) {
>     +        case 0:
>     +            return mode == PRV_M;
>     +        case 1:
>     +            return clic->clicintattr[irq_offset] & 0x80 ? (mode
>     == PRV_M) :
>     + (mode == PRV_U);
>     +        default:
>     +            qemu_log_mask(LOG_GUEST_ERROR,
>     +                "clic: nmbits can only be 0 or 1 for M/U hart");
>     +            exit(1);
>     +        }
>     +    } else { /* M/S/U */
>     +        switch (clic->nmbits) {
>     +        case 0:
>     +            return mode == PRV_M;
>     +        case 1:
>     +            return clic->clicintattr[irq_offset] & 0x80 ? (mode
>     == PRV_M) :
>     + (mode == PRV_S);
>     +        case 2:
>     +            return mode == clic->clicintattr[irq_offset];
>
>
> Should be: return mode == (clic->clicintattr[irq_offset] >> 6);
>
Agree, thanks.

Zhiwei

> Regards,
> Frank Chang
>
>     +        case 3:
>     +            qemu_log_mask(LOG_GUEST_ERROR,
>     +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U
>     hart");
>     +            exit(1);
>     +        }
>     +    }
>     +    return false;
>     +}
>     +
>     +/*
>     + * For level-triggered interrupts, software writes to pending
>     bits are
>     + * ignored completely. (Section 3.4)
>     + */
>     +static bool
>     +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>     +}
>     +
>     +static void
>     +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t value)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    clic->clicintip[irq_offset] = !!value;
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +/*
>     + * For security purpose, the field can only be set to a privilege
>     + * level that is equal mode to or lower than the currently running
>     + * privilege level.(Section 3.6)
>     + */
>     +
>     +static bool riscv_clic_validate_intattr(RISCVCLICState *clic,
>     uint64_t value)
>     +{
>     +    int mode = extract64(value, 6, 2);
>     +
>     +    if (!qtest_enabled()) {
>     +        CPURISCVState *env = current_cpu->env_ptr;
>     +        if (env->priv < mode) {
>     +            return false;
>     +        }
>     +    }
>     +    return true;
>     +}
>     +
>     +static inline int riscv_clic_encode_priority(const
>     CLICActiveInterrupt *i)
>     +{
>     +    return ((i->intcfg & 0x3ff) << 12) | /* Highest
>     mode+level+priority */
>     +           (i->irq & 0xfff);             /* Highest irq number */
>     +}
>     +
>     +static int riscv_clic_active_compare(const void *a, const void *b)
>     +{
>     +    return riscv_clic_encode_priority(b) -
>     riscv_clic_encode_priority(a);
>     +}
>     +
>     +static void
>     +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t new_intie)
>     +{
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    CLICActiveInterrupt *active_list =
>     &clic->active_list[hart_offset];
>     +    size_t *active_count = &clic->active_count[hartid];
>     +
>     +    uint8_t old_intie = clic->clicintie[irq_offset];
>     +    clic->clicintie[irq_offset] = !!new_intie;
>     +
>     +    /* Add to or remove from list of active interrupts */
>     +    if (new_intie && !old_intie) {
>     +        active_list[*active_count].intcfg = (mode << 8) |
>     + clic->clicintctl[irq_offset];
>     +        active_list[*active_count].irq = irq;
>     +        (*active_count)++;
>     +    } else if (!new_intie && old_intie) {
>     +        CLICActiveInterrupt key = {
>     +            (mode << 8) | clic->clicintctl[irq_offset], irq
>     +        };
>     +        CLICActiveInterrupt *result = bsearch(&key,
>     +                                              active_list,
>     *active_count,
>     + sizeof(CLICActiveInterrupt),
>     + riscv_clic_active_compare);
>     +        size_t elem = (result - active_list) /
>     sizeof(CLICActiveInterrupt);
>     +        size_t sz = (--(*active_count) - elem) *
>     sizeof(CLICActiveInterrupt);
>     +        assert(result);
>     +        memmove(&result[0], &result[1], sz);
>     +    }
>     +
>     +    /* Sort list of active interrupts */
>     +    qsort(active_list, *active_count,
>     +          sizeof(CLICActiveInterrupt),
>     +          riscv_clic_active_compare);
>     +
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +static void
>     +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>     +                      uint64_t value, unsigned size,
>     +                      int mode, int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>     +            /*
>     +             * The actual pending bit is located at bit 0 (i.e., the
>     +             * leastsignificant bit). In case future extensions
>     expand the bit
>     +             * field, from FW perspective clicintip[i]=zero means
>     no interrupt
>     +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>     +             * interrupt is pending. (Section 3.4)
>     +             */
>     +            if (value != clic->clicintip[irq_offset]) {
>     +                riscv_clic_update_intip(clic, mode, hartid, irq,
>     value);
>     +            }
>     +        }
>     +        break;
>     +    case 1: /* clicintie[i] */
>     +        if (clic->clicintie[irq_offset] != value) {
>     +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>     +        }
>     +        break;
>     +    case 2: /* clicintattr[i] */
>     +        if (riscv_clic_validate_intattr(clic, value)) {
>     +            if (clic->clicintattr[irq_offset] != value) {
>     +                /* When nmbits=2, check WARL */
>     +                bool invalid = (clic->nmbits == 2) &&
>     +                               (extract64(value, 6, 2) == 0b10);
>     +                if (invalid) {
>     +                    uint8_t old_mode =
>     extract32(clic->clicintattr[irq_offset],
>     +                                                 6, 2);
>     +                    value = deposit32(value, 6, 2, old_mode);
>     +                }
>     +                clic->clicintattr[irq_offset] = value;
>     +                riscv_clic_next_interrupt(clic, hartid);
>     +            }
>     +        }
>     +        break;
>     +    case 3: /* clicintctl[i] */
>     +        if (value != clic->clicintctl[irq_offset]) {
>     +            clic->clicintctl[irq_offset] = value;
>     +            riscv_clic_next_interrupt(clic, hartid);
>     +        }
>     +        break;
>     +    }
>     +}
>     +
>     +static uint64_t
>     +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>     +                     int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return 0;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return 0;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        return clic->clicintip[irq_offset];
>     +    case 1: /* clicintie[i] */
>     +        return clic->clicintie[irq_offset];
>     +    case 2: /* clicintattr[i] */
>     +        /*
>     +         * clicintattr register layout
>     +         * Bits Field
>     +         * 7:6 mode
>     +         * 5:3 reserved (WPRI 0)
>     +         * 2:1 trig
>     +         * 0 shv
>     +         */
>     +        return clic->clicintattr[irq_offset] & ~0x38;
>     +    case 3: /* clicintctrl */
>     +        /*
>     +         * The implemented bits are kept left-justified in the
>     most-significant
>     +         * bits of each 8-bit clicintctl[i] register, with the lower
>     +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>     +         */
>     +        return clic->clicintctl[irq_offset] |
>     +               ((1 << (8 - clic->clicintctlbits)) - 1);
>     +    }
>     +
>     +    return 0;
>     +}
>     +
>     +/* Return target interrupt mode */
>     +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>     +    switch (mode) {
>     +    case 0:
>     +        return PRV_M;
>     +    case 1:
>     +        assert(clic->prv_s || clic->prv_u);
>     +        return clic->prv_s ? PRV_S : PRV_U;
>     +    case 2:
>     +        assert(clic->prv_s && clic->prv_u);
>     +        return PRV_U;
>     +    default:
>     +        g_assert_not_reached();
>     +        break;
>     +    }
>     +}
>     +
>     +/* Return target hart id */
>     +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>     +    int hart_unit = 4 * clic->num_sources;
>     +
>     +    return (addr % mode_unit) / hart_unit;
>     +}
>     +
>     +/* Return target interrupt number */
>     +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int hart_unit = 4 * clic->num_sources;
>     +    return (addr % hart_unit) / 4;
>     +}
>     +
>     +static void
>     +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                {
>     +                    uint8_t nlbits = extract32(value, 1, 4);
>     +                    uint8_t nmbits = extract32(value, 5, 2);
>     +
>     +                    /*
>     +                     * The 4-bit cliccfg.nlbits WARL field.
>     +                     * Valid values are 0—8.
>     +                     */
>     +                    if (nlbits <= 8) {
>     +                        clic->nlbits = nlbits;
>     +                    }
>     +                    /* Valid values are given by implemented
>     priviledges */
>     +                    if (clic->prv_s && clic->prv_u) {
>     +                        if (nmbits <= 2) {
>     +                            clic->nmbits = nmbits;
>     +                        }
>     +                    } else if (clic->prv_u) {
>     +                        if (nmbits <= 1) {
>     +                            clic->nmbits = nmbits;
>     +                        }
>     +                    } else {
>     +                        assert(!clic->prv_s);
>     +                        if (nmbits == 0) {
>     +                            clic->nmbits = 0;
>     +                        }
>     +                    }
>     +                    clic->nvbits = extract32(value, 0, 1);
>     +                    break;
>     +                }
>     +            case 1: /* clicinfo, read-only register */
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: write read-only clicinfo.\n");
>     +                break;
>     +            case 0x10 ... 0x2F: /* clicinttrig */
>     +                {
>     +                    uint32_t interrupt_number = value &
>     MAKE_64BIT_MASK(0, 13);
>     +                    if (interrupt_number <= clic->num_sources) {
>     +                        value &= ~MAKE_64BIT_MASK(13, 18);
>     +                        clic->clicinttrig[index - 0x10] = value;
>     +                    }
>     +                    break;
>     +                }
>     +            case 2: /* mintthresh */
>     +                if (!strcmp(clic->version, "v0.8")) {
>     +                    clic->mintthresh = value;
>     +                    break;
>     +                }
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid write addr: 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            default:
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid write addr: 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                return;
>     +            }
>     +        } else {
>     +            addr -= 0x1000;
>     +            hartid = riscv_clic_get_hartid(clic, addr);
>     +            mode = riscv_clic_get_mode(clic, addr);
>     +            irq = riscv_clic_get_irq(clic, addr);
>     +
>     +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>     +                riscv_clic_hart_write(clic, addr, value, size, mode,
>     +                                      hartid, irq);
>     +            }
>     +        }
>     +    } else {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid write: 0x%" HWADDR_PRIx
>     "\n", addr);
>     +    }
>     +}
>     +
>     +static uint64_t riscv_clic_read(void *opaque, hwaddr addr,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                return clic->nvbits |
>     +                       (clic->nlbits << 1) |
>     +                       (clic->nmbits << 5);
>     +            case 1: /* clicinfo */
>     +                /*
>     +                 * clicinfo register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 reserved (WARL 0)
>     +                 * 30:25 num_trigger
>     +                 * 24:21 CLICINTCTLBITS
>     +                 * 20:13 version (for version control)
>     +                 * 12:0 num_interrupt
>     +                 */
>     +                return clic->clicinfo & ~INT32_MAX;
>     +            case 0x10 ... 0x2F: /* clicinttrig */
>     +                /*
>     +                 * clicinttrig register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 enable
>     +                 * 30:13 reserved (WARL 0)
>     +                 * 12:0 interrupt_number
>     +                 */
>     +                return clic->clicinttrig[index - 0x10] &
>     +                       ~MAKE_64BIT_MASK(13, 18);
>     +            case 2: /* mintthresh */
>     +                if (!strcmp(clic->version, "v0.8")) {
>     +                    return clic->mintthresh;
>     +                    break;
>     +                }
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid read : 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            default:
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid read : 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            }
>     +        } else {
>     +            addr -= 0x1000;
>     +            hartid = riscv_clic_get_hartid(clic, addr);
>     +            mode = riscv_clic_get_mode(clic, addr);
>     +            irq = riscv_clic_get_irq(clic, addr);
>     +
>     +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>     +                return riscv_clic_hart_read(clic, addr, mode,
>     hartid, irq);
>     +            }
>     +        }
>     +    } else {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n",
>     addr);
>     +    }
>     +    return 0;
>     +}
>     +
>     +static void riscv_clic_set_irq(void *opaque, int id, int level)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    int irq, hartid, mode;
>     +    hwaddr addr = 4 * id;
>     +    TRIG_TYPE type;
>     +
>     +    hartid = riscv_clic_get_hartid(clic, addr);
>     +    mode = riscv_clic_get_mode(clic, addr);
>     +    irq = riscv_clic_get_irq(clic, addr);
>     +    type = riscv_clic_get_trigger_type(clic, id);
>     +
>     +    /*
>     +     * In general, the edge-triggered interrupt state should be
>     kept in pending
>     +     * bit, while the level-triggered interrupt should be kept in
>     the level
>     +     * state of the incoming wire.
>     +     *
>     +     * For CLIC, model the level-triggered interrupt by read-only
>     pending bit.
>     +     */
>     +    if (level) {
>     +        switch (type) {
>     +        case POSITIVE_LEVEL:
>     +        case POSITIVE_EDGE:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>     +            break;
>     +        case NEG_LEVEL:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>     +            break;
>     +        case NEG_EDGE:
>     +            break;
>     +        }
>     +    } else {
>     +        switch (type) {
>     +        case POSITIVE_LEVEL:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>     +            break;
>     +        case POSITIVE_EDGE:
>     +            break;
>     +        case NEG_LEVEL:
>     +        case NEG_EDGE:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>     +            break;
>     +        }
>     +    }
>     +}
>     +
>     +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int
>     level)
>     +{
>     +    CPURISCVState *env = (CPURISCVState *)opaque;
>     +    RISCVCLICState *clic = env->clic;
>     +    CPUState *cpu = env_cpu(env);
>     +
>     +    if (level) {
>     +        env->exccode = clic->exccode[cpu->cpu_index];
>     +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>     +    }
>     +}
>     +
>     +static const MemoryRegionOps riscv_clic_ops = {
>     +    .read = riscv_clic_read,
>     +    .write = riscv_clic_write,
>     +    .endianness = DEVICE_LITTLE_ENDIAN,
>     +    .valid = {
>     +        .min_access_size = 1,
>     +        .max_access_size = 8
>     +    }
>     +};
>     +
>     +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>     +{
>     +    RISCVCLICState *clic = RISCV_CLIC(dev);
>     +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>     +    int irqs, i;
>     +
>     +    if (clic->prv_s && clic->prv_u) {
>     +        irqs = 3 * harts_x_sources;
>     +    } else if (clic->prv_s || clic->prv_u) {
>     +        irqs = 2 * harts_x_sources;
>     +    } else {
>     +        irqs = harts_x_sources;
>     +    }
>     +
>     +    clic->clic_size = irqs * 4 + 0x1000;
>     +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>     &riscv_clic_ops, clic,
>     +                          TYPE_RISCV_CLIC, clic->clic_size);
>     +
>     +    clic->clicintip = g_new0(uint8_t, irqs);
>     +    clic->clicintie = g_new0(uint8_t, irqs);
>     +    clic->clicintattr = g_new0(uint8_t, irqs);
>     +    clic->clicintctl = g_new0(uint8_t, irqs);
>     +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>     +    clic->active_count = g_new0(size_t, clic->num_harts);
>     +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>     +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>     +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>     +
>     +    /* Allocate irq through gpio, so that we can use qtest */
>     +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>     +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>     +
>     +    for (i = 0; i < clic->num_harts; i++) {
>     +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>     +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>     +                                         &cpu->env, 1);
>     +        qdev_connect_gpio_out(dev, i, irq);
>     +        cpu->env.clic = clic;
>     +    }
>     +}
>     +
>     +static Property riscv_clic_properties[] = {
>     +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>     +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>     +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>     +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState,
>     num_sources, 0),
>     +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState,
>     clicintctlbits, 0),
>     +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>     +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>     +    DEFINE_PROP_END_OF_LIST(),
>     +};
>     +
>     +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>     +{
>     +    DeviceClass *dc = DEVICE_CLASS(klass);
>     +
>     +    dc->realize = riscv_clic_realize;
>     +    device_class_set_props(dc, riscv_clic_properties);
>     +}
>     +
>     +static const TypeInfo riscv_clic_info = {
>     +    .name          = TYPE_RISCV_CLIC,
>     +    .parent        = TYPE_SYS_BUS_DEVICE,
>     +    .instance_size = sizeof(RISCVCLICState),
>     +    .class_init    = riscv_clic_class_init,
>     +};
>     +
>     +static void riscv_clic_register_types(void)
>     +{
>     +    type_register_static(&riscv_clic_info);
>     +}
>     +
>     +type_init(riscv_clic_register_types)
>     +
>     +/*
>     + * riscv_clic_create:
>     + *
>     + * @addr: base address of M-Mode CLIC memory-mapped registers
>     + * @prv_s: have smode region
>     + * @prv_u: have umode region
>     + * @num_harts: number of CPU harts
>     + * @num_sources: number of interrupts supporting by each aperture
>     + * @clicintctlbits: bits are actually implemented in the
>     clicintctl registers
>     + * @version: clic version, such as "v0.9"
>     + *
>     + * Returns: the device object
>     + */
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version)
>     +{
>     +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>     +
>     +    assert(num_sources <= 4096);
>     +    assert(num_harts <= 1024);
>     +    assert(clicintctlbits <= 8);
>     +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>     +
>     +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>     +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>     +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>     +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>     +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>     +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>     +    qdev_prop_set_string(dev, "version", version);
>     +
>     +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>     +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>     +    return dev;
>     +}
>     +
>     +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid,
>     int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>     +}
>     +
>     +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>     hartid, int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>     +}
>     +
>     +void riscv_clic_clean_pending(void *opaque, int mode, int hartid,
>     int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    clic->clicintip[irq_offset] = 0;
>     +}
>     +
>     +/*
>     + * The new CLIC interrupt-handling mode is encoded as a new state in
>     + * the existing WARL xtvec register, where the low two bits of 
>     are 11.
>     + */
>     +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>     +{
>     +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec :
>     env->stvec;
>     +    return env->clic && ((xtvec & 0x3) == 3);
>     +}
>     +
>     +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>     +                               int *il, int *irq)
>     +{
>     +    *irq = extract32(exccode, 0, 12);
>     +    *mode = extract32(exccode, 12, 2);
>     +    *il = extract32(exccode, 14, 8);
>     +}
>     diff --git a/include/hw/intc/riscv_clic.h
>     b/include/hw/intc/riscv_clic.h
>     new file mode 100644
>     index 0000000000..e5f89672a6
>     --- /dev/null
>     +++ b/include/hw/intc/riscv_clic.h
>     @@ -0,0 +1,103 @@
>     +/*
>     + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>     + *
>     + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights
>     reserved.
>     + *
>     + * This program is free software; you can redistribute it and/or
>     modify it
>     + * under the terms and conditions of the GNU General Public License,
>     + * version 2 or later, as published by the Free Software Foundation.
>     + *
>     + * This program is distributed in the hope it will be useful, but
>     WITHOUT
>     + * ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or
>     + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
>     License for
>     + * more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along with
>     + * this program.  If not, see <http://www.gnu.org/licenses/
>     <http://www.gnu.org/licenses/>>.
>     + */
>     +
>     +#ifndef RISCV_CLIC_H
>     +#define RISCV_CLIC_H
>     +
>     +#include "hw/irq.h"
>     +#include "hw/sysbus.h"
>     +
>     +#define TYPE_RISCV_CLIC "riscv_clic"
>     +#define RISCV_CLIC(obj) \
>     +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>     +
>     +/*
>     + * CLIC per hart active interrupts
>     + *
>     + * We maintain per hart lists of enabled interrupts sorted by
>     + * mode+level+priority. The sorting is done on the configuration path
>     + * so that the interrupt delivery fastpath can linear scan enabled
>     + * interrupts in priority order.
>     + */
>     +typedef struct CLICActiveInterrupt {
>     +    uint16_t intcfg;
>     +    uint16_t irq;
>     +} CLICActiveInterrupt;
>     +
>     +typedef enum TRIG_TYPE {
>     +    POSITIVE_LEVEL,
>     +    POSITIVE_EDGE,
>     +    NEG_LEVEL,
>     +    NEG_EDGE,
>     +} TRIG_TYPE;
>     +
>     +typedef struct RISCVCLICState {
>     +    /*< private >*/
>     +    SysBusDevice parent_obj;
>     +
>     +    /*< public >*/
>     +
>     +    /* Implementaion parameters */
>     +    bool prv_s;
>     +    bool prv_u;
>     +    uint32_t num_harts;
>     +    uint32_t num_sources;
>     +    uint32_t clic_size;
>     +    uint32_t clic_mmode_base;
>     +    uint32_t clicintctlbits;
>     +    uint64_t mclicbase;
>     +    char *version;
>     +
>     +    /* Global configuration */
>     +    uint8_t nmbits;
>     +    uint8_t nlbits;
>     +    uint8_t nvbits;
>     +    uint32_t clicinfo;
>     +    uint32_t clicinttrig[32];
>     +
>     +    /* Aperture configuration */
>     +    uint8_t *clicintip;
>     +    uint8_t *clicintie;
>     +    uint8_t *clicintattr;
>     +    uint8_t *clicintctl;
>     +
>     +    /* Complatible with v0.8 */
>     +    uint32_t mintthresh;
>     +    uint32_t sintthresh;
>     +    uint32_t uintthresh;
>     +
>     +    /* QEMU implementaion related fields */
>     +    uint32_t *exccode;
>     +    CLICActiveInterrupt *active_list;
>     +    size_t *active_count;
>     +    MemoryRegion mmio;
>     +    qemu_irq *cpu_irqs;
>     +} RISCVCLICState;
>     +
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version);
>     +
>     +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int
>     *il, int *irq);
>     +void riscv_clic_clean_pending(void *opaque, int mode, int hartid,
>     int irq);
>     +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>     hartid, int irq);
>     +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid,
>     int irq);
>     +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>     +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>     +#endif
>     diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>     index a5eab26a69..9e389d7bbf 100644
>     --- a/target/riscv/cpu.h
>     +++ b/target/riscv/cpu.h
>     @@ -33,6 +33,7 @@
>      #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>      #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>      #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>     +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>      #define TYPE_RISCV_CPU_ANY RISCV_CPU_TYPE_NAME("any")
>      #define TYPE_RISCV_CPU_BASE32  RISCV_CPU_TYPE_NAME("rv32")
>     @@ -247,6 +248,7 @@ struct CPURISCVState {
>          /* Fields from here on are preserved across CPU reset. */
>          QEMUTimer *timer; /* Internal timer */
>          void *clic;       /* clic interrupt controller */
>     +    uint32_t exccode; /* clic irq encode */
>      };
>
>      OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>     -- 
>     2.25.1
>
>
LIU Zhiwei June 28, 2021, 12:56 p.m. UTC | #25
On 2021/6/28 下午6:16, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年4月9日 週五 下午3:57寫道:
>
>     The Core-Local Interrupt Controller (CLIC) provides low-latency,
>     vectored, pre-emptive interrupts for RISC-V systems.
>
>     The CLIC also supports a new Selective Hardware Vectoring feature
>     that allow users to optimize each interrupt for either faster
>     response or smaller code size.
>
>     Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com
>     <mailto:zhiwei_liu@c-sky.com>>
>     ---
>      default-configs/devices/riscv32-softmmu.mak |   1 +
>      default-configs/devices/riscv64-softmmu.mak |   1 +
>      hw/intc/Kconfig                             |   3 +
>      hw/intc/meson.build                         |   1 +
>      hw/intc/riscv_clic.c                        | 835
>     ++++++++++++++++++++
>      include/hw/intc/riscv_clic.h                | 103 +++
>      target/riscv/cpu.h                          |   2 +
>      7 files changed, 946 insertions(+)
>      create mode 100644 hw/intc/riscv_clic.c
>      create mode 100644 include/hw/intc/riscv_clic.h
>
>     diff --git a/default-configs/devices/riscv32-softmmu.mak
>     b/default-configs/devices/riscv32-softmmu.mak
>     index d847bd5692..1430c30588 100644
>     --- a/default-configs/devices/riscv32-softmmu.mak
>     +++ b/default-configs/devices/riscv32-softmmu.mak
>     @@ -5,6 +5,7 @@
>      #CONFIG_PCI_DEVICES=n
>      CONFIG_SEMIHOSTING=y
>      CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>     +CONFIG_RISCV_CLIC=y
>
>      # Boards:
>      #
>     diff --git a/default-configs/devices/riscv64-softmmu.mak
>     b/default-configs/devices/riscv64-softmmu.mak
>     index d5eec75f05..396800bbbd 100644
>     --- a/default-configs/devices/riscv64-softmmu.mak
>     +++ b/default-configs/devices/riscv64-softmmu.mak
>     @@ -5,6 +5,7 @@
>      #CONFIG_PCI_DEVICES=n
>      CONFIG_SEMIHOSTING=y
>      CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>     +CONFIG_RISCV_CLIC=y
>
>      # Boards:
>      #
>     diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>     index f4694088a4..5bf492b48f 100644
>     --- a/hw/intc/Kconfig
>     +++ b/hw/intc/Kconfig
>     @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>      config SIFIVE_PLIC
>          bool
>
>     +config RISCV_CLIC
>     +    bool
>     +
>      config GOLDFISH_PIC
>          bool
>
>     diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>     index 1c299039f6..2aa71b6738 100644
>     --- a/hw/intc/meson.build
>     +++ b/hw/intc/meson.build
>     @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM',
>     if_true: files('s390_flic_kvm.c'))
>      specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>      specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>     files('sifive_clint.c'))
>      specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>     files('sifive_plic.c'))
>     +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>     files('riscv_clic.c'))
>      specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>      specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                     if_true: files('xics_kvm.c'))
>     diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>     new file mode 100644
>     index 0000000000..8ad534c506
>     --- /dev/null
>     +++ b/hw/intc/riscv_clic.c
>     @@ -0,0 +1,835 @@
>     +/*
>     + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>     + *
>     + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights
>     reserved.
>     + *
>     + * This program is free software; you can redistribute it and/or
>     modify it
>     + * under the terms and conditions of the GNU General Public License,
>     + * version 2 or later, as published by the Free Software Foundation.
>     + *
>     + * This program is distributed in the hope it will be useful, but
>     WITHOUT
>     + * ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or
>     + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
>     License for
>     + * more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along with
>     + * this program.  If not, see <http://www.gnu.org/licenses/
>     <http://www.gnu.org/licenses/>>.
>     + */
>     +
>     +#include "qemu/osdep.h"
>     +#include "qapi/error.h"
>     +#include "qemu/log.h"
>     +#include "hw/sysbus.h"
>     +#include "sysemu/qtest.h"
>     +#include "target/riscv/cpu.h"
>     +#include "hw/qdev-properties.h"
>     +#include "hw/intc/riscv_clic.h"
>     +
>     +/*
>     + * The 2-bit trig WARL field specifies the trigger type and
>     polarity for each
>     + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>     + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1],
>     is defined as
>     + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section
>     3.6)
>     + */
>     +
>     +static inline TRIG_TYPE
>     +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>     +}
>     +
>     +static inline bool
>     +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>     +}
>     +
>     +static inline bool
>     +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>     +{
>     +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>     +}
>     +
>     +static uint8_t
>     +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>     +{
>     +    int nlbits = clic->nlbits;
>     +
>     +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>     +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>     +    /* unused level bits are set to 1 */
>     +    return (intctl & mask_il) | mask_padding;
>     +}
>     +
>     +static uint8_t
>     +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t
>     intctl)
>     +{
>     +    int npbits = clic->clicintctlbits - clic->nlbits;
>     +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>     +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>     +
>     +    if (npbits < 0) {
>     +        return UINT8_MAX;
>     +    }
>     +    /* unused priority bits are set to 1 */
>     +    return (intctl & mask_priority) | mask_padding;
>     +}
>     +
>     +static void
>     +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>     +                         uint8_t *mode, uint8_t *level,
>     +                         uint8_t *priority)
>     +{
>     +    *mode = intcfg >> 8;
>     +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>     +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg &
>     0xff);
>     +}
>     +
>     +/*
>     + * In a system with multiple harts, the M-mode CLIC regions for
>     all the harts
>     + * are placed contiguously in the memory space, followed by the
>     S-mode CLIC
>     + * regions for all harts. (Section 3.11)
>     + */
>     +static size_t
>     +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t mode_offset = 0;
>     +    size_t unit = clic->num_harts * clic->num_sources;
>     +
>     +    switch (mode) {
>     +    case PRV_M:
>     +        mode_offset = 0;
>     +        break;
>     +    case PRV_S:
>     +        mode_offset = unit;
>     +        break;
>     +    case PRV_U:
>     +        mode_offset = clic->prv_s ? 2 * unit : unit;
>     +        break;
>     +    default:
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid mode %d\n", mode);
>     +        exit(1);
>     +    }
>     +    return mode_offset + hartid * clic->num_sources + irq;
>     +}
>     +
>     +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>     +{
>     +    /*
>     +     * Scan active list for highest priority pending interrupts
>     +     * comparing against this harts mintstatus register and interrupt
>     +     * the core if we have a higher priority interrupt to deliver
>     +     */
>     +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>     +    CPURISCVState *env = &cpu->env;
>     +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>     +
>     +    int il[4] = {
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>     +            clic->mintthresh), /* PRV_U */
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>     +            clic->sintthresh), /* PRV_S */
>     +        0,                     /* reserverd */
>     +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>     +            clic->uintthresh)  /* PRV_M */
>     +    };
>     +
>     +    /* Get sorted list of enabled interrupts for this hart */
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>     +    size_t active_count = clic->active_count[hartid];
>     +    uint8_t mode, level, priority;
>     +
>     +    /* Loop through the enabled interrupts sorted by
>     mode+priority+level */
>     +    while (active_count) {
>     +        size_t irq_offset;
>     +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>     +                                 &priority);
>     +        if (mode < env->priv || (mode == env->priv && level <=
>     il[mode])) {
>     +            /*
>     +             * No pending interrupts with high enough
>     mode+priority+level
>     +             * break and clear pending interrupt for this hart
>     +             */
>     +            break;
>     +        }
>     +        irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, active->irq);
>     +        /* Check pending interrupt with high enough
>     mode+priority+level */
>     +        if (clic->clicintip[irq_offset]) {
>     +            /* Clean vector edge-triggered pending */
>     +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>     +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>     +                clic->clicintip[irq_offset] = 0;
>     +            }
>     +            /* Post pending interrupt for this hart */
>     +            clic->exccode[hartid] = active->irq | mode << 12 |
>     level << 14;
>     +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>     +            return;
>     +        }
>     +        /* Check next enabled interrupt */
>     +        active_count--;
>     +        active++;
>     +    }
>     +}
>     +
>     +/*
>     + * Any interrupt i that is not accessible to S-mode or U-Mode
>     + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>     + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>     + */
>     +static bool
>     +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    if (!clic->prv_s && !clic->prv_u) { /* M */
>     +        return mode == PRV_M;
>
>
> Does the mode here to be checked should be the current hart's 
> privilege mode
> at the time register is been accessed?
>
Yes, it is.

After review the latest specification,  I think I have a  wrong 
understanding of CLIC
memory map.

S-mode or U-mode interrupt regions should just be a restricted view of 
M-mode interrupts.

The check of visible  should not use the irq_offset here. And all writes 
to S-mode or U-mode
interrupt region should also update the M-mode corresponding registers.

Thanks,
Zhiwei



> Regards,
> Frank Chang
>
>     +    } else if (!clic->prv_s) { /* M/U */
>     +        switch (clic->nmbits) {
>     +        case 0:
>     +            return mode == PRV_M;
>     +        case 1:
>     +            return clic->clicintattr[irq_offset] & 0x80 ? (mode
>     == PRV_M) :
>     + (mode == PRV_U);
>     +        default:
>     +            qemu_log_mask(LOG_GUEST_ERROR,
>     +                "clic: nmbits can only be 0 or 1 for M/U hart");
>     +            exit(1);
>     +        }
>     +    } else { /* M/S/U */
>     +        switch (clic->nmbits) {
>     +        case 0:
>     +            return mode == PRV_M;
>     +        case 1:
>     +            return clic->clicintattr[irq_offset] & 0x80 ? (mode
>     == PRV_M) :
>     + (mode == PRV_S);
>     +        case 2:
>     +            return mode == clic->clicintattr[irq_offset];
>     +        case 3:
>     +            qemu_log_mask(LOG_GUEST_ERROR,
>     +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U
>     hart");
>     +            exit(1);
>     +        }
>     +    }
>     +    return false;
>     +}
>     +
>     +/*
>     + * For level-triggered interrupts, software writes to pending
>     bits are
>     + * ignored completely. (Section 3.4)
>     + */
>     +static bool
>     +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int
>     hartid, int irq)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>     +}
>     +
>     +static void
>     +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t value)
>     +{
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    clic->clicintip[irq_offset] = !!value;
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +/*
>     + * For security purpose, the field can only be set to a privilege
>     + * level that is equal mode to or lower than the currently running
>     + * privilege level.(Section 3.6)
>     + */
>     +
>     +static bool riscv_clic_validate_intattr(RISCVCLICState *clic,
>     uint64_t value)
>     +{
>     +    int mode = extract64(value, 6, 2);
>     +
>     +    if (!qtest_enabled()) {
>     +        CPURISCVState *env = current_cpu->env_ptr;
>     +        if (env->priv < mode) {
>     +            return false;
>     +        }
>     +    }
>     +    return true;
>     +}
>     +
>     +static inline int riscv_clic_encode_priority(const
>     CLICActiveInterrupt *i)
>     +{
>     +    return ((i->intcfg & 0x3ff) << 12) | /* Highest
>     mode+level+priority */
>     +           (i->irq & 0xfff);             /* Highest irq number */
>     +}
>     +
>     +static int riscv_clic_active_compare(const void *a, const void *b)
>     +{
>     +    return riscv_clic_encode_priority(b) -
>     riscv_clic_encode_priority(a);
>     +}
>     +
>     +static void
>     +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t new_intie)
>     +{
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    CLICActiveInterrupt *active_list =
>     &clic->active_list[hart_offset];
>     +    size_t *active_count = &clic->active_count[hartid];
>     +
>     +    uint8_t old_intie = clic->clicintie[irq_offset];
>     +    clic->clicintie[irq_offset] = !!new_intie;
>     +
>     +    /* Add to or remove from list of active interrupts */
>     +    if (new_intie && !old_intie) {
>     +        active_list[*active_count].intcfg = (mode << 8) |
>     + clic->clicintctl[irq_offset];
>     +        active_list[*active_count].irq = irq;
>     +        (*active_count)++;
>     +    } else if (!new_intie && old_intie) {
>     +        CLICActiveInterrupt key = {
>     +            (mode << 8) | clic->clicintctl[irq_offset], irq
>     +        };
>     +        CLICActiveInterrupt *result = bsearch(&key,
>     +                                              active_list,
>     *active_count,
>     + sizeof(CLICActiveInterrupt),
>     + riscv_clic_active_compare);
>     +        size_t elem = (result - active_list) /
>     sizeof(CLICActiveInterrupt);
>     +        size_t sz = (--(*active_count) - elem) *
>     sizeof(CLICActiveInterrupt);
>     +        assert(result);
>     +        memmove(&result[0], &result[1], sz);
>     +    }
>     +
>     +    /* Sort list of active interrupts */
>     +    qsort(active_list, *active_count,
>     +          sizeof(CLICActiveInterrupt),
>     +          riscv_clic_active_compare);
>     +
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +static void
>     +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>     +                      uint64_t value, unsigned size,
>     +                      int mode, int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>     +            /*
>     +             * The actual pending bit is located at bit 0 (i.e., the
>     +             * leastsignificant bit). In case future extensions
>     expand the bit
>     +             * field, from FW perspective clicintip[i]=zero means
>     no interrupt
>     +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>     +             * interrupt is pending. (Section 3.4)
>     +             */
>     +            if (value != clic->clicintip[irq_offset]) {
>     +                riscv_clic_update_intip(clic, mode, hartid, irq,
>     value);
>     +            }
>     +        }
>     +        break;
>     +    case 1: /* clicintie[i] */
>     +        if (clic->clicintie[irq_offset] != value) {
>     +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>     +        }
>     +        break;
>     +    case 2: /* clicintattr[i] */
>     +        if (riscv_clic_validate_intattr(clic, value)) {
>     +            if (clic->clicintattr[irq_offset] != value) {
>     +                /* When nmbits=2, check WARL */
>     +                bool invalid = (clic->nmbits == 2) &&
>     +                               (extract64(value, 6, 2) == 0b10);
>     +                if (invalid) {
>     +                    uint8_t old_mode =
>     extract32(clic->clicintattr[irq_offset],
>     +                                                 6, 2);
>     +                    value = deposit32(value, 6, 2, old_mode);
>     +                }
>     +                clic->clicintattr[irq_offset] = value;
>     +                riscv_clic_next_interrupt(clic, hartid);
>     +            }
>     +        }
>     +        break;
>     +    case 3: /* clicintctl[i] */
>     +        if (value != clic->clicintctl[irq_offset]) {
>     +            clic->clicintctl[irq_offset] = value;
>     +            riscv_clic_next_interrupt(clic, hartid);
>     +        }
>     +        break;
>     +    }
>     +}
>     +
>     +static uint64_t
>     +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>     +                     int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return 0;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return 0;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        return clic->clicintip[irq_offset];
>     +    case 1: /* clicintie[i] */
>     +        return clic->clicintie[irq_offset];
>     +    case 2: /* clicintattr[i] */
>     +        /*
>     +         * clicintattr register layout
>     +         * Bits Field
>     +         * 7:6 mode
>     +         * 5:3 reserved (WPRI 0)
>     +         * 2:1 trig
>     +         * 0 shv
>     +         */
>     +        return clic->clicintattr[irq_offset] & ~0x38;
>     +    case 3: /* clicintctrl */
>     +        /*
>     +         * The implemented bits are kept left-justified in the
>     most-significant
>     +         * bits of each 8-bit clicintctl[i] register, with the lower
>     +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>     +         */
>     +        return clic->clicintctl[irq_offset] |
>     +               ((1 << (8 - clic->clicintctlbits)) - 1);
>     +    }
>     +
>     +    return 0;
>     +}
>     +
>     +/* Return target interrupt mode */
>     +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>     +    switch (mode) {
>     +    case 0:
>     +        return PRV_M;
>     +    case 1:
>     +        assert(clic->prv_s || clic->prv_u);
>     +        return clic->prv_s ? PRV_S : PRV_U;
>     +    case 2:
>     +        assert(clic->prv_s && clic->prv_u);
>     +        return PRV_U;
>     +    default:
>     +        g_assert_not_reached();
>     +        break;
>     +    }
>     +}
>     +
>     +/* Return target hart id */
>     +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>     +    int hart_unit = 4 * clic->num_sources;
>     +
>     +    return (addr % mode_unit) / hart_unit;
>     +}
>     +
>     +/* Return target interrupt number */
>     +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>     +{
>     +    int hart_unit = 4 * clic->num_sources;
>     +    return (addr % hart_unit) / 4;
>     +}
>     +
>     +static void
>     +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                {
>     +                    uint8_t nlbits = extract32(value, 1, 4);
>     +                    uint8_t nmbits = extract32(value, 5, 2);
>     +
>     +                    /*
>     +                     * The 4-bit cliccfg.nlbits WARL field.
>     +                     * Valid values are 0—8.
>     +                     */
>     +                    if (nlbits <= 8) {
>     +                        clic->nlbits = nlbits;
>     +                    }
>     +                    /* Valid values are given by implemented
>     priviledges */
>     +                    if (clic->prv_s && clic->prv_u) {
>     +                        if (nmbits <= 2) {
>     +                            clic->nmbits = nmbits;
>     +                        }
>     +                    } else if (clic->prv_u) {
>     +                        if (nmbits <= 1) {
>     +                            clic->nmbits = nmbits;
>     +                        }
>     +                    } else {
>     +                        assert(!clic->prv_s);
>     +                        if (nmbits == 0) {
>     +                            clic->nmbits = 0;
>     +                        }
>     +                    }
>     +                    clic->nvbits = extract32(value, 0, 1);
>     +                    break;
>     +                }
>     +            case 1: /* clicinfo, read-only register */
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: write read-only clicinfo.\n");
>     +                break;
>     +            case 0x10 ... 0x2F: /* clicinttrig */
>     +                {
>     +                    uint32_t interrupt_number = value &
>     MAKE_64BIT_MASK(0, 13);
>     +                    if (interrupt_number <= clic->num_sources) {
>     +                        value &= ~MAKE_64BIT_MASK(13, 18);
>     +                        clic->clicinttrig[index - 0x10] = value;
>     +                    }
>     +                    break;
>     +                }
>     +            case 2: /* mintthresh */
>     +                if (!strcmp(clic->version, "v0.8")) {
>     +                    clic->mintthresh = value;
>     +                    break;
>     +                }
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid write addr: 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            default:
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid write addr: 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                return;
>     +            }
>     +        } else {
>     +            addr -= 0x1000;
>     +            hartid = riscv_clic_get_hartid(clic, addr);
>     +            mode = riscv_clic_get_mode(clic, addr);
>     +            irq = riscv_clic_get_irq(clic, addr);
>     +
>     +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>     +                riscv_clic_hart_write(clic, addr, value, size, mode,
>     +                                      hartid, irq);
>     +            }
>     +        }
>     +    } else {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid write: 0x%" HWADDR_PRIx
>     "\n", addr);
>     +    }
>     +}
>     +
>     +static uint64_t riscv_clic_read(void *opaque, hwaddr addr,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                return clic->nvbits |
>     +                       (clic->nlbits << 1) |
>     +                       (clic->nmbits << 5);
>     +            case 1: /* clicinfo */
>     +                /*
>     +                 * clicinfo register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 reserved (WARL 0)
>     +                 * 30:25 num_trigger
>     +                 * 24:21 CLICINTCTLBITS
>     +                 * 20:13 version (for version control)
>     +                 * 12:0 num_interrupt
>     +                 */
>     +                return clic->clicinfo & ~INT32_MAX;
>     +            case 0x10 ... 0x2F: /* clicinttrig */
>     +                /*
>     +                 * clicinttrig register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 enable
>     +                 * 30:13 reserved (WARL 0)
>     +                 * 12:0 interrupt_number
>     +                 */
>     +                return clic->clicinttrig[index - 0x10] &
>     +                       ~MAKE_64BIT_MASK(13, 18);
>     +            case 2: /* mintthresh */
>     +                if (!strcmp(clic->version, "v0.8")) {
>     +                    return clic->mintthresh;
>     +                    break;
>     +                }
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid read : 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            default:
>     +                qemu_log_mask(LOG_GUEST_ERROR,
>     +                              "clic: invalid read : 0x%"
>     HWADDR_PRIx "\n",
>     +                              addr);
>     +                break;
>     +            }
>     +        } else {
>     +            addr -= 0x1000;
>     +            hartid = riscv_clic_get_hartid(clic, addr);
>     +            mode = riscv_clic_get_mode(clic, addr);
>     +            irq = riscv_clic_get_irq(clic, addr);
>     +
>     +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>     +                return riscv_clic_hart_read(clic, addr, mode,
>     hartid, irq);
>     +            }
>     +        }
>     +    } else {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n",
>     addr);
>     +    }
>     +    return 0;
>     +}
>     +
>     +static void riscv_clic_set_irq(void *opaque, int id, int level)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    int irq, hartid, mode;
>     +    hwaddr addr = 4 * id;
>     +    TRIG_TYPE type;
>     +
>     +    hartid = riscv_clic_get_hartid(clic, addr);
>     +    mode = riscv_clic_get_mode(clic, addr);
>     +    irq = riscv_clic_get_irq(clic, addr);
>     +    type = riscv_clic_get_trigger_type(clic, id);
>     +
>     +    /*
>     +     * In general, the edge-triggered interrupt state should be
>     kept in pending
>     +     * bit, while the level-triggered interrupt should be kept in
>     the level
>     +     * state of the incoming wire.
>     +     *
>     +     * For CLIC, model the level-triggered interrupt by read-only
>     pending bit.
>     +     */
>     +    if (level) {
>     +        switch (type) {
>     +        case POSITIVE_LEVEL:
>     +        case POSITIVE_EDGE:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>     +            break;
>     +        case NEG_LEVEL:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>     +            break;
>     +        case NEG_EDGE:
>     +            break;
>     +        }
>     +    } else {
>     +        switch (type) {
>     +        case POSITIVE_LEVEL:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>     +            break;
>     +        case POSITIVE_EDGE:
>     +            break;
>     +        case NEG_LEVEL:
>     +        case NEG_EDGE:
>     +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>     +            break;
>     +        }
>     +    }
>     +}
>     +
>     +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int
>     level)
>     +{
>     +    CPURISCVState *env = (CPURISCVState *)opaque;
>     +    RISCVCLICState *clic = env->clic;
>     +    CPUState *cpu = env_cpu(env);
>     +
>     +    if (level) {
>     +        env->exccode = clic->exccode[cpu->cpu_index];
>     +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>     +    }
>     +}
>     +
>     +static const MemoryRegionOps riscv_clic_ops = {
>     +    .read = riscv_clic_read,
>     +    .write = riscv_clic_write,
>     +    .endianness = DEVICE_LITTLE_ENDIAN,
>     +    .valid = {
>     +        .min_access_size = 1,
>     +        .max_access_size = 8
>     +    }
>     +};
>     +
>     +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>     +{
>     +    RISCVCLICState *clic = RISCV_CLIC(dev);
>     +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>     +    int irqs, i;
>     +
>     +    if (clic->prv_s && clic->prv_u) {
>     +        irqs = 3 * harts_x_sources;
>     +    } else if (clic->prv_s || clic->prv_u) {
>     +        irqs = 2 * harts_x_sources;
>     +    } else {
>     +        irqs = harts_x_sources;
>     +    }
>     +
>     +    clic->clic_size = irqs * 4 + 0x1000;
>     +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>     &riscv_clic_ops, clic,
>     +                          TYPE_RISCV_CLIC, clic->clic_size);
>     +
>     +    clic->clicintip = g_new0(uint8_t, irqs);
>     +    clic->clicintie = g_new0(uint8_t, irqs);
>     +    clic->clicintattr = g_new0(uint8_t, irqs);
>     +    clic->clicintctl = g_new0(uint8_t, irqs);
>     +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>     +    clic->active_count = g_new0(size_t, clic->num_harts);
>     +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>     +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>     +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>     +
>     +    /* Allocate irq through gpio, so that we can use qtest */
>     +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>     +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>     +
>     +    for (i = 0; i < clic->num_harts; i++) {
>     +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>     +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>     +                                         &cpu->env, 1);
>     +        qdev_connect_gpio_out(dev, i, irq);
>     +        cpu->env.clic = clic;
>     +    }
>     +}
>     +
>     +static Property riscv_clic_properties[] = {
>     +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>     +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>     +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>     +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState,
>     num_sources, 0),
>     +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState,
>     clicintctlbits, 0),
>     +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>     +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>     +    DEFINE_PROP_END_OF_LIST(),
>     +};
>     +
>     +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>     +{
>     +    DeviceClass *dc = DEVICE_CLASS(klass);
>     +
>     +    dc->realize = riscv_clic_realize;
>     +    device_class_set_props(dc, riscv_clic_properties);
>     +}
>     +
>     +static const TypeInfo riscv_clic_info = {
>     +    .name          = TYPE_RISCV_CLIC,
>     +    .parent        = TYPE_SYS_BUS_DEVICE,
>     +    .instance_size = sizeof(RISCVCLICState),
>     +    .class_init    = riscv_clic_class_init,
>     +};
>     +
>     +static void riscv_clic_register_types(void)
>     +{
>     +    type_register_static(&riscv_clic_info);
>     +}
>     +
>     +type_init(riscv_clic_register_types)
>     +
>     +/*
>     + * riscv_clic_create:
>     + *
>     + * @addr: base address of M-Mode CLIC memory-mapped registers
>     + * @prv_s: have smode region
>     + * @prv_u: have umode region
>     + * @num_harts: number of CPU harts
>     + * @num_sources: number of interrupts supporting by each aperture
>     + * @clicintctlbits: bits are actually implemented in the
>     clicintctl registers
>     + * @version: clic version, such as "v0.9"
>     + *
>     + * Returns: the device object
>     + */
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version)
>     +{
>     +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>     +
>     +    assert(num_sources <= 4096);
>     +    assert(num_harts <= 1024);
>     +    assert(clicintctlbits <= 8);
>     +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>     +
>     +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>     +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>     +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>     +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>     +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>     +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>     +    qdev_prop_set_string(dev, "version", version);
>     +
>     +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>     +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>     +    return dev;
>     +}
>     +
>     +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid,
>     int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>     +}
>     +
>     +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>     hartid, int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>     +}
>     +
>     +void riscv_clic_clean_pending(void *opaque, int mode, int hartid,
>     int irq)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    clic->clicintip[irq_offset] = 0;
>     +}
>     +
>     +/*
>     + * The new CLIC interrupt-handling mode is encoded as a new state in
>     + * the existing WARL xtvec register, where the low two bits of 
>     are 11.
>     + */
>     +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>     +{
>     +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec :
>     env->stvec;
>     +    return env->clic && ((xtvec & 0x3) == 3);
>     +}
>     +
>     +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>     +                               int *il, int *irq)
>     +{
>     +    *irq = extract32(exccode, 0, 12);
>     +    *mode = extract32(exccode, 12, 2);
>     +    *il = extract32(exccode, 14, 8);
>     +}
>     diff --git a/include/hw/intc/riscv_clic.h
>     b/include/hw/intc/riscv_clic.h
>     new file mode 100644
>     index 0000000000..e5f89672a6
>     --- /dev/null
>     +++ b/include/hw/intc/riscv_clic.h
>     @@ -0,0 +1,103 @@
>     +/*
>     + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>     + *
>     + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights
>     reserved.
>     + *
>     + * This program is free software; you can redistribute it and/or
>     modify it
>     + * under the terms and conditions of the GNU General Public License,
>     + * version 2 or later, as published by the Free Software Foundation.
>     + *
>     + * This program is distributed in the hope it will be useful, but
>     WITHOUT
>     + * ANY WARRANTY; without even the implied warranty of
>     MERCHANTABILITY or
>     + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
>     License for
>     + * more details.
>     + *
>     + * You should have received a copy of the GNU General Public
>     License along with
>     + * this program.  If not, see <http://www.gnu.org/licenses/
>     <http://www.gnu.org/licenses/>>.
>     + */
>     +
>     +#ifndef RISCV_CLIC_H
>     +#define RISCV_CLIC_H
>     +
>     +#include "hw/irq.h"
>     +#include "hw/sysbus.h"
>     +
>     +#define TYPE_RISCV_CLIC "riscv_clic"
>     +#define RISCV_CLIC(obj) \
>     +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>     +
>     +/*
>     + * CLIC per hart active interrupts
>     + *
>     + * We maintain per hart lists of enabled interrupts sorted by
>     + * mode+level+priority. The sorting is done on the configuration path
>     + * so that the interrupt delivery fastpath can linear scan enabled
>     + * interrupts in priority order.
>     + */
>     +typedef struct CLICActiveInterrupt {
>     +    uint16_t intcfg;
>     +    uint16_t irq;
>     +} CLICActiveInterrupt;
>     +
>     +typedef enum TRIG_TYPE {
>     +    POSITIVE_LEVEL,
>     +    POSITIVE_EDGE,
>     +    NEG_LEVEL,
>     +    NEG_EDGE,
>     +} TRIG_TYPE;
>     +
>     +typedef struct RISCVCLICState {
>     +    /*< private >*/
>     +    SysBusDevice parent_obj;
>     +
>     +    /*< public >*/
>     +
>     +    /* Implementaion parameters */
>     +    bool prv_s;
>     +    bool prv_u;
>     +    uint32_t num_harts;
>     +    uint32_t num_sources;
>     +    uint32_t clic_size;
>     +    uint32_t clic_mmode_base;
>     +    uint32_t clicintctlbits;
>     +    uint64_t mclicbase;
>     +    char *version;
>     +
>     +    /* Global configuration */
>     +    uint8_t nmbits;
>     +    uint8_t nlbits;
>     +    uint8_t nvbits;
>     +    uint32_t clicinfo;
>     +    uint32_t clicinttrig[32];
>     +
>     +    /* Aperture configuration */
>     +    uint8_t *clicintip;
>     +    uint8_t *clicintie;
>     +    uint8_t *clicintattr;
>     +    uint8_t *clicintctl;
>     +
>     +    /* Complatible with v0.8 */
>     +    uint32_t mintthresh;
>     +    uint32_t sintthresh;
>     +    uint32_t uintthresh;
>     +
>     +    /* QEMU implementaion related fields */
>     +    uint32_t *exccode;
>     +    CLICActiveInterrupt *active_list;
>     +    size_t *active_count;
>     +    MemoryRegion mmio;
>     +    qemu_irq *cpu_irqs;
>     +} RISCVCLICState;
>     +
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version);
>     +
>     +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int
>     *il, int *irq);
>     +void riscv_clic_clean_pending(void *opaque, int mode, int hartid,
>     int irq);
>     +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>     hartid, int irq);
>     +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid,
>     int irq);
>     +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>     +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>     +#endif
>     diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>     index a5eab26a69..9e389d7bbf 100644
>     --- a/target/riscv/cpu.h
>     +++ b/target/riscv/cpu.h
>     @@ -33,6 +33,7 @@
>      #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>      #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>      #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>     +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>      #define TYPE_RISCV_CPU_ANY RISCV_CPU_TYPE_NAME("any")
>      #define TYPE_RISCV_CPU_BASE32  RISCV_CPU_TYPE_NAME("rv32")
>     @@ -247,6 +248,7 @@ struct CPURISCVState {
>          /* Fields from here on are preserved across CPU reset. */
>          QEMUTimer *timer; /* Internal timer */
>          void *clic;       /* clic interrupt controller */
>     +    uint32_t exccode; /* clic irq encode */
>      };
>
>      OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>     -- 
>     2.25.1
>
>
Frank Chang June 28, 2021, 2:30 p.m. UTC | #26
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月28日 週一 下午9:03寫道:

>
> On 2021/6/28 下午6:16, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>
>> The Core-Local Interrupt Controller (CLIC) provides low-latency,
>> vectored, pre-emptive interrupts for RISC-V systems.
>>
>> The CLIC also supports a new Selective Hardware Vectoring feature
>> that allow users to optimize each interrupt for either faster
>> response or smaller code size.
>>
>> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
>> ---
>>  default-configs/devices/riscv32-softmmu.mak |   1 +
>>  default-configs/devices/riscv64-softmmu.mak |   1 +
>>  hw/intc/Kconfig                             |   3 +
>>  hw/intc/meson.build                         |   1 +
>>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>>  include/hw/intc/riscv_clic.h                | 103 +++
>>  target/riscv/cpu.h                          |   2 +
>>  7 files changed, 946 insertions(+)
>>  create mode 100644 hw/intc/riscv_clic.c
>>  create mode 100644 include/hw/intc/riscv_clic.h
>>
>> diff --git a/default-configs/devices/riscv32-softmmu.mak
>> b/default-configs/devices/riscv32-softmmu.mak
>> index d847bd5692..1430c30588 100644
>> --- a/default-configs/devices/riscv32-softmmu.mak
>> +++ b/default-configs/devices/riscv32-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/default-configs/devices/riscv64-softmmu.mak
>> b/default-configs/devices/riscv64-softmmu.mak
>> index d5eec75f05..396800bbbd 100644
>> --- a/default-configs/devices/riscv64-softmmu.mak
>> +++ b/default-configs/devices/riscv64-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>> index f4694088a4..5bf492b48f 100644
>> --- a/hw/intc/Kconfig
>> +++ b/hw/intc/Kconfig
>> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>  config SIFIVE_PLIC
>>      bool
>>
>> +config RISCV_CLIC
>> +    bool
>> +
>>  config GOLDFISH_PIC
>>      bool
>>
>> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>> index 1c299039f6..2aa71b6738 100644
>> --- a/hw/intc/meson.build
>> +++ b/hw/intc/meson.build
>> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
>> files('s390_flic_kvm.c'))
>>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>> files('sifive_clint.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>> files('sifive_plic.c'))
>> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>> files('riscv_clic.c'))
>>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                 if_true: files('xics_kvm.c'))
>> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>> new file mode 100644
>> index 0000000000..8ad534c506
>> --- /dev/null
>> +++ b/hw/intc/riscv_clic.c
>> @@ -0,0 +1,835 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "sysemu/qtest.h"
>> +#include "target/riscv/cpu.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/intc/riscv_clic.h"
>> +
>> +/*
>> + * The 2-bit trig WARL field specifies the trigger type and polarity for
>> each
>> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
>> defined as
>> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
>> + */
>> +
>> +static inline TRIG_TYPE
>> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int npbits = clic->clicintctlbits - clic->nlbits;
>> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>> +
>> +    if (npbits < 0) {
>> +        return UINT8_MAX;
>> +    }
>> +    /* unused priority bits are set to 1 */
>> +    return (intctl & mask_priority) | mask_padding;
>> +}
>> +
>> +static void
>> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>> +                         uint8_t *mode, uint8_t *level,
>> +                         uint8_t *priority)
>> +{
>> +    *mode = intcfg >> 8;
>> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
>> +}
>> +
>> +/*
>> + * In a system with multiple harts, the M-mode CLIC regions for all the
>> harts
>> + * are placed contiguously in the memory space, followed by the S-mode
>> CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>> +static size_t
>> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t mode_offset = 0;
>> +    size_t unit = clic->num_harts * clic->num_sources;
>> +
>> +    switch (mode) {
>> +    case PRV_M:
>> +        mode_offset = 0;
>> +        break;
>> +    case PRV_S:
>> +        mode_offset = unit;
>> +        break;
>> +    case PRV_U:
>> +        mode_offset = clic->prv_s ? 2 * unit : unit;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid mode %d\n", mode);
>> +        exit(1);
>> +    }
>> +    return mode_offset + hartid * clic->num_sources + irq;
>> +}
>> +
>> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>> +{
>> +    /*
>> +     * Scan active list for highest priority pending interrupts
>> +     * comparing against this harts mintstatus register and interrupt
>> +     * the core if we have a higher priority interrupt to deliver
>> +     */
>> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>> +    CPURISCVState *env = &cpu->env;
>> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>> +
>> +    int il[4] = {
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>> +            clic->mintthresh), /* PRV_U */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>> +            clic->sintthresh), /* PRV_S */
>> +        0,                     /* reserverd */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>> +            clic->uintthresh)  /* PRV_M */
>> +    };
>> +
>> +    /* Get sorted list of enabled interrupts for this hart */
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>> +    size_t active_count = clic->active_count[hartid];
>> +    uint8_t mode, level, priority;
>> +
>> +    /* Loop through the enabled interrupts sorted by mode+priority+level
>> */
>> +    while (active_count) {
>> +        size_t irq_offset;
>> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>> +                                 &priority);
>> +        if (mode < env->priv || (mode == env->priv && level <=
>> il[mode])) {
>> +            /*
>> +             * No pending interrupts with high enough mode+priority+level
>> +             * break and clear pending interrupt for this hart
>> +             */
>> +            break;
>> +        }
>> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> active->irq);
>> +        /* Check pending interrupt with high enough mode+priority+level
>> */
>> +        if (clic->clicintip[irq_offset]) {
>> +            /* Clean vector edge-triggered pending */
>> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>> +                clic->clicintip[irq_offset] = 0;
>> +            }
>> +            /* Post pending interrupt for this hart */
>> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
>> 14;
>> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>> +            return;
>> +        }
>> +        /* Check next enabled interrupt */
>> +        active_count--;
>> +        active++;
>> +    }
>> +}
>> +
>> +/*
>> + * Any interrupt i that is not accessible to S-mode or U-Mode
>> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>> + */
>> +static bool
>> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
>> irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    if (!clic->prv_s && !clic->prv_u) { /* M */
>> +        return mode == PRV_M;
>>
>
> Does the mode here to be checked should be the current hart's privilege
> mode
> at the time register is been accessed?
>
> Yes, it is.
>
> After review the latest specification,  I think I have a  wrong
> understanding of CLIC
> memory map.
>
> S-mode or U-mode interrupt regions should just be a restricted view of
> M-mode interrupts.
>
> The check of visible  should not use the irq_offset here. And all writes
> to S-mode or U-mode
> interrupt region should also update the M-mode corresponding registers.
>
Does it also mean that we don't need to allocate IRQ registers for each
mode?
They should share the same set of M-mode IRQ registers if accessible.

Thanks,
Frank Chang


> Thanks,
> Zhiwei
>
>
>
> Regards,
> Frank Chang
>
>
>> +    } else if (!clic->prv_s) { /* M/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_U);
>> +        default:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 for M/U hart");
>> +            exit(1);
>> +        }
>> +    } else { /* M/S/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_S);
>> +        case 2:
>> +            return mode == clic->clicintattr[irq_offset];
>> +        case 3:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
>> +            exit(1);
>> +        }
>> +    }
>> +    return false;
>> +}
>> +
>> +/*
>> + * For level-triggered interrupts, software writes to pending bits are
>> + * ignored completely. (Section 3.4)
>> + */
>> +static bool
>> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t value)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = !!value;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +/*
>> + * For security purpose, the field can only be set to a privilege
>> + * level that is equal mode to or lower than the currently running
>> + * privilege level.(Section 3.6)
>> + */
>> +
>> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
>> value)
>> +{
>> +    int mode = extract64(value, 6, 2);
>> +
>> +    if (!qtest_enabled()) {
>> +        CPURISCVState *env = current_cpu->env_ptr;
>> +        if (env->priv < mode) {
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt
>> *i)
>> +{
>> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority
>> */
>> +           (i->irq & 0xfff);             /* Highest irq number */
>> +}
>> +
>> +static int riscv_clic_active_compare(const void *a, const void *b)
>> +{
>> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +
>> sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) /
>> sizeof(CLICActiveInterrupt);
>> +        size_t sz = (--(*active_count) - elem) *
>> sizeof(CLICActiveInterrupt);
>> +        assert(result);
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand
>> the bit
>> +             * field, from FW perspective clicintip[i]=zero means no
>> interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode =
>> extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>> +        /*
>> +         * The implemented bits are kept left-justified in the
>> most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/* Return target interrupt mode */
>> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>> +    switch (mode) {
>> +    case 0:
>> +        return PRV_M;
>> +    case 1:
>> +        assert(clic->prv_s || clic->prv_u);
>> +        return clic->prv_s ? PRV_S : PRV_U;
>> +    case 2:
>> +        assert(clic->prv_s && clic->prv_u);
>> +        return PRV_U;
>> +    default:
>> +        g_assert_not_reached();
>> +        break;
>> +    }
>> +}
>> +
>> +/* Return target hart id */
>> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>> +    int hart_unit = 4 * clic->num_sources;
>> +
>> +    return (addr % mode_unit) / hart_unit;
>> +}
>> +
>> +/* Return target interrupt number */
>> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int hart_unit = 4 * clic->num_sources;
>> +    return (addr % hart_unit) / 4;
>> +}
>> +
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned
>> size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                {
>> +                    uint8_t nlbits = extract32(value, 1, 4);
>> +                    uint8_t nmbits = extract32(value, 5, 2);
>> +
>> +                    /*
>> +                     * The 4-bit cliccfg.nlbits WARL field.
>> +                     * Valid values are 0—8.
>> +                     */
>> +                    if (nlbits <= 8) {
>> +                        clic->nlbits = nlbits;
>> +                    }
>> +                    /* Valid values are given by implemented priviledges
>> */
>> +                    if (clic->prv_s && clic->prv_u) {
>> +                        if (nmbits <= 2) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else if (clic->prv_u) {
>> +                        if (nmbits <= 1) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else {
>> +                        assert(!clic->prv_s);
>> +                        if (nmbits == 0) {
>> +                            clic->nmbits = 0;
>> +                        }
>> +                    }
>> +                    clic->nvbits = extract32(value, 0, 1);
>> +                    break;
>> +                }
>> +            case 1: /* clicinfo, read-only register */
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: write read-only clicinfo.\n");
>> +                break;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                {
>> +                    uint32_t interrupt_number = value &
>> MAKE_64BIT_MASK(0, 13);
>> +                    if (interrupt_number <= clic->num_sources) {
>> +                        value &= ~MAKE_64BIT_MASK(13, 18);
>> +                        clic->clicinttrig[index - 0x10] = value;
>> +                    }
>> +                    break;
>> +                }
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    clic->mintthresh = value;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                return;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                riscv_clic_hart_write(clic, addr, value, size, mode,
>> +                                      hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +}
>> +
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                /*
>> +                 * clicinttrig register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 enable
>> +                 * 30:13 reserved (WARL 0)
>> +                 * 12:0 interrupt_number
>> +                 */
>> +                return clic->clicinttrig[index - 0x10] &
>> +                       ~MAKE_64BIT_MASK(13, 18);
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    return clic->mintthresh;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
>> irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void riscv_clic_set_irq(void *opaque, int id, int level)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    int irq, hartid, mode;
>> +    hwaddr addr = 4 * id;
>> +    TRIG_TYPE type;
>> +
>> +    hartid = riscv_clic_get_hartid(clic, addr);
>> +    mode = riscv_clic_get_mode(clic, addr);
>> +    irq = riscv_clic_get_irq(clic, addr);
>> +    type = riscv_clic_get_trigger_type(clic, id);
>> +
>> +    /*
>> +     * In general, the edge-triggered interrupt state should be kept in
>> pending
>> +     * bit, while the level-triggered interrupt should be kept in the
>> level
>> +     * state of the incoming wire.
>> +     *
>> +     * For CLIC, model the level-triggered interrupt by read-only
>> pending bit.
>> +     */
>> +    if (level) {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +        case POSITIVE_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case NEG_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        case NEG_EDGE:
>> +            break;
>> +        }
>> +    } else {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case POSITIVE_EDGE:
>> +            break;
>> +        case NEG_LEVEL:
>> +        case NEG_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
>> +{
>> +    CPURISCVState *env = (CPURISCVState *)opaque;
>> +    RISCVCLICState *clic = env->clic;
>> +    CPUState *cpu = env_cpu(env);
>> +
>> +    if (level) {
>> +        env->exccode = clic->exccode[cpu->cpu_index];
>> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps riscv_clic_ops = {
>> +    .read = riscv_clic_read,
>> +    .write = riscv_clic_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 8
>> +    }
>> +};
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>> clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>> +
>> +    /* Allocate irq through gpio, so that we can use qtest */
>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>> +
>> +    for (i = 0; i < clic->num_harts; i++) {
>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>> +                                         &cpu->env, 1);
>> +        qdev_connect_gpio_out(dev, i, irq);
>> +        cpu->env.clic = clic;
>> +    }
>> +}
>> +
>> +static Property riscv_clic_properties[] = {
>> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
>> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
>> 0),
>> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = riscv_clic_realize;
>> +    device_class_set_props(dc, riscv_clic_properties);
>> +}
>> +
>> +static const TypeInfo riscv_clic_info = {
>> +    .name          = TYPE_RISCV_CLIC,
>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(RISCVCLICState),
>> +    .class_init    = riscv_clic_class_init,
>> +};
>> +
>> +static void riscv_clic_register_types(void)
>> +{
>> +    type_register_static(&riscv_clic_info);
>> +}
>> +
>> +type_init(riscv_clic_register_types)
>> +
>> +/*
>> + * riscv_clic_create:
>> + *
>> + * @addr: base address of M-Mode CLIC memory-mapped registers
>> + * @prv_s: have smode region
>> + * @prv_u: have umode region
>> + * @num_harts: number of CPU harts
>> + * @num_sources: number of interrupts supporting by each aperture
>> + * @clicintctlbits: bits are actually implemented in the clicintctl
>> registers
>> + * @version: clic version, such as "v0.9"
>> + *
>> + * Returns: the device object
>> + */
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version)
>> +{
>> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>> +
>> +    assert(num_sources <= 4096);
>> +    assert(num_harts <= 1024);
>> +    assert(clicintctlbits <= 8);
>> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>> +
>> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>> +    qdev_prop_set_string(dev, "version", version);
>> +
>> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>> +    return dev;
>> +}
>> +
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>> +}
>> +
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = 0;
>> +}
>> +
>> +/*
>> + * The new CLIC interrupt-handling mode is encoded as a new state in
>> + * the existing WARL xtvec register, where the low two bits of  are 11.
>> + */
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>> +{
>> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
>> +    return env->clic && ((xtvec & 0x3) == 3);
>> +}
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>> +                               int *il, int *irq)
>> +{
>> +    *irq = extract32(exccode, 0, 12);
>> +    *mode = extract32(exccode, 12, 2);
>> +    *il = extract32(exccode, 14, 8);
>> +}
>> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
>> new file mode 100644
>> index 0000000000..e5f89672a6
>> --- /dev/null
>> +++ b/include/hw/intc/riscv_clic.h
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef RISCV_CLIC_H
>> +#define RISCV_CLIC_H
>> +
>> +#include "hw/irq.h"
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_RISCV_CLIC "riscv_clic"
>> +#define RISCV_CLIC(obj) \
>> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>> +
>> +/*
>> + * CLIC per hart active interrupts
>> + *
>> + * We maintain per hart lists of enabled interrupts sorted by
>> + * mode+level+priority. The sorting is done on the configuration path
>> + * so that the interrupt delivery fastpath can linear scan enabled
>> + * interrupts in priority order.
>> + */
>> +typedef struct CLICActiveInterrupt {
>> +    uint16_t intcfg;
>> +    uint16_t irq;
>> +} CLICActiveInterrupt;
>> +
>> +typedef enum TRIG_TYPE {
>> +    POSITIVE_LEVEL,
>> +    POSITIVE_EDGE,
>> +    NEG_LEVEL,
>> +    NEG_EDGE,
>> +} TRIG_TYPE;
>> +
>> +typedef struct RISCVCLICState {
>> +    /*< private >*/
>> +    SysBusDevice parent_obj;
>> +
>> +    /*< public >*/
>> +
>> +    /* Implementaion parameters */
>> +    bool prv_s;
>> +    bool prv_u;
>> +    uint32_t num_harts;
>> +    uint32_t num_sources;
>> +    uint32_t clic_size;
>> +    uint32_t clic_mmode_base;
>> +    uint32_t clicintctlbits;
>> +    uint64_t mclicbase;
>> +    char *version;
>> +
>> +    /* Global configuration */
>> +    uint8_t nmbits;
>> +    uint8_t nlbits;
>> +    uint8_t nvbits;
>> +    uint32_t clicinfo;
>> +    uint32_t clicinttrig[32];
>> +
>> +    /* Aperture configuration */
>> +    uint8_t *clicintip;
>> +    uint8_t *clicintie;
>> +    uint8_t *clicintattr;
>> +    uint8_t *clicintctl;
>> +
>> +    /* Complatible with v0.8 */
>> +    uint32_t mintthresh;
>> +    uint32_t sintthresh;
>> +    uint32_t uintthresh;
>> +
>> +    /* QEMU implementaion related fields */
>> +    uint32_t *exccode;
>> +    CLICActiveInterrupt *active_list;
>> +    size_t *active_count;
>> +    MemoryRegion mmio;
>> +    qemu_irq *cpu_irqs;
>> +} RISCVCLICState;
>> +
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version);
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
>> *irq);
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq);
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>> +#endif
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index a5eab26a69..9e389d7bbf 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -33,6 +33,7 @@
>>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
>> @@ -247,6 +248,7 @@ struct CPURISCVState {
>>      /* Fields from here on are preserved across CPU reset. */
>>      QEMUTimer *timer; /* Internal timer */
>>      void *clic;       /* clic interrupt controller */
>> +    uint32_t exccode; /* clic irq encode */
>>  };
>>
>>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>> --
>> 2.25.1
>>
>>
>>
LIU Zhiwei June 28, 2021, 9:36 p.m. UTC | #27
On 2021/6/28 下午10:30, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年6月28日 週一 下午9:03寫道:
>
>
>     On 2021/6/28 下午6:16, Frank Chang wrote:
>>     LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於
>>     2021年4月9日 週五 下午3:57寫道:
>>
>>         The Core-Local Interrupt Controller (CLIC) provides low-latency,
>>         vectored, pre-emptive interrupts for RISC-V systems.
>>
>>         The CLIC also supports a new Selective Hardware Vectoring feature
>>         that allow users to optimize each interrupt for either faster
>>         response or smaller code size.
>>
>>         Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com
>>         <mailto:zhiwei_liu@c-sky.com>>
>>         ---
>>          default-configs/devices/riscv32-softmmu.mak |   1 +
>>          default-configs/devices/riscv64-softmmu.mak |   1 +
>>          hw/intc/Kconfig                             |   3 +
>>          hw/intc/meson.build                         |   1 +
>>          hw/intc/riscv_clic.c                        | 835
>>         ++++++++++++++++++++
>>          include/hw/intc/riscv_clic.h                | 103 +++
>>          target/riscv/cpu.h                          |   2 +
>>          7 files changed, 946 insertions(+)
>>          create mode 100644 hw/intc/riscv_clic.c
>>          create mode 100644 include/hw/intc/riscv_clic.h
>>
>>         diff --git a/default-configs/devices/riscv32-softmmu.mak
>>         b/default-configs/devices/riscv32-softmmu.mak
>>         index d847bd5692..1430c30588 100644
>>         --- a/default-configs/devices/riscv32-softmmu.mak
>>         +++ b/default-configs/devices/riscv32-softmmu.mak
>>         @@ -5,6 +5,7 @@
>>          #CONFIG_PCI_DEVICES=n
>>          CONFIG_SEMIHOSTING=y
>>          CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>>         +CONFIG_RISCV_CLIC=y
>>
>>          # Boards:
>>          #
>>         diff --git a/default-configs/devices/riscv64-softmmu.mak
>>         b/default-configs/devices/riscv64-softmmu.mak
>>         index d5eec75f05..396800bbbd 100644
>>         --- a/default-configs/devices/riscv64-softmmu.mak
>>         +++ b/default-configs/devices/riscv64-softmmu.mak
>>         @@ -5,6 +5,7 @@
>>          #CONFIG_PCI_DEVICES=n
>>          CONFIG_SEMIHOSTING=y
>>          CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>>         +CONFIG_RISCV_CLIC=y
>>
>>          # Boards:
>>          #
>>         diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>>         index f4694088a4..5bf492b48f 100644
>>         --- a/hw/intc/Kconfig
>>         +++ b/hw/intc/Kconfig
>>         @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>          config SIFIVE_PLIC
>>              bool
>>
>>         +config RISCV_CLIC
>>         +    bool
>>         +
>>          config GOLDFISH_PIC
>>              bool
>>
>>         diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>>         index 1c299039f6..2aa71b6738 100644
>>         --- a/hw/intc/meson.build
>>         +++ b/hw/intc/meson.build
>>         @@ -50,6 +50,7 @@ specific_ss.add(when:
>>         'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c'))
>>          specific_ss.add(when: 'CONFIG_SH_INTC', if_true:
>>         files('sh_intc.c'))
>>          specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>>         files('sifive_clint.c'))
>>          specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>>         files('sifive_plic.c'))
>>         +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>>         files('riscv_clic.c'))
>>          specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>          specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                         if_true: files('xics_kvm.c'))
>>         diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>>         new file mode 100644
>>         index 0000000000..8ad534c506
>>         --- /dev/null
>>         +++ b/hw/intc/riscv_clic.c
>>         @@ -0,0 +1,835 @@
>>         +/*
>>         + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>>         + *
>>         + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All
>>         rights reserved.
>>         + *
>>         + * This program is free software; you can redistribute it
>>         and/or modify it
>>         + * under the terms and conditions of the GNU General Public
>>         License,
>>         + * version 2 or later, as published by the Free Software
>>         Foundation.
>>         + *
>>         + * This program is distributed in the hope it will be
>>         useful, but WITHOUT
>>         + * ANY WARRANTY; without even the implied warranty of
>>         MERCHANTABILITY or
>>         + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
>>         Public License for
>>         + * more details.
>>         + *
>>         + * You should have received a copy of the GNU General Public
>>         License along with
>>         + * this program.  If not, see <http://www.gnu.org/licenses/
>>         <http://www.gnu.org/licenses/>>.
>>         + */
>>         +
>>         +#include "qemu/osdep.h"
>>         +#include "qapi/error.h"
>>         +#include "qemu/log.h"
>>         +#include "hw/sysbus.h"
>>         +#include "sysemu/qtest.h"
>>         +#include "target/riscv/cpu.h"
>>         +#include "hw/qdev-properties.h"
>>         +#include "hw/intc/riscv_clic.h"
>>         +
>>         +/*
>>         + * The 2-bit trig WARL field specifies the trigger type and
>>         polarity for each
>>         + * interrupt input. Bit 1, trig[0], is defined as
>>         "edge-triggered"
>>         + * (0: level-triggered, 1: edge-triggered); while bit 2,
>>         trig[1], is defined as
>>         + * "negative-edge" (0: positive-edge, 1: negative-edge).
>>         (Section 3.6)
>>         + */
>>         +
>>         +static inline TRIG_TYPE
>>         +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t
>>         irq_offset)
>>         +{
>>         +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>>         +}
>>         +
>>         +static inline bool
>>         +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t
>>         irq_offset)
>>         +{
>>         +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>>         +}
>>         +
>>         +static inline bool
>>         +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t
>>         irq_offset)
>>         +{
>>         +    return (clic->clicintattr[irq_offset] & 0x1) &&
>>         clic->nvbits;
>>         +}
>>         +
>>         +static uint8_t
>>         +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t
>>         intctl)
>>         +{
>>         +    int nlbits = clic->nlbits;
>>         +
>>         +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>>         +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>>         +    /* unused level bits are set to 1 */
>>         +    return (intctl & mask_il) | mask_padding;
>>         +}
>>         +
>>         +static uint8_t
>>         +riscv_clic_get_interrupt_priority(RISCVCLICState *clic,
>>         uint8_t intctl)
>>         +{
>>         +    int npbits = clic->clicintctlbits - clic->nlbits;
>>         +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>>         +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>>         +
>>         +    if (npbits < 0) {
>>         +        return UINT8_MAX;
>>         +    }
>>         +    /* unused priority bits are set to 1 */
>>         +    return (intctl & mask_priority) | mask_padding;
>>         +}
>>         +
>>         +static void
>>         +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>>         +                         uint8_t *mode, uint8_t *level,
>>         +                         uint8_t *priority)
>>         +{
>>         +    *mode = intcfg >> 8;
>>         +    *level = riscv_clic_get_interrupt_level(clic, intcfg &
>>         0xff);
>>         +    *priority = riscv_clic_get_interrupt_priority(clic,
>>         intcfg & 0xff);
>>         +}
>>         +
>>         +/*
>>         + * In a system with multiple harts, the M-mode CLIC regions
>>         for all the harts
>>         + * are placed contiguously in the memory space, followed by
>>         the S-mode CLIC
>>         + * regions for all harts. (Section 3.11)
>>         + */
>>         +static size_t
>>         +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode,
>>         int hartid, int irq)
>>         +{
>>         +    size_t mode_offset = 0;
>>         +    size_t unit = clic->num_harts * clic->num_sources;
>>         +
>>         +    switch (mode) {
>>         +    case PRV_M:
>>         +        mode_offset = 0;
>>         +        break;
>>         +    case PRV_S:
>>         +        mode_offset = unit;
>>         +        break;
>>         +    case PRV_U:
>>         +        mode_offset = clic->prv_s ? 2 * unit : unit;
>>         +        break;
>>         +    default:
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid mode %d\n", mode);
>>         +        exit(1);
>>         +    }
>>         +    return mode_offset + hartid * clic->num_sources + irq;
>>         +}
>>         +
>>         +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>>         +{
>>         +    /*
>>         +     * Scan active list for highest priority pending interrupts
>>         +     * comparing against this harts mintstatus register and
>>         interrupt
>>         +     * the core if we have a higher priority interrupt to
>>         deliver
>>         +     */
>>         +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>>         +    CPURISCVState *env = &cpu->env;
>>         +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>>         +
>>         +    int il[4] = {
>>         +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>>         +            clic->mintthresh), /* PRV_U */
>>         +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>>         +            clic->sintthresh), /* PRV_S */
>>         +        0,                     /* reserverd */
>>         +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>>         +            clic->uintthresh)  /* PRV_M */
>>         +    };
>>         +
>>         +    /* Get sorted list of enabled interrupts for this hart */
>>         +    size_t hart_offset = hartid * clic->num_sources;
>>         +    CLICActiveInterrupt *active =
>>         &clic->active_list[hart_offset];
>>         +    size_t active_count = clic->active_count[hartid];
>>         +    uint8_t mode, level, priority;
>>         +
>>         +    /* Loop through the enabled interrupts sorted by
>>         mode+priority+level */
>>         +    while (active_count) {
>>         +        size_t irq_offset;
>>         +        riscv_clic_intcfg_decode(clic, active->intcfg,
>>         &mode, &level,
>>         +                                 &priority);
>>         +        if (mode < env->priv || (mode == env->priv && level
>>         <= il[mode])) {
>>         +            /*
>>         +             * No pending interrupts with high enough
>>         mode+priority+level
>>         +             * break and clear pending interrupt for this hart
>>         +             */
>>         +            break;
>>         +        }
>>         +        irq_offset = riscv_clic_get_irq_offset(clic, mode,
>>         hartid, active->irq);
>>         +        /* Check pending interrupt with high enough
>>         mode+priority+level */
>>         +        if (clic->clicintip[irq_offset]) {
>>         +            /* Clean vector edge-triggered pending */
>>         +            if (riscv_clic_is_edge_triggered(clic,
>>         irq_offset) &&
>>         +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>>         +                clic->clicintip[irq_offset] = 0;
>>         +            }
>>         +            /* Post pending interrupt for this hart */
>>         +            clic->exccode[hartid] = active->irq | mode << 12
>>         | level << 14;
>>         + qemu_set_irq(clic->cpu_irqs[hartid], 1);
>>         +            return;
>>         +        }
>>         +        /* Check next enabled interrupt */
>>         +        active_count--;
>>         +        active++;
>>         +    }
>>         +}
>>         +
>>         +/*
>>         + * Any interrupt i that is not accessible to S-mode or U-Mode
>>         + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>>         + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>>         + */
>>         +static bool
>>         +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int
>>         hartid, int irq)
>>         +{
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    if (!clic->prv_s && !clic->prv_u) { /* M */
>>         +        return mode == PRV_M;
>>
>>
>>     Does the mode here to be checked should be the current hart's
>>     privilege mode
>>     at the time register is been accessed?
>>
>     Yes, it is.
>
>     After review the latest specification,  I think I have a  wrong
>     understanding of CLIC
>     memory map.
>
>     S-mode or U-mode interrupt regions should just be a restricted
>     view of M-mode interrupts.
>
>     The check of visible  should not use the irq_offset here. And all
>     writes to S-mode or U-mode
>     interrupt region should also update the M-mode corresponding
>     registers.
>
> Does it also mean that we don't need to allocate IRQ registers for 
> each mode?
> They should share the same set of M-mode IRQ registers if accessible.
>
I think so. For external device, they doesn't know which privilege mode 
interrupt they should raise.
The privilege mode is maintained in M-mode CLIC memory region.

Thanks,
Zhiwei

> Thanks,
> Frank Chang
>
>     Thanks,
>     Zhiwei
>
>
>
>>     Regards,
>>     Frank Chang
>>
>>         +    } else if (!clic->prv_s) { /* M/U */
>>         +        switch (clic->nmbits) {
>>         +        case 0:
>>         +            return mode == PRV_M;
>>         +        case 1:
>>         +            return clic->clicintattr[irq_offset] & 0x80 ?
>>         (mode == PRV_M) :
>>         +         (mode == PRV_U);
>>         +        default:
>>         +            qemu_log_mask(LOG_GUEST_ERROR,
>>         +                "clic: nmbits can only be 0 or 1 for M/U hart");
>>         +            exit(1);
>>         +        }
>>         +    } else { /* M/S/U */
>>         +        switch (clic->nmbits) {
>>         +        case 0:
>>         +            return mode == PRV_M;
>>         +        case 1:
>>         +            return clic->clicintattr[irq_offset] & 0x80 ?
>>         (mode == PRV_M) :
>>         +         (mode == PRV_S);
>>         +        case 2:
>>         +            return mode == clic->clicintattr[irq_offset];
>>         +        case 3:
>>         +            qemu_log_mask(LOG_GUEST_ERROR,
>>         +                "clic: nmbits can only be 0 or 1 or 2 for
>>         M/S/U hart");
>>         +            exit(1);
>>         +        }
>>         +    }
>>         +    return false;
>>         +}
>>         +
>>         +/*
>>         + * For level-triggered interrupts, software writes to
>>         pending bits are
>>         + * ignored completely. (Section 3.4)
>>         + */
>>         +static bool
>>         +riscv_clic_validate_intip(RISCVCLICState *clic, int mode,
>>         int hartid, int irq)
>>         +{
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>>         +}
>>         +
>>         +static void
>>         +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int
>>         hartid,
>>         +                        int irq, uint64_t value)
>>         +{
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    clic->clicintip[irq_offset] = !!value;
>>         +    riscv_clic_next_interrupt(clic, hartid);
>>         +}
>>         +
>>         +/*
>>         + * For security purpose, the field can only be set to a
>>         privilege
>>         + * level that is equal mode to or lower than the currently
>>         running
>>         + * privilege level.(Section 3.6)
>>         + */
>>         +
>>         +static bool riscv_clic_validate_intattr(RISCVCLICState
>>         *clic, uint64_t value)
>>         +{
>>         +    int mode = extract64(value, 6, 2);
>>         +
>>         +    if (!qtest_enabled()) {
>>         +        CPURISCVState *env = current_cpu->env_ptr;
>>         +        if (env->priv < mode) {
>>         +            return false;
>>         +        }
>>         +    }
>>         +    return true;
>>         +}
>>         +
>>         +static inline int riscv_clic_encode_priority(const
>>         CLICActiveInterrupt *i)
>>         +{
>>         +    return ((i->intcfg & 0x3ff) << 12) | /* Highest
>>         mode+level+priority */
>>         +           (i->irq & 0xfff);  /* Highest irq number */
>>         +}
>>         +
>>         +static int riscv_clic_active_compare(const void *a, const
>>         void *b)
>>         +{
>>         +    return riscv_clic_encode_priority(b) -
>>         riscv_clic_encode_priority(a);
>>         +}
>>         +
>>         +static void
>>         +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int
>>         hartid,
>>         +                        int irq, uint64_t new_intie)
>>         +{
>>         +    size_t hart_offset = hartid * clic->num_sources;
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    CLICActiveInterrupt *active_list =
>>         &clic->active_list[hart_offset];
>>         +    size_t *active_count = &clic->active_count[hartid];
>>         +
>>         +    uint8_t old_intie = clic->clicintie[irq_offset];
>>         +    clic->clicintie[irq_offset] = !!new_intie;
>>         +
>>         +    /* Add to or remove from list of active interrupts */
>>         +    if (new_intie && !old_intie) {
>>         +        active_list[*active_count].intcfg = (mode << 8) |
>>         + clic->clicintctl[irq_offset];
>>         +        active_list[*active_count].irq = irq;
>>         +        (*active_count)++;
>>         +    } else if (!new_intie && old_intie) {
>>         +        CLICActiveInterrupt key = {
>>         +            (mode << 8) | clic->clicintctl[irq_offset], irq
>>         +        };
>>         +        CLICActiveInterrupt *result = bsearch(&key,
>>         + active_list, *active_count,
>>         + sizeof(CLICActiveInterrupt),
>>         + riscv_clic_active_compare);
>>         +        size_t elem = (result - active_list) /
>>         sizeof(CLICActiveInterrupt);
>>         +        size_t sz = (--(*active_count) - elem) *
>>         sizeof(CLICActiveInterrupt);
>>         +        assert(result);
>>         +        memmove(&result[0], &result[1], sz);
>>         +    }
>>         +
>>         +    /* Sort list of active interrupts */
>>         +    qsort(active_list, *active_count,
>>         +          sizeof(CLICActiveInterrupt),
>>         +          riscv_clic_active_compare);
>>         +
>>         +    riscv_clic_next_interrupt(clic, hartid);
>>         +}
>>         +
>>         +static void
>>         +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>>         +                      uint64_t value, unsigned size,
>>         +                      int mode, int hartid, int irq)
>>         +{
>>         +    int req = extract32(addr, 0, 2);
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +
>>         +    if (hartid >= clic->num_harts) {
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid hartid %u: 0x%"
>>         HWADDR_PRIx "\n",
>>         +                      hartid, addr);
>>         +        return;
>>         +    }
>>         +
>>         +    if (irq >= clic->num_sources) {
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid irq %u: 0x%"
>>         HWADDR_PRIx "\n", irq, addr);
>>         +        return;
>>         +    }
>>         +
>>         +    switch (req) {
>>         +    case 0: /* clicintip[i] */
>>         +        if (riscv_clic_validate_intip(clic, mode, hartid,
>>         irq)) {
>>         +            /*
>>         +             * The actual pending bit is located at bit 0
>>         (i.e., the
>>         +             * leastsignificant bit). In case future
>>         extensions expand the bit
>>         +             * field, from FW perspective clicintip[i]=zero
>>         means no interrupt
>>         +             * pending, and clicintip[i]!=0 (not just 1)
>>         indicates an
>>         +             * interrupt is pending. (Section 3.4)
>>         +             */
>>         +            if (value != clic->clicintip[irq_offset]) {
>>         +                riscv_clic_update_intip(clic, mode, hartid,
>>         irq, value);
>>         +            }
>>         +        }
>>         +        break;
>>         +    case 1: /* clicintie[i] */
>>         +        if (clic->clicintie[irq_offset] != value) {
>>         +            riscv_clic_update_intie(clic, mode, hartid, irq,
>>         value);
>>         +        }
>>         +        break;
>>         +    case 2: /* clicintattr[i] */
>>         +        if (riscv_clic_validate_intattr(clic, value)) {
>>         +            if (clic->clicintattr[irq_offset] != value) {
>>         +                /* When nmbits=2, check WARL */
>>         +                bool invalid = (clic->nmbits == 2) &&
>>         +                               (extract64(value, 6, 2) == 0b10);
>>         +                if (invalid) {
>>         +                    uint8_t old_mode =
>>         extract32(clic->clicintattr[irq_offset],
>>         +  6, 2);
>>         +                    value = deposit32(value, 6, 2, old_mode);
>>         +                }
>>         +                clic->clicintattr[irq_offset] = value;
>>         +                riscv_clic_next_interrupt(clic, hartid);
>>         +            }
>>         +        }
>>         +        break;
>>         +    case 3: /* clicintctl[i] */
>>         +        if (value != clic->clicintctl[irq_offset]) {
>>         +            clic->clicintctl[irq_offset] = value;
>>         +            riscv_clic_next_interrupt(clic, hartid);
>>         +        }
>>         +        break;
>>         +    }
>>         +}
>>         +
>>         +static uint64_t
>>         +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int
>>         mode,
>>         +                     int hartid, int irq)
>>         +{
>>         +    int req = extract32(addr, 0, 2);
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +
>>         +    if (hartid >= clic->num_harts) {
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid hartid %u: 0x%"
>>         HWADDR_PRIx "\n",
>>         +                      hartid, addr);
>>         +        return 0;
>>         +    }
>>         +
>>         +    if (irq >= clic->num_sources) {
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid irq %u: 0x%"
>>         HWADDR_PRIx "\n", irq, addr);
>>         +        return 0;
>>         +    }
>>         +
>>         +    switch (req) {
>>         +    case 0: /* clicintip[i] */
>>         +        return clic->clicintip[irq_offset];
>>         +    case 1: /* clicintie[i] */
>>         +        return clic->clicintie[irq_offset];
>>         +    case 2: /* clicintattr[i] */
>>         +        /*
>>         +         * clicintattr register layout
>>         +         * Bits Field
>>         +         * 7:6 mode
>>         +         * 5:3 reserved (WPRI 0)
>>         +         * 2:1 trig
>>         +         * 0 shv
>>         +         */
>>         +        return clic->clicintattr[irq_offset] & ~0x38;
>>         +    case 3: /* clicintctrl */
>>         +        /*
>>         +         * The implemented bits are kept left-justified in
>>         the most-significant
>>         +         * bits of each 8-bit clicintctl[i] register, with
>>         the lower
>>         +         * unimplemented bits treated as hardwired to
>>         1.(Section 3.7)
>>         +         */
>>         +        return clic->clicintctl[irq_offset] |
>>         +               ((1 << (8 - clic->clicintctlbits)) - 1);
>>         +    }
>>         +
>>         +    return 0;
>>         +}
>>         +
>>         +/* Return target interrupt mode */
>>         +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr
>>         addr)
>>         +{
>>         +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>>         +    switch (mode) {
>>         +    case 0:
>>         +        return PRV_M;
>>         +    case 1:
>>         +        assert(clic->prv_s || clic->prv_u);
>>         +        return clic->prv_s ? PRV_S : PRV_U;
>>         +    case 2:
>>         +        assert(clic->prv_s && clic->prv_u);
>>         +        return PRV_U;
>>         +    default:
>>         +        g_assert_not_reached();
>>         +        break;
>>         +    }
>>         +}
>>         +
>>         +/* Return target hart id */
>>         +static int riscv_clic_get_hartid(RISCVCLICState *clic,
>>         hwaddr addr)
>>         +{
>>         +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>>         +    int hart_unit = 4 * clic->num_sources;
>>         +
>>         +    return (addr % mode_unit) / hart_unit;
>>         +}
>>         +
>>         +/* Return target interrupt number */
>>         +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>>         +{
>>         +    int hart_unit = 4 * clic->num_sources;
>>         +    return (addr % hart_unit) / 4;
>>         +}
>>         +
>>         +static void
>>         +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value,
>>         unsigned size)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    hwaddr clic_size = clic->clic_size;
>>         +    int hartid, mode, irq;
>>         +
>>         +    if (addr < clic_size) {
>>         +        if (addr < 0x1000) {
>>         +            assert(addr % 4 == 0);
>>         +            int index = addr / 4;
>>         +            switch (index) {
>>         +            case 0: /* cliccfg */
>>         +                {
>>         +                    uint8_t nlbits = extract32(value, 1, 4);
>>         +                    uint8_t nmbits = extract32(value, 5, 2);
>>         +
>>         +                    /*
>>         +                     * The 4-bit cliccfg.nlbits WARL field.
>>         +                     * Valid values are 0—8.
>>         +                     */
>>         +                    if (nlbits <= 8) {
>>         +                        clic->nlbits = nlbits;
>>         +                    }
>>         +                    /* Valid values are given by implemented
>>         priviledges */
>>         +                    if (clic->prv_s && clic->prv_u) {
>>         +                        if (nmbits <= 2) {
>>         +                            clic->nmbits = nmbits;
>>         +                        }
>>         +                    } else if (clic->prv_u) {
>>         +                        if (nmbits <= 1) {
>>         +                            clic->nmbits = nmbits;
>>         +                        }
>>         +                    } else {
>>         +                        assert(!clic->prv_s);
>>         +                        if (nmbits == 0) {
>>         +                            clic->nmbits = 0;
>>         +                        }
>>         +                    }
>>         +                    clic->nvbits = extract32(value, 0, 1);
>>         +                    break;
>>         +                }
>>         +            case 1: /* clicinfo, read-only register */
>>         +                qemu_log_mask(LOG_GUEST_ERROR,
>>         +                              "clic: write read-only
>>         clicinfo.\n");
>>         +                break;
>>         +            case 0x10 ... 0x2F: /* clicinttrig */
>>         +                {
>>         +                    uint32_t interrupt_number = value &
>>         MAKE_64BIT_MASK(0, 13);
>>         +                    if (interrupt_number <= clic->num_sources) {
>>         +                        value &= ~MAKE_64BIT_MASK(13, 18);
>>         + clic->clicinttrig[index - 0x10] = value;
>>         +                    }
>>         +                    break;
>>         +                }
>>         +            case 2: /* mintthresh */
>>         +                if (!strcmp(clic->version, "v0.8")) {
>>         +                    clic->mintthresh = value;
>>         +                    break;
>>         +                }
>>         +                qemu_log_mask(LOG_GUEST_ERROR,
>>         +                              "clic: invalid write addr:
>>         0x%" HWADDR_PRIx "\n",
>>         +                              addr);
>>         +                break;
>>         +            default:
>>         +                qemu_log_mask(LOG_GUEST_ERROR,
>>         +                              "clic: invalid write addr:
>>         0x%" HWADDR_PRIx "\n",
>>         +                              addr);
>>         +                return;
>>         +            }
>>         +        } else {
>>         +            addr -= 0x1000;
>>         +            hartid = riscv_clic_get_hartid(clic, addr);
>>         +            mode = riscv_clic_get_mode(clic, addr);
>>         +            irq = riscv_clic_get_irq(clic, addr);
>>         +
>>         +            if (riscv_clic_check_visible(clic, mode, hartid,
>>         irq)) {
>>         +                riscv_clic_hart_write(clic, addr, value,
>>         size, mode,
>>         +                                      hartid, irq);
>>         +            }
>>         +        }
>>         +    } else {
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid write: 0x%" HWADDR_PRIx
>>         "\n", addr);
>>         +    }
>>         +}
>>         +
>>         +static uint64_t riscv_clic_read(void *opaque, hwaddr addr,
>>         unsigned size)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    hwaddr clic_size = clic->clic_size;
>>         +    int hartid, mode, irq;
>>         +
>>         +    if (addr < clic_size) {
>>         +        if (addr < 0x1000) {
>>         +            assert(addr % 4 == 0);
>>         +            int index = addr / 4;
>>         +            switch (index) {
>>         +            case 0: /* cliccfg */
>>         +                return clic->nvbits |
>>         +                       (clic->nlbits << 1) |
>>         +                       (clic->nmbits << 5);
>>         +            case 1: /* clicinfo */
>>         +                /*
>>         +                 * clicinfo register layout
>>         +                 *
>>         +                 * Bits Field
>>         +                 * 31 reserved (WARL 0)
>>         +                 * 30:25 num_trigger
>>         +                 * 24:21 CLICINTCTLBITS
>>         +                 * 20:13 version (for version control)
>>         +                 * 12:0 num_interrupt
>>         +                 */
>>         +                return clic->clicinfo & ~INT32_MAX;
>>         +            case 0x10 ... 0x2F: /* clicinttrig */
>>         +                /*
>>         +                 * clicinttrig register layout
>>         +                 *
>>         +                 * Bits Field
>>         +                 * 31 enable
>>         +                 * 30:13 reserved (WARL 0)
>>         +                 * 12:0 interrupt_number
>>         +                 */
>>         +                return clic->clicinttrig[index - 0x10] &
>>         +                       ~MAKE_64BIT_MASK(13, 18);
>>         +            case 2: /* mintthresh */
>>         +                if (!strcmp(clic->version, "v0.8")) {
>>         +                    return clic->mintthresh;
>>         +                    break;
>>         +                }
>>         +                qemu_log_mask(LOG_GUEST_ERROR,
>>         +                              "clic: invalid read : 0x%"
>>         HWADDR_PRIx "\n",
>>         +                              addr);
>>         +                break;
>>         +            default:
>>         +                qemu_log_mask(LOG_GUEST_ERROR,
>>         +                              "clic: invalid read : 0x%"
>>         HWADDR_PRIx "\n",
>>         +                              addr);
>>         +                break;
>>         +            }
>>         +        } else {
>>         +            addr -= 0x1000;
>>         +            hartid = riscv_clic_get_hartid(clic, addr);
>>         +            mode = riscv_clic_get_mode(clic, addr);
>>         +            irq = riscv_clic_get_irq(clic, addr);
>>         +
>>         +            if (riscv_clic_check_visible(clic, mode, hartid,
>>         irq)) {
>>         +                return riscv_clic_hart_read(clic, addr,
>>         mode, hartid, irq);
>>         +            }
>>         +        }
>>         +    } else {
>>         +        qemu_log_mask(LOG_GUEST_ERROR,
>>         +                      "clic: invalid read: 0x%" HWADDR_PRIx
>>         "\n", addr);
>>         +    }
>>         +    return 0;
>>         +}
>>         +
>>         +static void riscv_clic_set_irq(void *opaque, int id, int level)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    int irq, hartid, mode;
>>         +    hwaddr addr = 4 * id;
>>         +    TRIG_TYPE type;
>>         +
>>         +    hartid = riscv_clic_get_hartid(clic, addr);
>>         +    mode = riscv_clic_get_mode(clic, addr);
>>         +    irq = riscv_clic_get_irq(clic, addr);
>>         +    type = riscv_clic_get_trigger_type(clic, id);
>>         +
>>         +    /*
>>         +     * In general, the edge-triggered interrupt state should
>>         be kept in pending
>>         +     * bit, while the level-triggered interrupt should be
>>         kept in the level
>>         +     * state of the incoming wire.
>>         +     *
>>         +     * For CLIC, model the level-triggered interrupt by
>>         read-only pending bit.
>>         +     */
>>         +    if (level) {
>>         +        switch (type) {
>>         +        case POSITIVE_LEVEL:
>>         +        case POSITIVE_EDGE:
>>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>>         level);
>>         +            break;
>>         +        case NEG_LEVEL:
>>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>>         !level);
>>         +            break;
>>         +        case NEG_EDGE:
>>         +            break;
>>         +        }
>>         +    } else {
>>         +        switch (type) {
>>         +        case POSITIVE_LEVEL:
>>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>>         level);
>>         +            break;
>>         +        case POSITIVE_EDGE:
>>         +            break;
>>         +        case NEG_LEVEL:
>>         +        case NEG_EDGE:
>>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>>         !level);
>>         +            break;
>>         +        }
>>         +    }
>>         +}
>>         +
>>         +static void riscv_clic_cpu_irq_handler(void *opaque, int
>>         irq, int level)
>>         +{
>>         +    CPURISCVState *env = (CPURISCVState *)opaque;
>>         +    RISCVCLICState *clic = env->clic;
>>         +    CPUState *cpu = env_cpu(env);
>>         +
>>         +    if (level) {
>>         +        env->exccode = clic->exccode[cpu->cpu_index];
>>         +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>>         +    }
>>         +}
>>         +
>>         +static const MemoryRegionOps riscv_clic_ops = {
>>         +    .read = riscv_clic_read,
>>         +    .write = riscv_clic_write,
>>         +    .endianness = DEVICE_LITTLE_ENDIAN,
>>         +    .valid = {
>>         +        .min_access_size = 1,
>>         +        .max_access_size = 8
>>         +    }
>>         +};
>>         +
>>         +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>>         +{
>>         +    RISCVCLICState *clic = RISCV_CLIC(dev);
>>         +    size_t harts_x_sources = clic->num_harts *
>>         clic->num_sources;
>>         +    int irqs, i;
>>         +
>>         +    if (clic->prv_s && clic->prv_u) {
>>         +        irqs = 3 * harts_x_sources;
>>         +    } else if (clic->prv_s || clic->prv_u) {
>>         +        irqs = 2 * harts_x_sources;
>>         +    } else {
>>         +        irqs = harts_x_sources;
>>         +    }
>>         +
>>         +    clic->clic_size = irqs * 4 + 0x1000;
>>         +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>>         &riscv_clic_ops, clic,
>>         +                          TYPE_RISCV_CLIC, clic->clic_size);
>>         +
>>         +    clic->clicintip = g_new0(uint8_t, irqs);
>>         +    clic->clicintie = g_new0(uint8_t, irqs);
>>         +    clic->clicintattr = g_new0(uint8_t, irqs);
>>         +    clic->clicintctl = g_new0(uint8_t, irqs);
>>         +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>         +    clic->active_count = g_new0(size_t, clic->num_harts);
>>         +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>>         +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>>         +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>>         +
>>         +    /* Allocate irq through gpio, so that we can use qtest */
>>         +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>>         +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>>         +
>>         +    for (i = 0; i < clic->num_harts; i++) {
>>         +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>>         +        qemu_irq irq =
>>         qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>>         +  &cpu->env, 1);
>>         +        qdev_connect_gpio_out(dev, i, irq);
>>         +        cpu->env.clic = clic;
>>         +    }
>>         +}
>>         +
>>         +static Property riscv_clic_properties[] = {
>>         +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>>         +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>>         +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState,
>>         num_harts, 0),
>>         +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState,
>>         num_sources, 0),
>>         +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState,
>>         clicintctlbits, 0),
>>         +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState,
>>         mclicbase, 0),
>>         +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>>         +    DEFINE_PROP_END_OF_LIST(),
>>         +};
>>         +
>>         +static void riscv_clic_class_init(ObjectClass *klass, void
>>         *data)
>>         +{
>>         +    DeviceClass *dc = DEVICE_CLASS(klass);
>>         +
>>         +    dc->realize = riscv_clic_realize;
>>         +    device_class_set_props(dc, riscv_clic_properties);
>>         +}
>>         +
>>         +static const TypeInfo riscv_clic_info = {
>>         +    .name          = TYPE_RISCV_CLIC,
>>         +    .parent        = TYPE_SYS_BUS_DEVICE,
>>         +    .instance_size = sizeof(RISCVCLICState),
>>         +    .class_init    = riscv_clic_class_init,
>>         +};
>>         +
>>         +static void riscv_clic_register_types(void)
>>         +{
>>         +    type_register_static(&riscv_clic_info);
>>         +}
>>         +
>>         +type_init(riscv_clic_register_types)
>>         +
>>         +/*
>>         + * riscv_clic_create:
>>         + *
>>         + * @addr: base address of M-Mode CLIC memory-mapped registers
>>         + * @prv_s: have smode region
>>         + * @prv_u: have umode region
>>         + * @num_harts: number of CPU harts
>>         + * @num_sources: number of interrupts supporting by each
>>         aperture
>>         + * @clicintctlbits: bits are actually implemented in the
>>         clicintctl registers
>>         + * @version: clic version, such as "v0.9"
>>         + *
>>         + * Returns: the device object
>>         + */
>>         +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool
>>         prv_u,
>>         +                               uint32_t num_harts, uint32_t
>>         num_sources,
>>         +                               uint8_t clicintctlbits,
>>         +                               const char *version)
>>         +{
>>         +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>>         +
>>         +    assert(num_sources <= 4096);
>>         +    assert(num_harts <= 1024);
>>         +    assert(clicintctlbits <= 8);
>>         +    assert(!strcmp(version, "v0.8") || !strcmp(version,
>>         "v0.9"));
>>         +
>>         +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>>         +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>>         +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>>         +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>>         +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>>         +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>>         +    qdev_prop_set_string(dev, "version", version);
>>         +
>>         +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>>         +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>>         +    return dev;
>>         +}
>>         +
>>         +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    riscv_clic_next_interrupt(clic, hartid);
>>         +}
>>         +
>>         +bool riscv_clic_shv_interrupt(void *opaque, int mode, int
>>         hartid, int irq)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>>         +}
>>         +
>>         +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>>         hartid, int irq)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>>         +}
>>         +
>>         +void riscv_clic_clean_pending(void *opaque, int mode, int
>>         hartid, int irq)
>>         +{
>>         +    RISCVCLICState *clic = opaque;
>>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic,
>>         mode, hartid, irq);
>>         +    clic->clicintip[irq_offset] = 0;
>>         +}
>>         +
>>         +/*
>>         + * The new CLIC interrupt-handling mode is encoded as a new
>>         state in
>>         + * the existing WARL xtvec register, where the low two bits
>>         of  are 11.
>>         + */
>>         +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>>         +{
>>         +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec :
>>         env->stvec;
>>         +    return env->clic && ((xtvec & 0x3) == 3);
>>         +}
>>         +
>>         +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>>         +                               int *il, int *irq)
>>         +{
>>         +    *irq = extract32(exccode, 0, 12);
>>         +    *mode = extract32(exccode, 12, 2);
>>         +    *il = extract32(exccode, 14, 8);
>>         +}
>>         diff --git a/include/hw/intc/riscv_clic.h
>>         b/include/hw/intc/riscv_clic.h
>>         new file mode 100644
>>         index 0000000000..e5f89672a6
>>         --- /dev/null
>>         +++ b/include/hw/intc/riscv_clic.h
>>         @@ -0,0 +1,103 @@
>>         +/*
>>         + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>>         + *
>>         + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All
>>         rights reserved.
>>         + *
>>         + * This program is free software; you can redistribute it
>>         and/or modify it
>>         + * under the terms and conditions of the GNU General Public
>>         License,
>>         + * version 2 or later, as published by the Free Software
>>         Foundation.
>>         + *
>>         + * This program is distributed in the hope it will be
>>         useful, but WITHOUT
>>         + * ANY WARRANTY; without even the implied warranty of
>>         MERCHANTABILITY or
>>         + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
>>         Public License for
>>         + * more details.
>>         + *
>>         + * You should have received a copy of the GNU General Public
>>         License along with
>>         + * this program.  If not, see <http://www.gnu.org/licenses/
>>         <http://www.gnu.org/licenses/>>.
>>         + */
>>         +
>>         +#ifndef RISCV_CLIC_H
>>         +#define RISCV_CLIC_H
>>         +
>>         +#include "hw/irq.h"
>>         +#include "hw/sysbus.h"
>>         +
>>         +#define TYPE_RISCV_CLIC "riscv_clic"
>>         +#define RISCV_CLIC(obj) \
>>         +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>>         +
>>         +/*
>>         + * CLIC per hart active interrupts
>>         + *
>>         + * We maintain per hart lists of enabled interrupts sorted by
>>         + * mode+level+priority. The sorting is done on the
>>         configuration path
>>         + * so that the interrupt delivery fastpath can linear scan
>>         enabled
>>         + * interrupts in priority order.
>>         + */
>>         +typedef struct CLICActiveInterrupt {
>>         +    uint16_t intcfg;
>>         +    uint16_t irq;
>>         +} CLICActiveInterrupt;
>>         +
>>         +typedef enum TRIG_TYPE {
>>         +    POSITIVE_LEVEL,
>>         +    POSITIVE_EDGE,
>>         +    NEG_LEVEL,
>>         +    NEG_EDGE,
>>         +} TRIG_TYPE;
>>         +
>>         +typedef struct RISCVCLICState {
>>         +    /*< private >*/
>>         +    SysBusDevice parent_obj;
>>         +
>>         +    /*< public >*/
>>         +
>>         +    /* Implementaion parameters */
>>         +    bool prv_s;
>>         +    bool prv_u;
>>         +    uint32_t num_harts;
>>         +    uint32_t num_sources;
>>         +    uint32_t clic_size;
>>         +    uint32_t clic_mmode_base;
>>         +    uint32_t clicintctlbits;
>>         +    uint64_t mclicbase;
>>         +    char *version;
>>         +
>>         +    /* Global configuration */
>>         +    uint8_t nmbits;
>>         +    uint8_t nlbits;
>>         +    uint8_t nvbits;
>>         +    uint32_t clicinfo;
>>         +    uint32_t clicinttrig[32];
>>         +
>>         +    /* Aperture configuration */
>>         +    uint8_t *clicintip;
>>         +    uint8_t *clicintie;
>>         +    uint8_t *clicintattr;
>>         +    uint8_t *clicintctl;
>>         +
>>         +    /* Complatible with v0.8 */
>>         +    uint32_t mintthresh;
>>         +    uint32_t sintthresh;
>>         +    uint32_t uintthresh;
>>         +
>>         +    /* QEMU implementaion related fields */
>>         +    uint32_t *exccode;
>>         +    CLICActiveInterrupt *active_list;
>>         +    size_t *active_count;
>>         +    MemoryRegion mmio;
>>         +    qemu_irq *cpu_irqs;
>>         +} RISCVCLICState;
>>         +
>>         +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool
>>         prv_u,
>>         +                               uint32_t num_harts, uint32_t
>>         num_sources,
>>         +                               uint8_t clicintctlbits,
>>         +                               const char *version);
>>         +
>>         +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>>         int *il, int *irq);
>>         +void riscv_clic_clean_pending(void *opaque, int mode, int
>>         hartid, int irq);
>>         +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>>         hartid, int irq);
>>         +bool riscv_clic_shv_interrupt(void *opaque, int mode, int
>>         hartid, int irq);
>>         +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>>         +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>>         +#endif
>>         diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>>         index a5eab26a69..9e389d7bbf 100644
>>         --- a/target/riscv/cpu.h
>>         +++ b/target/riscv/cpu.h
>>         @@ -33,6 +33,7 @@
>>          #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>          #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>          #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>>         +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>          #define TYPE_RISCV_CPU_ANY RISCV_CPU_TYPE_NAME("any")
>>          #define TYPE_RISCV_CPU_BASE32  RISCV_CPU_TYPE_NAME("rv32")
>>         @@ -247,6 +248,7 @@ struct CPURISCVState {
>>              /* Fields from here on are preserved across CPU reset. */
>>              QEMUTimer *timer; /* Internal timer */
>>              void *clic;       /* clic interrupt controller */
>>         +    uint32_t exccode; /* clic irq encode */
>>          };
>>
>>          OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>>         -- 
>>         2.25.1
>>
>>
LIU Zhiwei June 29, 2021, 2:43 a.m. UTC | #28
Hi Frank,

Thanks for a lot of good points.

On 2021/6/26 下午11:03, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年4月9日 週五 下午3:57寫道:
>
>     +static uint8_t
>     +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>     +{
>     +    int nlbits = clic->nlbits;
>     +
>     +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>     +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>     +    /* unused level bits are set to 1 */
>     +    return (intctl & mask_il) | mask_padding;
>     +}
>
>
> According to spec:
>   if the nlbits > CLICINTCTLBITS, then the lower bits of the 8-bit
>   interrupt level are assumed to be all 1s.
>
> That is, the valid nlbits should be: min(clic->nlbits, CLICINTCTLBITS);
> The cliccfg example in spec also shows that:
>
> CLICINTCTLBITS  nlbits  clicintctl[i]  interrupt levels
>       0                       2         ........         255
>       1                       2         l.......  127,255
>       2                       2         ll......  63,127,191,255
>       3                       3         lll..... 
>  31,63,95,127,159,191,223,255
>       4                       1         lppp.... 127,255
Agree, thanks.
>
>
>     + * In a system with multiple harts, the M-mode CLIC regions for
>     all the harts
>     + * are placed contiguously in the memory space, followed by the
>     S-mode CLIC
>     + * regions for all harts. (Section 3.11)
>     + */
>
>
> The above description is not true any more in the latest spec:
>   The CLIC specification does not dictate how CLIC memory-mapped 
> registers are
>   split between M/S/U regions as well as the layout of multiple harts 
> as this is generally
>   a platform issue and each platform needs to define a discovery 
> mechanism to determine
>   the memory map locations.
>
> But I think we can just keep the current design for now anyway, as 
> it's also one of legal memory layout.
> Otherwise, the design would be more complicated I think.

We can pass an array containing indexed by hart_id and mode from the 
machine board, such as

hwaddr clic_memmap[HARTS][MODE] = {

{0x0000, 0x10000, 0x20000},

{0x4000, 0x14000, 0x24000},

{0x8000, 0x18000, 0x28000},

{0xc000, 0x1c000, 0x2c000},

}


>
>
>     +static void
>     +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>     +                        int irq, uint64_t new_intie)
>     +{
>     +    size_t hart_offset = hartid * clic->num_sources;
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +    CLICActiveInterrupt *active_list =
>     &clic->active_list[hart_offset];
>     +    size_t *active_count = &clic->active_count[hartid];
>     +
>     +    uint8_t old_intie = clic->clicintie[irq_offset];
>     +    clic->clicintie[irq_offset] = !!new_intie;
>     +
>     +    /* Add to or remove from list of active interrupts */
>     +    if (new_intie && !old_intie) {
>     +        active_list[*active_count].intcfg = (mode << 8) |
>     + clic->clicintctl[irq_offset];
>     +        active_list[*active_count].irq = irq;
>     +        (*active_count)++;
>     +    } else if (!new_intie && old_intie) {
>     +        CLICActiveInterrupt key = {
>     +            (mode << 8) | clic->clicintctl[irq_offset], irq
>     +        };
>     +        CLICActiveInterrupt *result = bsearch(&key,
>     +                                              active_list,
>     *active_count,
>     + sizeof(CLICActiveInterrupt),
>     + riscv_clic_active_compare);
>     +        size_t elem = (result - active_list) /
>     sizeof(CLICActiveInterrupt);
>
>
> I think what you are trying to do here is to get the number of elements
> right after the active interrupt to be deleted in order to calculate 
> the size of
> active interrupts to be memmoved.
>
Agree, thanks.
> However, according to C spec:
>   When two pointers are subtracted, both shall point to elements of 
> the same array object,
>   or one past the last element of the array object; the result is the 
> difference of the
>   subscripts of the two array elements.
>
> So I think: (result - active_list) is already the number of elements 
> you want.
> You don't have to divide it with sizeof(CLICActiveInterrupt) again.
>
>     +        size_t sz = (--(*active_count) - elem) *
>     sizeof(CLICActiveInterrupt);
>     +        assert(result);
>
>
> Nit: assert(result) can be moved above size_t elem statement.
Agree.
>
>     +        memmove(&result[0], &result[1], sz);
>     +    }
>     +
>     +    /* Sort list of active interrupts */
>     +    qsort(active_list, *active_count,
>     +          sizeof(CLICActiveInterrupt),
>     +          riscv_clic_active_compare);
>     +
>     +    riscv_clic_next_interrupt(clic, hartid);
>     +}
>     +
>     +static void
>     +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>     +                      uint64_t value, unsigned size,
>     +                      int mode, int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return;
>     +    }
>     +
>     +    switch (req) {
>
>
> Spec. says that it's legal to write 32-bit value to set
> {clicintctl[i], clicintattr[i], clicintie[i] ,clicintip[i]} at the 
> same time:
>   A 32-bit write to {clicintctl,clicintattr,clicintie,clicintip} is legal.
>   However, there is no specified order in which the effects of
>   the individual byte updates take effect.

I miss it. I think it only supports 1 byte access or 4 bytes access. For 
4 bytes access,  we need to pass an flag to specify to the order from 
the machine board.

Thoughts?

>     +    case 0: /* clicintip[i] */
>     +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>     +            /*
>     +             * The actual pending bit is located at bit 0 (i.e., the
>     +             * leastsignificant bit). In case future extensions
>     expand the bit
>
>
> Typo: leastsignificant => least significant
OK.
>
>     +             * field, from FW perspective clicintip[i]=zero means
>     no interrupt
>     +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>     +             * interrupt is pending. (Section 3.4)
>     +             */
>     +            if (value != clic->clicintip[irq_offset]) {
>     +                riscv_clic_update_intip(clic, mode, hartid, irq,
>     value);
>     +            }
>     +        }
>     +        break;
>     +    case 1: /* clicintie[i] */
>     +        if (clic->clicintie[irq_offset] != value) {
>     +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>     +        }
>     +        break;
>     +    case 2: /* clicintattr[i] */
>     +        if (riscv_clic_validate_intattr(clic, value)) {
>     +            if (clic->clicintattr[irq_offset] != value) {
>     +                /* When nmbits=2, check WARL */
>     +                bool invalid = (clic->nmbits == 2) &&
>     +                               (extract64(value, 6, 2) == 0b10);
>     +                if (invalid) {
>     +                    uint8_t old_mode =
>     extract32(clic->clicintattr[irq_offset],
>     +                                                 6, 2);
>     +                    value = deposit32(value, 6, 2, old_mode);
>     +                }
>     +                clic->clicintattr[irq_offset] = value;
>     +                riscv_clic_next_interrupt(clic, hartid);
>     +            }
>     +        }
>     +        break;
>     +    case 3: /* clicintctl[i] */
>     +        if (value != clic->clicintctl[irq_offset]) {
>     +            clic->clicintctl[irq_offset] = value;
>
>
> If irq i is already in the active_list, when will its intcfg been synced?
Good. I think should sync immediately.
>
>     +            riscv_clic_next_interrupt(clic, hartid);
>     +        }
>     +        break;
>     +    }
>     +}
>     +
>     +static uint64_t
>     +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>     +                     int hartid, int irq)
>     +{
>     +    int req = extract32(addr, 0, 2);
>     +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>     hartid, irq);
>     +
>     +    if (hartid >= clic->num_harts) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx
>     "\n",
>     +                      hartid, addr);
>     +        return 0;
>     +    }
>     +
>     +    if (irq >= clic->num_sources) {
>     +        qemu_log_mask(LOG_GUEST_ERROR,
>     +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>     "\n", irq, addr);
>     +        return 0;
>     +    }
>     +
>     +    switch (req) {
>     +    case 0: /* clicintip[i] */
>     +        return clic->clicintip[irq_offset];
>     +    case 1: /* clicintie[i] */
>     +        return clic->clicintie[irq_offset];
>     +    case 2: /* clicintattr[i] */
>     +        /*
>     +         * clicintattr register layout
>     +         * Bits Field
>     +         * 7:6 mode
>     +         * 5:3 reserved (WPRI 0)
>     +         * 2:1 trig
>     +         * 0 shv
>     +         */
>     +        return clic->clicintattr[irq_offset] & ~0x38;
>     +    case 3: /* clicintctrl */
>
>
> Typo: clicintctl
OK.
>
>     +        /*
>     +         * The implemented bits are kept left-justified in the
>     most-significant
>     +         * bits of each 8-bit clicintctl[i] register, with the lower
>     +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>     +         */
>     +        return clic->clicintctl[irq_offset] |
>     +               ((1 << (8 - clic->clicintctlbits)) - 1);
>     +    }
>     +
>     +    return 0;
>     +}
>     +
>
>     +static void
>     +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>
>
> Is this necessary?
> I think memory region size already limits the request address to be 
> within the range of clic_size.

At this point, not necessary.

>
>     +static uint64_t riscv_clic_read(void *opaque, hwaddr addr,
>     unsigned size)
>     +{
>     +    RISCVCLICState *clic = opaque;
>     +    hwaddr clic_size = clic->clic_size;
>     +    int hartid, mode, irq;
>     +
>     +    if (addr < clic_size) {
>
>
> Same to riscv_clic_write().
>
> Thanks,
> Frank Chang
>
>     +        if (addr < 0x1000) {
>     +            assert(addr % 4 == 0);
>     +            int index = addr / 4;
>     +            switch (index) {
>     +            case 0: /* cliccfg */
>     +                return clic->nvbits |
>     +                       (clic->nlbits << 1) |
>     +                       (clic->nmbits << 5);
>     +            case 1: /* clicinfo */
>     +                /*
>     +                 * clicinfo register layout
>     +                 *
>     +                 * Bits Field
>     +                 * 31 reserved (WARL 0)
>     +                 * 30:25 num_trigger
>     +                 * 24:21 CLICINTCTLBITS
>     +                 * 20:13 version (for version control)
>     +                 * 12:0 num_interrupt
>     +                 */
>     +                return clic->clicinfo & ~INT32_MAX;
>
>
> clic->clicinfo should represent the CLIC setting information.
> I think it's better to add clic reset function or 
> in riscv_clic_realize() to initialize clic->clicinfo.
Agree.
>
>     +
>     +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>     +{
>     +    RISCVCLICState *clic = RISCV_CLIC(dev);
>     +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>     +    int irqs, i;
>     +
>     +    if (clic->prv_s && clic->prv_u) {
>     +        irqs = 3 * harts_x_sources;
>     +    } else if (clic->prv_s || clic->prv_u) {
>     +        irqs = 2 * harts_x_sources;
>     +    } else {
>     +        irqs = harts_x_sources;
>     +    }
>     +
>     +    clic->clic_size = irqs * 4 + 0x1000;
>     +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>     &riscv_clic_ops, clic,
>     +                          TYPE_RISCV_CLIC, clic->clic_size);
>     +
>     +    clic->clicintip = g_new0(uint8_t, irqs);
>     +    clic->clicintie = g_new0(uint8_t, irqs);
>     +    clic->clicintattr = g_new0(uint8_t, irqs);
>     +    clic->clicintctl = g_new0(uint8_t, irqs);
>     +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>
>
> Should the size of clic->active_list be: harts_x_sources?

Every irq can be in the active_list, so just the number of irqs.

Remind we will only use M-mode IRQ in next patch set, I still think we 
should use the
irqs here, because irq in active_list has privilege information.

Thanks,
Zhiwei

>
>
LIU Zhiwei June 29, 2021, 2:50 a.m. UTC | #29
On 2021/6/26 下午11:20, Frank Chang wrote:
> LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於 
> 2021年4月9日 週五 下午3:57寫道:
>
>
>     +
>     +/*
>     + * riscv_clic_create:
>     + *
>     + * @addr: base address of M-Mode CLIC memory-mapped registers
>     + * @prv_s: have smode region
>     + * @prv_u: have umode region
>     + * @num_harts: number of CPU harts
>     + * @num_sources: number of interrupts supporting by each aperture
>     + * @clicintctlbits: bits are actually implemented in the
>     clicintctl registers
>     + * @version: clic version, such as "v0.9"
>     + *
>     + * Returns: the device object
>     + */
>     +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>     +                               uint32_t num_harts, uint32_t
>     num_sources,
>     +                               uint8_t clicintctlbits,
>     +                               const char *version)
>     +{
>     +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>     +
>     +    assert(num_sources <= 4096);
>     +    assert(num_harts <= 1024);
>     +    assert(clicintctlbits <= 8);
>     +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>     +
>     +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>     +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>     +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>     +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>     +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>     +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>
>
> According to spec:
>   Since the CLIC memory map must be aligned at a 4KiB boundary,
>   the mclicbase CSR has its 12 least-significant bits hardwired to zero.
>   It is used to inform software about the location of CLIC memory 
> mappped registers.
>
> I think it's better to add another addr check to ensure it's 4KiB aligned.
>
Agree.

Thanks,
Zhiwei

> Thanks,
> Frank Chang
>
>     +    qdev_prop_set_string(dev, "version", version);
>     +
>     +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>     +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>     +    return dev;
>     +}
>     +
>
LIU Zhiwei June 29, 2021, 2:52 a.m. UTC | #30
On 2021/6/26 下午11:26, Frank Chang wrote:
> Frank Chang <frank.chang@sifive.com <mailto:frank.chang@sifive.com>> 於 
> 2021年6月26日 週六 下午11:03寫道:
>
>     LIU Zhiwei <zhiwei_liu@c-sky.com <mailto:zhiwei_liu@c-sky.com>> 於
>     2021年4月9日 週五 下午3:57寫道:
>
>         The Core-Local Interrupt Controller (CLIC) provides low-latency,
>         vectored, pre-emptive interrupts for RISC-V systems.
>
>         The CLIC also supports a new Selective Hardware Vectoring feature
>         that allow users to optimize each interrupt for either faster
>         response or smaller code size.
>
>         Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com
>         <mailto:zhiwei_liu@c-sky.com>>
>         ---
>          default-configs/devices/riscv32-softmmu.mak |   1 +
>          default-configs/devices/riscv64-softmmu.mak |   1 +
>          hw/intc/Kconfig                             |   3 +
>          hw/intc/meson.build                         |   1 +
>          hw/intc/riscv_clic.c                        | 835
>         ++++++++++++++++++++
>          include/hw/intc/riscv_clic.h                | 103 +++
>          target/riscv/cpu.h                          |   2 +
>          7 files changed, 946 insertions(+)
>          create mode 100644 hw/intc/riscv_clic.c
>          create mode 100644 include/hw/intc/riscv_clic.h
>
>         diff --git a/default-configs/devices/riscv32-softmmu.mak
>         b/default-configs/devices/riscv32-softmmu.mak
>         index d847bd5692..1430c30588 100644
>         --- a/default-configs/devices/riscv32-softmmu.mak
>         +++ b/default-configs/devices/riscv32-softmmu.mak
>         @@ -5,6 +5,7 @@
>          #CONFIG_PCI_DEVICES=n
>          CONFIG_SEMIHOSTING=y
>          CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>         +CONFIG_RISCV_CLIC=y
>
>          # Boards:
>          #
>         diff --git a/default-configs/devices/riscv64-softmmu.mak
>         b/default-configs/devices/riscv64-softmmu.mak
>         index d5eec75f05..396800bbbd 100644
>         --- a/default-configs/devices/riscv64-softmmu.mak
>         +++ b/default-configs/devices/riscv64-softmmu.mak
>         @@ -5,6 +5,7 @@
>          #CONFIG_PCI_DEVICES=n
>          CONFIG_SEMIHOSTING=y
>          CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>         +CONFIG_RISCV_CLIC=y
>
>          # Boards:
>          #
>         diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>         index f4694088a4..5bf492b48f 100644
>         --- a/hw/intc/Kconfig
>         +++ b/hw/intc/Kconfig
>         @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>          config SIFIVE_PLIC
>              bool
>
>         +config RISCV_CLIC
>         +    bool
>         +
>          config GOLDFISH_PIC
>              bool
>
>         diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>         index 1c299039f6..2aa71b6738 100644
>         --- a/hw/intc/meson.build
>         +++ b/hw/intc/meson.build
>         @@ -50,6 +50,7 @@ specific_ss.add(when:
>         'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c'))
>          specific_ss.add(when: 'CONFIG_SH_INTC', if_true:
>         files('sh_intc.c'))
>          specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>         files('sifive_clint.c'))
>          specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>         files('sifive_plic.c'))
>         +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>         files('riscv_clic.c'))
>          specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>          specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                         if_true: files('xics_kvm.c'))
>         diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>         new file mode 100644
>         index 0000000000..8ad534c506
>         --- /dev/null
>         +++ b/hw/intc/riscv_clic.c
>         @@ -0,0 +1,835 @@
>         +/*
>         + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>         + *
>         + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All
>         rights reserved.
>         + *
>         + * This program is free software; you can redistribute it
>         and/or modify it
>         + * under the terms and conditions of the GNU General Public
>         License,
>         + * version 2 or later, as published by the Free Software
>         Foundation.
>         + *
>         + * This program is distributed in the hope it will be useful,
>         but WITHOUT
>         + * ANY WARRANTY; without even the implied warranty of
>         MERCHANTABILITY or
>         + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
>         Public License for
>         + * more details.
>         + *
>         + * You should have received a copy of the GNU General Public
>         License along with
>         + * this program.  If not, see <http://www.gnu.org/licenses/
>         <http://www.gnu.org/licenses/>>.
>         + */
>         +
>         +#include "qemu/osdep.h"
>         +#include "qapi/error.h"
>         +#include "qemu/log.h"
>         +#include "hw/sysbus.h"
>         +#include "sysemu/qtest.h"
>         +#include "target/riscv/cpu.h"
>         +#include "hw/qdev-properties.h"
>         +#include "hw/intc/riscv_clic.h"
>         +
>         +/*
>         + * The 2-bit trig WARL field specifies the trigger type and
>         polarity for each
>         + * interrupt input. Bit 1, trig[0], is defined as
>         "edge-triggered"
>         + * (0: level-triggered, 1: edge-triggered); while bit 2,
>         trig[1], is defined as
>         + * "negative-edge" (0: positive-edge, 1: negative-edge).
>         (Section 3.6)
>         + */
>         +
>         +static inline TRIG_TYPE
>         +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t
>         irq_offset)
>         +{
>         +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>         +}
>         +
>         +static inline bool
>         +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t
>         irq_offset)
>         +{
>         +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>         +}
>         +
>         +static inline bool
>         +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t
>         irq_offset)
>         +{
>         +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>         +}
>         +
>         +static uint8_t
>         +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t
>         intctl)
>         +{
>         +    int nlbits = clic->nlbits;
>         +
>         +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>         +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>         +    /* unused level bits are set to 1 */
>         +    return (intctl & mask_il) | mask_padding;
>         +}
>
>
>     According to spec:
>       if the nlbits > CLICINTCTLBITS, then the lower bits of the 8-bit
>       interrupt level are assumed to be all 1s.
>
>     That is, the valid nlbits should be: min(clic->nlbits,
>     CLICINTCTLBITS);
>     The cliccfg example in spec also shows that:
>
>     CLICINTCTLBITS  nlbits  clicintctl[i]  interrupt levels
>           0                       2         ........    255
>           1                       2         l.......    127,255
>           2                       2         ll......    63,127,191,255
>           3                       3         lll.....  
>      31,63,95,127,159,191,223,255
>           4                       1         lppp.... 127,255
>
>         +
>         +static uint8_t
>         +riscv_clic_get_interrupt_priority(RISCVCLICState *clic,
>         uint8_t intctl)
>         +{
>         +    int npbits = clic->clicintctlbits - clic->nlbits;
>         +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>         +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>         +
>         +    if (npbits < 0) {
>         +        return UINT8_MAX;
>         +    }
>         +    /* unused priority bits are set to 1 */
>         +    return (intctl & mask_priority) | mask_padding;
>         +}
>         +
>         +static void
>         +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>         +                         uint8_t *mode, uint8_t *level,
>         +                         uint8_t *priority)
>         +{
>         +    *mode = intcfg >> 8;
>         +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>         +    *priority = riscv_clic_get_interrupt_priority(clic,
>         intcfg & 0xff);
>         +}
>         +
>         +/*
>         + * In a system with multiple harts, the M-mode CLIC regions
>         for all the harts
>         + * are placed contiguously in the memory space, followed by
>         the S-mode CLIC
>         + * regions for all harts. (Section 3.11)
>         + */
>
>
>     The above description is not true any more in the latest spec:
>       The CLIC specification does not dictate how CLIC memory-mapped
>     registers are
>       split between M/S/U regions as well as the layout of multiple
>     harts as this is generally
>       a platform issue and each platform needs to define a discovery
>     mechanism to determine
>       the memory map locations.
>
>
> Comparing to 20210217 spec, the original description you wrote is correct.
> But it's sad that the latest change is still in v0.9-draft, no version 
> is bumped.
>
> Minor:
> I think most of the description in comments may need to be updated
> if you want to update to the latest v0.9-draft spec.
> Do you have any plans to follow the latest changes?

Yes, maybe this week or next.

Thanks,
Zhiwei

>
> Regards,
> Frank Chang
>
>
>     But I think we can just keep the current design for now anyway, as
>     it's also one of legal memory layout.
>     Otherwise, the design would be more complicated I think.
>
>
>         +static size_t
>         +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int
>         hartid, int irq)
>         +{
>         +    size_t mode_offset = 0;
>         +    size_t unit = clic->num_harts * clic->num_sources;
>         +
>         +    switch (mode) {
>         +    case PRV_M:
>         +        mode_offset = 0;
>         +        break;
>         +    case PRV_S:
>         +        mode_offset = unit;
>         +        break;
>         +    case PRV_U:
>         +        mode_offset = clic->prv_s ? 2 * unit : unit;
>         +        break;
>         +    default:
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid mode %d\n", mode);
>         +        exit(1);
>         +    }
>         +    return mode_offset + hartid * clic->num_sources + irq;
>         +}
>         +
>         +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>         +{
>         +    /*
>         +     * Scan active list for highest priority pending interrupts
>         +     * comparing against this harts mintstatus register and
>         interrupt
>         +     * the core if we have a higher priority interrupt to deliver
>         +     */
>         +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>         +    CPURISCVState *env = &cpu->env;
>         +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>         +
>         +    int il[4] = {
>         +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>         +            clic->mintthresh), /* PRV_U */
>         +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>         +            clic->sintthresh), /* PRV_S */
>         +        0,                     /* reserverd */
>         +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>         +            clic->uintthresh)  /* PRV_M */
>         +    };
>         +
>         +    /* Get sorted list of enabled interrupts for this hart */
>         +    size_t hart_offset = hartid * clic->num_sources;
>         +    CLICActiveInterrupt *active =
>         &clic->active_list[hart_offset];
>         +    size_t active_count = clic->active_count[hartid];
>         +    uint8_t mode, level, priority;
>         +
>         +    /* Loop through the enabled interrupts sorted by
>         mode+priority+level */
>         +    while (active_count) {
>         +        size_t irq_offset;
>         +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode,
>         &level,
>         +                                 &priority);
>         +        if (mode < env->priv || (mode == env->priv && level
>         <= il[mode])) {
>         +            /*
>         +             * No pending interrupts with high enough
>         mode+priority+level
>         +             * break and clear pending interrupt for this hart
>         +             */
>         +            break;
>         +        }
>         +        irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, active->irq);
>         +        /* Check pending interrupt with high enough
>         mode+priority+level */
>         +        if (clic->clicintip[irq_offset]) {
>         +            /* Clean vector edge-triggered pending */
>         +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>         +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>         +                clic->clicintip[irq_offset] = 0;
>         +            }
>         +            /* Post pending interrupt for this hart */
>         +            clic->exccode[hartid] = active->irq | mode << 12
>         | level << 14;
>         +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>         +            return;
>         +        }
>         +        /* Check next enabled interrupt */
>         +        active_count--;
>         +        active++;
>         +    }
>         +}
>         +
>         +/*
>         + * Any interrupt i that is not accessible to S-mode or U-Mode
>         + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>         + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>         + */
>         +static bool
>         +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int
>         hartid, int irq)
>         +{
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    if (!clic->prv_s && !clic->prv_u) { /* M */
>         +        return mode == PRV_M;
>         +    } else if (!clic->prv_s) { /* M/U */
>         +        switch (clic->nmbits) {
>         +        case 0:
>         +            return mode == PRV_M;
>         +        case 1:
>         +            return clic->clicintattr[irq_offset] & 0x80 ?
>         (mode == PRV_M) :
>         +     (mode == PRV_U);
>         +        default:
>         +            qemu_log_mask(LOG_GUEST_ERROR,
>         +                "clic: nmbits can only be 0 or 1 for M/U hart");
>         +            exit(1);
>         +        }
>         +    } else { /* M/S/U */
>         +        switch (clic->nmbits) {
>         +        case 0:
>         +            return mode == PRV_M;
>         +        case 1:
>         +            return clic->clicintattr[irq_offset] & 0x80 ?
>         (mode == PRV_M) :
>         +     (mode == PRV_S);
>         +        case 2:
>         +            return mode == clic->clicintattr[irq_offset];
>         +        case 3:
>         +            qemu_log_mask(LOG_GUEST_ERROR,
>         +                "clic: nmbits can only be 0 or 1 or 2 for
>         M/S/U hart");
>         +            exit(1);
>         +        }
>         +    }
>         +    return false;
>         +}
>         +
>         +/*
>         + * For level-triggered interrupts, software writes to pending
>         bits are
>         + * ignored completely. (Section 3.4)
>         + */
>         +static bool
>         +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int
>         hartid, int irq)
>         +{
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>         +}
>         +
>         +static void
>         +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int
>         hartid,
>         +                        int irq, uint64_t value)
>         +{
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    clic->clicintip[irq_offset] = !!value;
>         +    riscv_clic_next_interrupt(clic, hartid);
>         +}
>         +
>         +/*
>         + * For security purpose, the field can only be set to a privilege
>         + * level that is equal mode to or lower than the currently
>         running
>         + * privilege level.(Section 3.6)
>         + */
>         +
>         +static bool riscv_clic_validate_intattr(RISCVCLICState *clic,
>         uint64_t value)
>         +{
>         +    int mode = extract64(value, 6, 2);
>         +
>         +    if (!qtest_enabled()) {
>         +        CPURISCVState *env = current_cpu->env_ptr;
>         +        if (env->priv < mode) {
>         +            return false;
>         +        }
>         +    }
>         +    return true;
>         +}
>         +
>         +static inline int riscv_clic_encode_priority(const
>         CLICActiveInterrupt *i)
>         +{
>         +    return ((i->intcfg & 0x3ff) << 12) | /* Highest
>         mode+level+priority */
>         +           (i->irq & 0xfff);             /* Highest irq number */
>         +}
>         +
>         +static int riscv_clic_active_compare(const void *a, const
>         void *b)
>         +{
>         +    return riscv_clic_encode_priority(b) -
>         riscv_clic_encode_priority(a);
>         +}
>         +
>         +static void
>         +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int
>         hartid,
>         +                        int irq, uint64_t new_intie)
>         +{
>         +    size_t hart_offset = hartid * clic->num_sources;
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    CLICActiveInterrupt *active_list =
>         &clic->active_list[hart_offset];
>         +    size_t *active_count = &clic->active_count[hartid];
>         +
>         +    uint8_t old_intie = clic->clicintie[irq_offset];
>         +    clic->clicintie[irq_offset] = !!new_intie;
>         +
>         +    /* Add to or remove from list of active interrupts */
>         +    if (new_intie && !old_intie) {
>         +        active_list[*active_count].intcfg = (mode << 8) |
>         + clic->clicintctl[irq_offset];
>         +        active_list[*active_count].irq = irq;
>         +        (*active_count)++;
>         +    } else if (!new_intie && old_intie) {
>         +        CLICActiveInterrupt key = {
>         +            (mode << 8) | clic->clicintctl[irq_offset], irq
>         +        };
>         +        CLICActiveInterrupt *result = bsearch(&key,
>         + active_list, *active_count,
>         + sizeof(CLICActiveInterrupt),
>         + riscv_clic_active_compare);
>         +        size_t elem = (result - active_list) /
>         sizeof(CLICActiveInterrupt);
>
>
>     I think what you are trying to do here is to get the number of
>     elements
>     right after the active interrupt to be deleted in order to
>     calculate the size of
>     active interrupts to be memmoved.
>
>     However, according to C spec:
>       When two pointers are subtracted, both shall point to elements
>     of the same array object,
>       or one past the last element of the array object; the result is
>     the difference of the
>       subscripts of the two array elements.
>
>     So I think: (result - active_list) is already the number of
>     elements you want.
>     You don't have to divide it with sizeof(CLICActiveInterrupt) again.
>
>         +        size_t sz = (--(*active_count) - elem) *
>         sizeof(CLICActiveInterrupt);
>         +        assert(result);
>
>
>     Nit: assert(result) can be moved above size_t elem statement.
>
>         +        memmove(&result[0], &result[1], sz);
>         +    }
>         +
>         +    /* Sort list of active interrupts */
>         +    qsort(active_list, *active_count,
>         +          sizeof(CLICActiveInterrupt),
>         +          riscv_clic_active_compare);
>         +
>         +    riscv_clic_next_interrupt(clic, hartid);
>         +}
>         +
>         +static void
>         +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>         +                      uint64_t value, unsigned size,
>         +                      int mode, int hartid, int irq)
>         +{
>         +    int req = extract32(addr, 0, 2);
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +
>         +    if (hartid >= clic->num_harts) {
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid hartid %u: 0x%"
>         HWADDR_PRIx "\n",
>         +                      hartid, addr);
>         +        return;
>         +    }
>         +
>         +    if (irq >= clic->num_sources) {
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>         "\n", irq, addr);
>         +        return;
>         +    }
>         +
>         +    switch (req) {
>
>
>     Spec. says that it's legal to write 32-bit value to set
>     {clicintctl[i], clicintattr[i], clicintie[i] ,clicintip[i]} at the
>     same time:
>       A 32-bit write to {clicintctl,clicintattr,clicintie,clicintip}
>     is legal.
>       However, there is no specified order in which the effects of
>       the individual byte updates take effect.
>
>         +    case 0: /* clicintip[i] */
>         +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>         +            /*
>         +             * The actual pending bit is located at bit 0
>         (i.e., the
>         +             * leastsignificant bit). In case future
>         extensions expand the bit
>
>
>     Typo: leastsignificant => least significant
>
>         +             * field, from FW perspective clicintip[i]=zero
>         means no interrupt
>         +             * pending, and clicintip[i]!=0 (not just 1)
>         indicates an
>         +             * interrupt is pending. (Section 3.4)
>         +             */
>         +            if (value != clic->clicintip[irq_offset]) {
>         +                riscv_clic_update_intip(clic, mode, hartid,
>         irq, value);
>         +            }
>         +        }
>         +        break;
>         +    case 1: /* clicintie[i] */
>         +        if (clic->clicintie[irq_offset] != value) {
>         +            riscv_clic_update_intie(clic, mode, hartid, irq,
>         value);
>         +        }
>         +        break;
>         +    case 2: /* clicintattr[i] */
>         +        if (riscv_clic_validate_intattr(clic, value)) {
>         +            if (clic->clicintattr[irq_offset] != value) {
>         +                /* When nmbits=2, check WARL */
>         +                bool invalid = (clic->nmbits == 2) &&
>         +                               (extract64(value, 6, 2) == 0b10);
>         +                if (invalid) {
>         +                    uint8_t old_mode =
>         extract32(clic->clicintattr[irq_offset],
>         +                                                 6, 2);
>         +                    value = deposit32(value, 6, 2, old_mode);
>         +                }
>         +                clic->clicintattr[irq_offset] = value;
>         +                riscv_clic_next_interrupt(clic, hartid);
>         +            }
>         +        }
>         +        break;
>         +    case 3: /* clicintctl[i] */
>         +        if (value != clic->clicintctl[irq_offset]) {
>         +            clic->clicintctl[irq_offset] = value;
>
>
>     If irq i is already in the active_list, when will its intcfg been
>     synced?
>
>         +            riscv_clic_next_interrupt(clic, hartid);
>         +        }
>         +        break;
>         +    }
>         +}
>         +
>         +static uint64_t
>         +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>         +                     int hartid, int irq)
>         +{
>         +    int req = extract32(addr, 0, 2);
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +
>         +    if (hartid >= clic->num_harts) {
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid hartid %u: 0x%"
>         HWADDR_PRIx "\n",
>         +                      hartid, addr);
>         +        return 0;
>         +    }
>         +
>         +    if (irq >= clic->num_sources) {
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx
>         "\n", irq, addr);
>         +        return 0;
>         +    }
>         +
>         +    switch (req) {
>         +    case 0: /* clicintip[i] */
>         +        return clic->clicintip[irq_offset];
>         +    case 1: /* clicintie[i] */
>         +        return clic->clicintie[irq_offset];
>         +    case 2: /* clicintattr[i] */
>         +        /*
>         +         * clicintattr register layout
>         +         * Bits Field
>         +         * 7:6 mode
>         +         * 5:3 reserved (WPRI 0)
>         +         * 2:1 trig
>         +         * 0 shv
>         +         */
>         +        return clic->clicintattr[irq_offset] & ~0x38;
>         +    case 3: /* clicintctrl */
>
>
>     Typo: clicintctl
>
>         +        /*
>         +         * The implemented bits are kept left-justified in
>         the most-significant
>         +         * bits of each 8-bit clicintctl[i] register, with
>         the lower
>         +         * unimplemented bits treated as hardwired to
>         1.(Section 3.7)
>         +         */
>         +        return clic->clicintctl[irq_offset] |
>         +               ((1 << (8 - clic->clicintctlbits)) - 1);
>         +    }
>         +
>         +    return 0;
>         +}
>         +
>         +/* Return target interrupt mode */
>         +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>         +{
>         +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>         +    switch (mode) {
>         +    case 0:
>         +        return PRV_M;
>         +    case 1:
>         +        assert(clic->prv_s || clic->prv_u);
>         +        return clic->prv_s ? PRV_S : PRV_U;
>         +    case 2:
>         +        assert(clic->prv_s && clic->prv_u);
>         +        return PRV_U;
>         +    default:
>         +        g_assert_not_reached();
>         +        break;
>         +    }
>         +}
>         +
>         +/* Return target hart id */
>         +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr
>         addr)
>         +{
>         +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>         +    int hart_unit = 4 * clic->num_sources;
>         +
>         +    return (addr % mode_unit) / hart_unit;
>         +}
>         +
>         +/* Return target interrupt number */
>         +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>         +{
>         +    int hart_unit = 4 * clic->num_sources;
>         +    return (addr % hart_unit) / 4;
>         +}
>         +
>         +static void
>         +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value,
>         unsigned size)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    hwaddr clic_size = clic->clic_size;
>         +    int hartid, mode, irq;
>         +
>         +    if (addr < clic_size) {
>
>
>     Is this necessary?
>     I think memory region size already limits the request address to
>     be within the range of clic_size.
>
>         +        if (addr < 0x1000) {
>         +            assert(addr % 4 == 0);
>         +            int index = addr / 4;
>         +            switch (index) {
>         +            case 0: /* cliccfg */
>         +                {
>         +                    uint8_t nlbits = extract32(value, 1, 4);
>         +                    uint8_t nmbits = extract32(value, 5, 2);
>         +
>         +                    /*
>         +                     * The 4-bit cliccfg.nlbits WARL field.
>         +                     * Valid values are 0—8.
>         +                     */
>         +                    if (nlbits <= 8) {
>         +                        clic->nlbits = nlbits;
>         +                    }
>         +                    /* Valid values are given by implemented
>         priviledges */
>         +                    if (clic->prv_s && clic->prv_u) {
>         +                        if (nmbits <= 2) {
>         +                            clic->nmbits = nmbits;
>         +                        }
>         +                    } else if (clic->prv_u) {
>         +                        if (nmbits <= 1) {
>         +                            clic->nmbits = nmbits;
>         +                        }
>         +                    } else {
>         +                        assert(!clic->prv_s);
>         +                        if (nmbits == 0) {
>         +                            clic->nmbits = 0;
>         +                        }
>         +                    }
>         +                    clic->nvbits = extract32(value, 0, 1);
>         +                    break;
>         +                }
>         +            case 1: /* clicinfo, read-only register */
>         +                qemu_log_mask(LOG_GUEST_ERROR,
>         +                              "clic: write read-only
>         clicinfo.\n");
>         +                break;
>         +            case 0x10 ... 0x2F: /* clicinttrig */
>         +                {
>         +                    uint32_t interrupt_number = value &
>         MAKE_64BIT_MASK(0, 13);
>         +                    if (interrupt_number <= clic->num_sources) {
>         +                        value &= ~MAKE_64BIT_MASK(13, 18);
>         +                        clic->clicinttrig[index - 0x10] = value;
>         +                    }
>         +                    break;
>         +                }
>         +            case 2: /* mintthresh */
>         +                if (!strcmp(clic->version, "v0.8")) {
>         +                    clic->mintthresh = value;
>         +                    break;
>         +                }
>         +                qemu_log_mask(LOG_GUEST_ERROR,
>         +                              "clic: invalid write addr: 0x%"
>         HWADDR_PRIx "\n",
>         +                              addr);
>         +                break;
>         +            default:
>         +                qemu_log_mask(LOG_GUEST_ERROR,
>         +                              "clic: invalid write addr: 0x%"
>         HWADDR_PRIx "\n",
>         +                              addr);
>         +                return;
>         +            }
>         +        } else {
>         +            addr -= 0x1000;
>         +            hartid = riscv_clic_get_hartid(clic, addr);
>         +            mode = riscv_clic_get_mode(clic, addr);
>         +            irq = riscv_clic_get_irq(clic, addr);
>         +
>         +            if (riscv_clic_check_visible(clic, mode, hartid,
>         irq)) {
>         +                riscv_clic_hart_write(clic, addr, value,
>         size, mode,
>         +                                      hartid, irq);
>         +            }
>         +        }
>         +    } else {
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid write: 0x%" HWADDR_PRIx
>         "\n", addr);
>         +    }
>         +}
>         +
>         +static uint64_t riscv_clic_read(void *opaque, hwaddr addr,
>         unsigned size)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    hwaddr clic_size = clic->clic_size;
>         +    int hartid, mode, irq;
>         +
>         +    if (addr < clic_size) {
>
>
>     Same to riscv_clic_write().
>
>     Thanks,
>     Frank Chang
>
>         +        if (addr < 0x1000) {
>         +            assert(addr % 4 == 0);
>         +            int index = addr / 4;
>         +            switch (index) {
>         +            case 0: /* cliccfg */
>         +                return clic->nvbits |
>         +                       (clic->nlbits << 1) |
>         +                       (clic->nmbits << 5);
>         +            case 1: /* clicinfo */
>         +                /*
>         +                 * clicinfo register layout
>         +                 *
>         +                 * Bits Field
>         +                 * 31 reserved (WARL 0)
>         +                 * 30:25 num_trigger
>         +                 * 24:21 CLICINTCTLBITS
>         +                 * 20:13 version (for version control)
>         +                 * 12:0 num_interrupt
>         +                 */
>         +                return clic->clicinfo & ~INT32_MAX;
>
>
>     clic->clicinfo should represent the CLIC setting information.
>     I think it's better to add clic reset function or
>     in riscv_clic_realize() to initialize clic->clicinfo.
>
>         +            case 0x10 ... 0x2F: /* clicinttrig */
>         +                /*
>         +                 * clicinttrig register layout
>         +                 *
>         +                 * Bits Field
>         +                 * 31 enable
>         +                 * 30:13 reserved (WARL 0)
>         +                 * 12:0 interrupt_number
>         +                 */
>         +                return clic->clicinttrig[index - 0x10] &
>         +                       ~MAKE_64BIT_MASK(13, 18);
>         +            case 2: /* mintthresh */
>         +                if (!strcmp(clic->version, "v0.8")) {
>         +                    return clic->mintthresh;
>         +                    break;
>         +                }
>         +                qemu_log_mask(LOG_GUEST_ERROR,
>         +                              "clic: invalid read : 0x%"
>         HWADDR_PRIx "\n",
>         +                              addr);
>         +                break;
>         +            default:
>         +                qemu_log_mask(LOG_GUEST_ERROR,
>         +                              "clic: invalid read : 0x%"
>         HWADDR_PRIx "\n",
>         +                              addr);
>         +                break;
>         +            }
>         +        } else {
>         +            addr -= 0x1000;
>         +            hartid = riscv_clic_get_hartid(clic, addr);
>         +            mode = riscv_clic_get_mode(clic, addr);
>         +            irq = riscv_clic_get_irq(clic, addr);
>         +
>         +            if (riscv_clic_check_visible(clic, mode, hartid,
>         irq)) {
>         +                return riscv_clic_hart_read(clic, addr, mode,
>         hartid, irq);
>         +            }
>         +        }
>         +    } else {
>         +        qemu_log_mask(LOG_GUEST_ERROR,
>         +                      "clic: invalid read: 0x%" HWADDR_PRIx
>         "\n", addr);
>         +    }
>         +    return 0;
>         +}
>         +
>         +static void riscv_clic_set_irq(void *opaque, int id, int level)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    int irq, hartid, mode;
>         +    hwaddr addr = 4 * id;
>         +    TRIG_TYPE type;
>         +
>         +    hartid = riscv_clic_get_hartid(clic, addr);
>         +    mode = riscv_clic_get_mode(clic, addr);
>         +    irq = riscv_clic_get_irq(clic, addr);
>         +    type = riscv_clic_get_trigger_type(clic, id);
>         +
>         +    /*
>         +     * In general, the edge-triggered interrupt state should
>         be kept in pending
>         +     * bit, while the level-triggered interrupt should be
>         kept in the level
>         +     * state of the incoming wire.
>         +     *
>         +     * For CLIC, model the level-triggered interrupt by
>         read-only pending bit.
>         +     */
>         +    if (level) {
>         +        switch (type) {
>         +        case POSITIVE_LEVEL:
>         +        case POSITIVE_EDGE:
>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>         level);
>         +            break;
>         +        case NEG_LEVEL:
>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>         !level);
>         +            break;
>         +        case NEG_EDGE:
>         +            break;
>         +        }
>         +    } else {
>         +        switch (type) {
>         +        case POSITIVE_LEVEL:
>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>         level);
>         +            break;
>         +        case POSITIVE_EDGE:
>         +            break;
>         +        case NEG_LEVEL:
>         +        case NEG_EDGE:
>         +            riscv_clic_update_intip(clic, mode, hartid, irq,
>         !level);
>         +            break;
>         +        }
>         +    }
>         +}
>         +
>         +static void riscv_clic_cpu_irq_handler(void *opaque, int irq,
>         int level)
>         +{
>         +    CPURISCVState *env = (CPURISCVState *)opaque;
>         +    RISCVCLICState *clic = env->clic;
>         +    CPUState *cpu = env_cpu(env);
>         +
>         +    if (level) {
>         +        env->exccode = clic->exccode[cpu->cpu_index];
>         +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>         +    }
>         +}
>         +
>         +static const MemoryRegionOps riscv_clic_ops = {
>         +    .read = riscv_clic_read,
>         +    .write = riscv_clic_write,
>         +    .endianness = DEVICE_LITTLE_ENDIAN,
>         +    .valid = {
>         +        .min_access_size = 1,
>         +        .max_access_size = 8
>         +    }
>         +};
>         +
>         +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>         +{
>         +    RISCVCLICState *clic = RISCV_CLIC(dev);
>         +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>         +    int irqs, i;
>         +
>         +    if (clic->prv_s && clic->prv_u) {
>         +        irqs = 3 * harts_x_sources;
>         +    } else if (clic->prv_s || clic->prv_u) {
>         +        irqs = 2 * harts_x_sources;
>         +    } else {
>         +        irqs = harts_x_sources;
>         +    }
>         +
>         +    clic->clic_size = irqs * 4 + 0x1000;
>         +    memory_region_init_io(&clic->mmio, OBJECT(dev),
>         &riscv_clic_ops, clic,
>         +                          TYPE_RISCV_CLIC, clic->clic_size);
>         +
>         +    clic->clicintip = g_new0(uint8_t, irqs);
>         +    clic->clicintie = g_new0(uint8_t, irqs);
>         +    clic->clicintattr = g_new0(uint8_t, irqs);
>         +    clic->clicintctl = g_new0(uint8_t, irqs);
>         +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>
>
>     Should the size of clic->active_list be: harts_x_sources?
>
>         +    clic->active_count = g_new0(size_t, clic->num_harts);
>         +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>         +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>         +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>         +
>         +    /* Allocate irq through gpio, so that we can use qtest */
>         +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>         +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>         +
>         +    for (i = 0; i < clic->num_harts; i++) {
>         +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>         +        qemu_irq irq =
>         qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>         +  &cpu->env, 1);
>         +        qdev_connect_gpio_out(dev, i, irq);
>         +        cpu->env.clic = clic;
>         +    }
>         +}
>         +
>         +static Property riscv_clic_properties[] = {
>         +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>         +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>         +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState,
>         num_harts, 0),
>         +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState,
>         num_sources, 0),
>         +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState,
>         clicintctlbits, 0),
>         +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState,
>         mclicbase, 0),
>         +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>         +    DEFINE_PROP_END_OF_LIST(),
>         +};
>         +
>         +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>         +{
>         +    DeviceClass *dc = DEVICE_CLASS(klass);
>         +
>         +    dc->realize = riscv_clic_realize;
>         +    device_class_set_props(dc, riscv_clic_properties);
>         +}
>         +
>         +static const TypeInfo riscv_clic_info = {
>         +    .name          = TYPE_RISCV_CLIC,
>         +    .parent        = TYPE_SYS_BUS_DEVICE,
>         +    .instance_size = sizeof(RISCVCLICState),
>         +    .class_init    = riscv_clic_class_init,
>         +};
>         +
>         +static void riscv_clic_register_types(void)
>         +{
>         +    type_register_static(&riscv_clic_info);
>         +}
>         +
>         +type_init(riscv_clic_register_types)
>         +
>         +/*
>         + * riscv_clic_create:
>         + *
>         + * @addr: base address of M-Mode CLIC memory-mapped registers
>         + * @prv_s: have smode region
>         + * @prv_u: have umode region
>         + * @num_harts: number of CPU harts
>         + * @num_sources: number of interrupts supporting by each aperture
>         + * @clicintctlbits: bits are actually implemented in the
>         clicintctl registers
>         + * @version: clic version, such as "v0.9"
>         + *
>         + * Returns: the device object
>         + */
>         +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool
>         prv_u,
>         +                               uint32_t num_harts, uint32_t
>         num_sources,
>         +                               uint8_t clicintctlbits,
>         +                               const char *version)
>         +{
>         +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>         +
>         +    assert(num_sources <= 4096);
>         +    assert(num_harts <= 1024);
>         +    assert(clicintctlbits <= 8);
>         +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>         +
>         +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>         +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>         +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>         +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>         +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>         +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>         +    qdev_prop_set_string(dev, "version", version);
>         +
>         +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>         +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>         +    return dev;
>         +}
>         +
>         +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    riscv_clic_next_interrupt(clic, hartid);
>         +}
>         +
>         +bool riscv_clic_shv_interrupt(void *opaque, int mode, int
>         hartid, int irq)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>         +}
>         +
>         +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>         hartid, int irq)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>         +}
>         +
>         +void riscv_clic_clean_pending(void *opaque, int mode, int
>         hartid, int irq)
>         +{
>         +    RISCVCLICState *clic = opaque;
>         +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode,
>         hartid, irq);
>         +    clic->clicintip[irq_offset] = 0;
>         +}
>         +
>         +/*
>         + * The new CLIC interrupt-handling mode is encoded as a new
>         state in
>         + * the existing WARL xtvec register, where the low two bits
>         of  are 11.
>         + */
>         +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>         +{
>         +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec :
>         env->stvec;
>         +    return env->clic && ((xtvec & 0x3) == 3);
>         +}
>         +
>         +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>         +                               int *il, int *irq)
>         +{
>         +    *irq = extract32(exccode, 0, 12);
>         +    *mode = extract32(exccode, 12, 2);
>         +    *il = extract32(exccode, 14, 8);
>         +}
>         diff --git a/include/hw/intc/riscv_clic.h
>         b/include/hw/intc/riscv_clic.h
>         new file mode 100644
>         index 0000000000..e5f89672a6
>         --- /dev/null
>         +++ b/include/hw/intc/riscv_clic.h
>         @@ -0,0 +1,103 @@
>         +/*
>         + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>         + *
>         + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All
>         rights reserved.
>         + *
>         + * This program is free software; you can redistribute it
>         and/or modify it
>         + * under the terms and conditions of the GNU General Public
>         License,
>         + * version 2 or later, as published by the Free Software
>         Foundation.
>         + *
>         + * This program is distributed in the hope it will be useful,
>         but WITHOUT
>         + * ANY WARRANTY; without even the implied warranty of
>         MERCHANTABILITY or
>         + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
>         Public License for
>         + * more details.
>         + *
>         + * You should have received a copy of the GNU General Public
>         License along with
>         + * this program.  If not, see <http://www.gnu.org/licenses/
>         <http://www.gnu.org/licenses/>>.
>         + */
>         +
>         +#ifndef RISCV_CLIC_H
>         +#define RISCV_CLIC_H
>         +
>         +#include "hw/irq.h"
>         +#include "hw/sysbus.h"
>         +
>         +#define TYPE_RISCV_CLIC "riscv_clic"
>         +#define RISCV_CLIC(obj) \
>         +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>         +
>         +/*
>         + * CLIC per hart active interrupts
>         + *
>         + * We maintain per hart lists of enabled interrupts sorted by
>         + * mode+level+priority. The sorting is done on the
>         configuration path
>         + * so that the interrupt delivery fastpath can linear scan
>         enabled
>         + * interrupts in priority order.
>         + */
>         +typedef struct CLICActiveInterrupt {
>         +    uint16_t intcfg;
>         +    uint16_t irq;
>         +} CLICActiveInterrupt;
>         +
>         +typedef enum TRIG_TYPE {
>         +    POSITIVE_LEVEL,
>         +    POSITIVE_EDGE,
>         +    NEG_LEVEL,
>         +    NEG_EDGE,
>         +} TRIG_TYPE;
>         +
>         +typedef struct RISCVCLICState {
>         +    /*< private >*/
>         +    SysBusDevice parent_obj;
>         +
>         +    /*< public >*/
>         +
>         +    /* Implementaion parameters */
>         +    bool prv_s;
>         +    bool prv_u;
>         +    uint32_t num_harts;
>         +    uint32_t num_sources;
>         +    uint32_t clic_size;
>         +    uint32_t clic_mmode_base;
>         +    uint32_t clicintctlbits;
>         +    uint64_t mclicbase;
>         +    char *version;
>         +
>         +    /* Global configuration */
>         +    uint8_t nmbits;
>         +    uint8_t nlbits;
>         +    uint8_t nvbits;
>         +    uint32_t clicinfo;
>         +    uint32_t clicinttrig[32];
>         +
>         +    /* Aperture configuration */
>         +    uint8_t *clicintip;
>         +    uint8_t *clicintie;
>         +    uint8_t *clicintattr;
>         +    uint8_t *clicintctl;
>         +
>         +    /* Complatible with v0.8 */
>         +    uint32_t mintthresh;
>         +    uint32_t sintthresh;
>         +    uint32_t uintthresh;
>         +
>         +    /* QEMU implementaion related fields */
>         +    uint32_t *exccode;
>         +    CLICActiveInterrupt *active_list;
>         +    size_t *active_count;
>         +    MemoryRegion mmio;
>         +    qemu_irq *cpu_irqs;
>         +} RISCVCLICState;
>         +
>         +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool
>         prv_u,
>         +                               uint32_t num_harts, uint32_t
>         num_sources,
>         +                               uint8_t clicintctlbits,
>         +                               const char *version);
>         +
>         +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>         int *il, int *irq);
>         +void riscv_clic_clean_pending(void *opaque, int mode, int
>         hartid, int irq);
>         +bool riscv_clic_edge_triggered(void *opaque, int mode, int
>         hartid, int irq);
>         +bool riscv_clic_shv_interrupt(void *opaque, int mode, int
>         hartid, int irq);
>         +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>         +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>         +#endif
>         diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>         index a5eab26a69..9e389d7bbf 100644
>         --- a/target/riscv/cpu.h
>         +++ b/target/riscv/cpu.h
>         @@ -33,6 +33,7 @@
>          #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>          #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>          #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>         +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>          #define TYPE_RISCV_CPU_ANY RISCV_CPU_TYPE_NAME("any")
>          #define TYPE_RISCV_CPU_BASE32  RISCV_CPU_TYPE_NAME("rv32")
>         @@ -247,6 +248,7 @@ struct CPURISCVState {
>              /* Fields from here on are preserved across CPU reset. */
>              QEMUTimer *timer; /* Internal timer */
>              void *clic;       /* clic interrupt controller */
>         +    uint32_t exccode; /* clic irq encode */
>          };
>
>          OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>         -- 
>         2.25.1
>
>
Frank Chang June 30, 2021, 5:37 a.m. UTC | #31
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年6月29日 週二 上午10:45寫道:

> Hi Frank,
>
> Thanks for a lot of good points.
> On 2021/6/26 下午11:03, Frank Chang wrote:
>
> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>>
>
> According to spec:
>   if the nlbits > CLICINTCTLBITS, then the lower bits of the 8-bit
>   interrupt level are assumed to be all 1s.
>
> That is, the valid nlbits should be: min(clic->nlbits, CLICINTCTLBITS);
> The cliccfg example in spec also shows that:
>
> CLICINTCTLBITS  nlbits  clicintctl[i]  interrupt levels
>       0                       2         ........         255
>       1                       2         l.......         127,255
>       2                       2         ll......         63,127,191,255
>       3                       3         lll.....
>  31,63,95,127,159,191,223,255
>       4                       1         lppp....      127,255
>
>
> Agree, thanks.
>
>
>> + * In a system with multiple harts, the M-mode CLIC regions for all the
>> harts
>> + * are placed contiguously in the memory space, followed by the S-mode
>> CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>>
>
> The above description is not true any more in the latest spec:
>   The CLIC specification does not dictate how CLIC memory-mapped registers
> are
>   split between M/S/U regions as well as the layout of multiple harts as
> this is generally
>   a platform issue and each platform needs to define a discovery mechanism
> to determine
>   the memory map locations.
>
> But I think we can just keep the current design for now anyway, as it's
> also one of legal memory layout.
> Otherwise, the design would be more complicated I think.
>
> We can pass an array containing indexed by hart_id and mode from the
> machine board, such as
>
> hwaddr clic_memmap[HARTS][MODE] = {
>
> {0x0000, 0x10000, 0x20000},
>
> {0x4000, 0x14000, 0x24000},
>
> {0x8000, 0x18000, 0x28000},
>
> {0xc000, 0x1c000, 0x2c000},
>
> }
>
That would be great.
We can create different memory regions for each memory map
and assign them with the same read/write ops.

Thanks,
Frank Chang


>
>
>>
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +
>> sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) /
>> sizeof(CLICActiveInterrupt);
>>
>
> I think what you are trying to do here is to get the number of elements
> right after the active interrupt to be deleted in order to calculate the
> size of
> active interrupts to be memmoved.
>
> Agree, thanks.
>
> However, according to C spec:
>   When two pointers are subtracted, both shall point to elements of the
> same array object,
>   or one past the last element of the array object; the result is the
> difference of the
>   subscripts of the two array elements.
>
> So I think: (result - active_list) is already the number of elements you
> want.
> You don't have to divide it with sizeof(CLICActiveInterrupt) again.
>
>
>> +        size_t sz = (--(*active_count) - elem) *
>> sizeof(CLICActiveInterrupt);
>> +        assert(result);
>>
>
> Nit: assert(result) can be moved above size_t elem statement.
>
> Agree.
>
>
>
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>>
>
> Spec. says that it's legal to write 32-bit value to set
> {clicintctl[i], clicintattr[i], clicintie[i] ,clicintip[i]} at the same
> time:
>   A 32-bit write to {clicintctl,clicintattr,clicintie,clicintip} is legal.
>   However, there is no specified order in which the effects of
>   the individual byte updates take effect.
>
>
> I miss it. I think it only supports 1 byte access or 4 bytes access. For 4
> bytes access,  we need to pass an flag to specify to the order from the
> machine board.
>
> Thoughts?
>
> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand
>> the bit
>>
>
> Typo: leastsignificant => least significant
>
> OK.
>
>
>
>> +             * field, from FW perspective clicintip[i]=zero means no
>> interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode =
>> extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>>
>
> If irq i is already in the active_list, when will its intcfg been synced?
>
> Good. I think should sync immediately.
>
>
>
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>>
>
> Typo: clicintctl
>
> OK.
>
>
>
>> +        /*
>> +         * The implemented bits are kept left-justified in the
>> most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>>
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned
>> size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>>
>
> Is this necessary?
> I think memory region size already limits the request address to be within
> the range of clic_size.
>
> At this point, not necessary.
>
>
>
>>
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>>
>
> Same to riscv_clic_write().
>
> Thanks,
> Frank Chang
>
>
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>>
>
> clic->clicinfo should represent the CLIC setting information.
> I think it's better to add clic reset function or in riscv_clic_realize()
> to initialize clic->clicinfo.
>
> Agree.
>
>
>
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>> clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>>
>
> Should the size of clic->active_list be: harts_x_sources?
>
> Every irq can be in the active_list, so just the number of irqs.
>
> Remind we will only use M-mode IRQ in next patch set, I still think we
> should use the
> irqs here, because irq in active_list has privilege information.
>
> Thanks,
> Zhiwei
>
>
>
>>
>>
Frank Chang July 13, 2021, 6:53 a.m. UTC | #32
LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:

> The Core-Local Interrupt Controller (CLIC) provides low-latency,
> vectored, pre-emptive interrupts for RISC-V systems.
>
> The CLIC also supports a new Selective Hardware Vectoring feature
> that allow users to optimize each interrupt for either faster
> response or smaller code size.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> ---
>  default-configs/devices/riscv32-softmmu.mak |   1 +
>  default-configs/devices/riscv64-softmmu.mak |   1 +
>  hw/intc/Kconfig                             |   3 +
>  hw/intc/meson.build                         |   1 +
>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>  include/hw/intc/riscv_clic.h                | 103 +++
>  target/riscv/cpu.h                          |   2 +
>  7 files changed, 946 insertions(+)
>  create mode 100644 hw/intc/riscv_clic.c
>  create mode 100644 include/hw/intc/riscv_clic.h
>
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index d847bd5692..1430c30588 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index d5eec75f05..396800bbbd 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -5,6 +5,7 @@
>  #CONFIG_PCI_DEVICES=n
>  CONFIG_SEMIHOSTING=y
>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> +CONFIG_RISCV_CLIC=y
>
>  # Boards:
>  #
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index f4694088a4..5bf492b48f 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>  config SIFIVE_PLIC
>      bool
>
> +config RISCV_CLIC
> +    bool
> +
>  config GOLDFISH_PIC
>      bool
>
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index 1c299039f6..2aa71b6738 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
> files('s390_flic_kvm.c'))
>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
> files('sifive_clint.c'))
>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
> files('sifive_plic.c'))
> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>                 if_true: files('xics_kvm.c'))
> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
> new file mode 100644
> index 0000000000..8ad534c506
> --- /dev/null
> +++ b/hw/intc/riscv_clic.c
> @@ -0,0 +1,835 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/qtest.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/intc/riscv_clic.h"
> +
> +/*
> + * The 2-bit trig WARL field specifies the trigger type and polarity for
> each
> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
> defined as
> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
> + */
> +
> +static inline TRIG_TYPE
> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
> +}
> +
> +static inline bool
> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
> +}
> +
> +static inline bool
> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
> +{
> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int nlbits = clic->nlbits;
> +
> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
> +    /* unused level bits are set to 1 */
> +    return (intctl & mask_il) | mask_padding;
> +}
> +
> +static uint8_t
> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
> +{
> +    int npbits = clic->clicintctlbits - clic->nlbits;
> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
> +
> +    if (npbits < 0) {
> +        return UINT8_MAX;
> +    }
> +    /* unused priority bits are set to 1 */
> +    return (intctl & mask_priority) | mask_padding;
> +}
> +
> +static void
> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
> +                         uint8_t *mode, uint8_t *level,
> +                         uint8_t *priority)
> +{
> +    *mode = intcfg >> 8;
> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
> +}
> +
> +/*
> + * In a system with multiple harts, the M-mode CLIC regions for all the
> harts
> + * are placed contiguously in the memory space, followed by the S-mode
> CLIC
> + * regions for all harts. (Section 3.11)
> + */
> +static size_t
> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t mode_offset = 0;
> +    size_t unit = clic->num_harts * clic->num_sources;
> +
> +    switch (mode) {
> +    case PRV_M:
> +        mode_offset = 0;
> +        break;
> +    case PRV_S:
> +        mode_offset = unit;
> +        break;
> +    case PRV_U:
> +        mode_offset = clic->prv_s ? 2 * unit : unit;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid mode %d\n", mode);
> +        exit(1);
> +    }
> +    return mode_offset + hartid * clic->num_sources + irq;
> +}
> +
> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
> +{
> +    /*
> +     * Scan active list for highest priority pending interrupts
> +     * comparing against this harts mintstatus register and interrupt
> +     * the core if we have a higher priority interrupt to deliver
> +     */
> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
> +    CPURISCVState *env = &cpu->env;
> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
> +
> +    int il[4] = {
> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
> +            clic->mintthresh), /* PRV_U */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
> +            clic->sintthresh), /* PRV_S */
> +        0,                     /* reserverd */
> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
> +            clic->uintthresh)  /* PRV_M */
> +    };
> +
> +    /* Get sorted list of enabled interrupts for this hart */
> +    size_t hart_offset = hartid * clic->num_sources;
> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
> +    size_t active_count = clic->active_count[hartid];
> +    uint8_t mode, level, priority;
> +
> +    /* Loop through the enabled interrupts sorted by mode+priority+level
> */
> +    while (active_count) {
> +        size_t irq_offset;
> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
> +                                 &priority);
> +        if (mode < env->priv || (mode == env->priv && level <= il[mode]))
> {
> +            /*
> +             * No pending interrupts with high enough mode+priority+level
> +             * break and clear pending interrupt for this hart
> +             */
> +            break;
> +        }
> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> active->irq);
> +        /* Check pending interrupt with high enough mode+priority+level */
> +        if (clic->clicintip[irq_offset]) {
> +            /* Clean vector edge-triggered pending */
> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
> +                clic->clicintip[irq_offset] = 0;
> +            }
> +            /* Post pending interrupt for this hart */
> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
> 14;
> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
> +            return;
> +        }
> +        /* Check next enabled interrupt */
> +        active_count--;
> +        active++;
> +    }
> +}
>

Hi Zhiwei,

The global interrupt may be disabled (xstatus.xie = 0) at the time active
interrupt i
is both enabled and pending (i.e. clicintie[i] = 1 && clicintip[i] = 1)
Therefore, interrupt i will not be taken right away until xstatus.xie is
set to 1.

However, during global interrupt is disabled,
SW/HW may either disable or change pending interrupt i to non-pending.
Interrupt i should not be active anymore.
But current implementation can't reflect that because
CPU_INTERRUPT_CLIC bit won't be unset.

To solve this issue, in my own implementation, I tried to:

In riscv_clic_update_intip(), if interrupt i turns to non-pending,
check if interrupt i is an active pending interrupt.
(by decoding IRQ number from env->exccode and check if
cpu->interrupt_request flag also has CPU_INTERRUPT_CLIC bit set.)

If so, interrupt i is current active pending interrupt to be disabled, we
have to
unset CPU_INTERRUPT_CLIC bit from interrupt request flag for the hart
to prevent interrupt i been taken later as it's not pending anymore.
Then call riscv_clic_next_interrupt() as usual to pick next interrupt.

Same behavior is also applied in riscv_clic_update_intie() when
interrupt i is disabled and interrupt i is also the active pending
interrupt.

Also, I think the effective interrupt level check should be moved
from riscv_clic_update_intip() to riscv_cpu_local_irq_mode_enabled() in
cpu_helper.c.

Consider the following scenario:
    a. Active pending interrupt i is not been handled due to global
interrupt is disabled.
    b. Software accesses xnxti CSR with write side effect (xstatus.xie is
still set to 0).
        xintstatus.xil will be updated to the interrupt level of interrupt
i.
    c. Interrupt i turns to non-pending/disabled.
    d. Interrupt j is enabled and pending.
    e. Interrupt j won't be selected in riscv_clic_next_interrupt() if its
interrupt level
        is lower than interrupt i (xinitstatus.xil still holds the
interrupt level of interrupt i),
        which is incorrect because interrupt i is not pending and active
anymore.

By moving effective interrupt level check from
riscv_clic_update_intip() to riscv_cpu_local_irq_mode_enabled()
can eliminate the issue of lower-priority interrupt been masked out
by higher-interrupt-level non active interrupt.

Let me know if I miss anything.

Regards,
Frank Chang


> +
> +/*
> + * Any interrupt i that is not accessible to S-mode or U-Mode
> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
> + */
> +static bool
> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    if (!clic->prv_s && !clic->prv_u) { /* M */
> +        return mode == PRV_M;
> +    } else if (!clic->prv_s) { /* M/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_U);
> +        default:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 for M/U hart");
> +            exit(1);
> +        }
> +    } else { /* M/S/U */
> +        switch (clic->nmbits) {
> +        case 0:
> +            return mode == PRV_M;
> +        case 1:
> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M)
> :
> +                                                          (mode == PRV_S);
> +        case 2:
> +            return mode == clic->clicintattr[irq_offset];
> +        case 3:
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
> +            exit(1);
> +        }
> +    }
> +    return false;
> +}
> +
> +/*
> + * For level-triggered interrupts, software writes to pending bits are
> + * ignored completely. (Section 3.4)
> + */
> +static bool
> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int
> irq)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +static void
> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t value)
> +{
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = !!value;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +/*
> + * For security purpose, the field can only be set to a privilege
> + * level that is equal mode to or lower than the currently running
> + * privilege level.(Section 3.6)
> + */
> +
> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
> value)
> +{
> +    int mode = extract64(value, 6, 2);
> +
> +    if (!qtest_enabled()) {
> +        CPURISCVState *env = current_cpu->env_ptr;
> +        if (env->priv < mode) {
> +            return false;
> +        }
> +    }
> +    return true;
> +}
> +
> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
> +{
> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
> +           (i->irq & 0xfff);             /* Highest irq number */
> +}
> +
> +static int riscv_clic_active_compare(const void *a, const void *b)
> +{
> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
> +}
> +
> +static void
> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
> +                        int irq, uint64_t new_intie)
> +{
> +    size_t hart_offset = hartid * clic->num_sources;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
> +    size_t *active_count = &clic->active_count[hartid];
> +
> +    uint8_t old_intie = clic->clicintie[irq_offset];
> +    clic->clicintie[irq_offset] = !!new_intie;
> +
> +    /* Add to or remove from list of active interrupts */
> +    if (new_intie && !old_intie) {
> +        active_list[*active_count].intcfg = (mode << 8) |
> +                                            clic->clicintctl[irq_offset];
> +        active_list[*active_count].irq = irq;
> +        (*active_count)++;
> +    } else if (!new_intie && old_intie) {
> +        CLICActiveInterrupt key = {
> +            (mode << 8) | clic->clicintctl[irq_offset], irq
> +        };
> +        CLICActiveInterrupt *result = bsearch(&key,
> +                                              active_list, *active_count,
> +                                              sizeof(CLICActiveInterrupt),
> +                                              riscv_clic_active_compare);
> +        size_t elem = (result - active_list) /
> sizeof(CLICActiveInterrupt);
> +        size_t sz = (--(*active_count) - elem) *
> sizeof(CLICActiveInterrupt);
> +        assert(result);
> +        memmove(&result[0], &result[1], sz);
> +    }
> +
> +    /* Sort list of active interrupts */
> +    qsort(active_list, *active_count,
> +          sizeof(CLICActiveInterrupt),
> +          riscv_clic_active_compare);
> +
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +static void
> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
> +                      uint64_t value, unsigned size,
> +                      int mode, int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
> +            /*
> +             * The actual pending bit is located at bit 0 (i.e., the
> +             * leastsignificant bit). In case future extensions expand
> the bit
> +             * field, from FW perspective clicintip[i]=zero means no
> interrupt
> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
> +             * interrupt is pending. (Section 3.4)
> +             */
> +            if (value != clic->clicintip[irq_offset]) {
> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
> +            }
> +        }
> +        break;
> +    case 1: /* clicintie[i] */
> +        if (clic->clicintie[irq_offset] != value) {
> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
> +        }
> +        break;
> +    case 2: /* clicintattr[i] */
> +        if (riscv_clic_validate_intattr(clic, value)) {
> +            if (clic->clicintattr[irq_offset] != value) {
> +                /* When nmbits=2, check WARL */
> +                bool invalid = (clic->nmbits == 2) &&
> +                               (extract64(value, 6, 2) == 0b10);
> +                if (invalid) {
> +                    uint8_t old_mode =
> extract32(clic->clicintattr[irq_offset],
> +                                                 6, 2);
> +                    value = deposit32(value, 6, 2, old_mode);
> +                }
> +                clic->clicintattr[irq_offset] = value;
> +                riscv_clic_next_interrupt(clic, hartid);
> +            }
> +        }
> +        break;
> +    case 3: /* clicintctl[i] */
> +        if (value != clic->clicintctl[irq_offset]) {
> +            clic->clicintctl[irq_offset] = value;
> +            riscv_clic_next_interrupt(clic, hartid);
> +        }
> +        break;
> +    }
> +}
> +
> +static uint64_t
> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
> +                     int hartid, int irq)
> +{
> +    int req = extract32(addr, 0, 2);
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +
> +    if (hartid >= clic->num_harts) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
> +                      hartid, addr);
> +        return 0;
> +    }
> +
> +    if (irq >= clic->num_sources) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
> addr);
> +        return 0;
> +    }
> +
> +    switch (req) {
> +    case 0: /* clicintip[i] */
> +        return clic->clicintip[irq_offset];
> +    case 1: /* clicintie[i] */
> +        return clic->clicintie[irq_offset];
> +    case 2: /* clicintattr[i] */
> +        /*
> +         * clicintattr register layout
> +         * Bits Field
> +         * 7:6 mode
> +         * 5:3 reserved (WPRI 0)
> +         * 2:1 trig
> +         * 0 shv
> +         */
> +        return clic->clicintattr[irq_offset] & ~0x38;
> +    case 3: /* clicintctrl */
> +        /*
> +         * The implemented bits are kept left-justified in the
> most-significant
> +         * bits of each 8-bit clicintctl[i] register, with the lower
> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
> +         */
> +        return clic->clicintctl[irq_offset] |
> +               ((1 << (8 - clic->clicintctlbits)) - 1);
> +    }
> +
> +    return 0;
> +}
> +
> +/* Return target interrupt mode */
> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
> +    switch (mode) {
> +    case 0:
> +        return PRV_M;
> +    case 1:
> +        assert(clic->prv_s || clic->prv_u);
> +        return clic->prv_s ? PRV_S : PRV_U;
> +    case 2:
> +        assert(clic->prv_s && clic->prv_u);
> +        return PRV_U;
> +    default:
> +        g_assert_not_reached();
> +        break;
> +    }
> +}
> +
> +/* Return target hart id */
> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
> +    int hart_unit = 4 * clic->num_sources;
> +
> +    return (addr % mode_unit) / hart_unit;
> +}
> +
> +/* Return target interrupt number */
> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
> +{
> +    int hart_unit = 4 * clic->num_sources;
> +    return (addr % hart_unit) / 4;
> +}
> +
> +static void
> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                {
> +                    uint8_t nlbits = extract32(value, 1, 4);
> +                    uint8_t nmbits = extract32(value, 5, 2);
> +
> +                    /*
> +                     * The 4-bit cliccfg.nlbits WARL field.
> +                     * Valid values are 0—8.
> +                     */
> +                    if (nlbits <= 8) {
> +                        clic->nlbits = nlbits;
> +                    }
> +                    /* Valid values are given by implemented priviledges
> */
> +                    if (clic->prv_s && clic->prv_u) {
> +                        if (nmbits <= 2) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else if (clic->prv_u) {
> +                        if (nmbits <= 1) {
> +                            clic->nmbits = nmbits;
> +                        }
> +                    } else {
> +                        assert(!clic->prv_s);
> +                        if (nmbits == 0) {
> +                            clic->nmbits = 0;
> +                        }
> +                    }
> +                    clic->nvbits = extract32(value, 0, 1);
> +                    break;
> +                }
> +            case 1: /* clicinfo, read-only register */
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: write read-only clicinfo.\n");
> +                break;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                {
> +                    uint32_t interrupt_number = value &
> MAKE_64BIT_MASK(0, 13);
> +                    if (interrupt_number <= clic->num_sources) {
> +                        value &= ~MAKE_64BIT_MASK(13, 18);
> +                        clic->clicinttrig[index - 0x10] = value;
> +                    }
> +                    break;
> +                }
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    clic->mintthresh = value;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid write addr: 0x%" HWADDR_PRIx
> "\n",
> +                              addr);
> +                return;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                riscv_clic_hart_write(clic, addr, value, size, mode,
> +                                      hartid, irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +}
> +
> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RISCVCLICState *clic = opaque;
> +    hwaddr clic_size = clic->clic_size;
> +    int hartid, mode, irq;
> +
> +    if (addr < clic_size) {
> +        if (addr < 0x1000) {
> +            assert(addr % 4 == 0);
> +            int index = addr / 4;
> +            switch (index) {
> +            case 0: /* cliccfg */
> +                return clic->nvbits |
> +                       (clic->nlbits << 1) |
> +                       (clic->nmbits << 5);
> +            case 1: /* clicinfo */
> +                /*
> +                 * clicinfo register layout
> +                 *
> +                 * Bits Field
> +                 * 31 reserved (WARL 0)
> +                 * 30:25 num_trigger
> +                 * 24:21 CLICINTCTLBITS
> +                 * 20:13 version (for version control)
> +                 * 12:0 num_interrupt
> +                 */
> +                return clic->clicinfo & ~INT32_MAX;
> +            case 0x10 ... 0x2F: /* clicinttrig */
> +                /*
> +                 * clicinttrig register layout
> +                 *
> +                 * Bits Field
> +                 * 31 enable
> +                 * 30:13 reserved (WARL 0)
> +                 * 12:0 interrupt_number
> +                 */
> +                return clic->clicinttrig[index - 0x10] &
> +                       ~MAKE_64BIT_MASK(13, 18);
> +            case 2: /* mintthresh */
> +                if (!strcmp(clic->version, "v0.8")) {
> +                    return clic->mintthresh;
> +                    break;
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            default:
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
> +                              addr);
> +                break;
> +            }
> +        } else {
> +            addr -= 0x1000;
> +            hartid = riscv_clic_get_hartid(clic, addr);
> +            mode = riscv_clic_get_mode(clic, addr);
> +            irq = riscv_clic_get_irq(clic, addr);
> +
> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
> irq);
> +            }
> +        }
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
> +    }
> +    return 0;
> +}
> +
> +static void riscv_clic_set_irq(void *opaque, int id, int level)
> +{
> +    RISCVCLICState *clic = opaque;
> +    int irq, hartid, mode;
> +    hwaddr addr = 4 * id;
> +    TRIG_TYPE type;
> +
> +    hartid = riscv_clic_get_hartid(clic, addr);
> +    mode = riscv_clic_get_mode(clic, addr);
> +    irq = riscv_clic_get_irq(clic, addr);
> +    type = riscv_clic_get_trigger_type(clic, id);
> +
> +    /*
> +     * In general, the edge-triggered interrupt state should be kept in
> pending
> +     * bit, while the level-triggered interrupt should be kept in the
> level
> +     * state of the incoming wire.
> +     *
> +     * For CLIC, model the level-triggered interrupt by read-only pending
> bit.
> +     */
> +    if (level) {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +        case POSITIVE_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case NEG_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        case NEG_EDGE:
> +            break;
> +        }
> +    } else {
> +        switch (type) {
> +        case POSITIVE_LEVEL:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
> +            break;
> +        case POSITIVE_EDGE:
> +            break;
> +        case NEG_LEVEL:
> +        case NEG_EDGE:
> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
> +            break;
> +        }
> +    }
> +}
> +
> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
> +{
> +    CPURISCVState *env = (CPURISCVState *)opaque;
> +    RISCVCLICState *clic = env->clic;
> +    CPUState *cpu = env_cpu(env);
> +
> +    if (level) {
> +        env->exccode = clic->exccode[cpu->cpu_index];
> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
> +    }
> +}
> +
> +static const MemoryRegionOps riscv_clic_ops = {
> +    .read = riscv_clic_read,
> +    .write = riscv_clic_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8
> +    }
> +};
> +
> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
> +{
> +    RISCVCLICState *clic = RISCV_CLIC(dev);
> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
> +    int irqs, i;
> +
> +    if (clic->prv_s && clic->prv_u) {
> +        irqs = 3 * harts_x_sources;
> +    } else if (clic->prv_s || clic->prv_u) {
> +        irqs = 2 * harts_x_sources;
> +    } else {
> +        irqs = harts_x_sources;
> +    }
> +
> +    clic->clic_size = irqs * 4 + 0x1000;
> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
> +                          TYPE_RISCV_CLIC, clic->clic_size);
> +
> +    clic->clicintip = g_new0(uint8_t, irqs);
> +    clic->clicintie = g_new0(uint8_t, irqs);
> +    clic->clicintattr = g_new0(uint8_t, irqs);
> +    clic->clicintctl = g_new0(uint8_t, irqs);
> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
> +    clic->active_count = g_new0(size_t, clic->num_harts);
> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
> +
> +    /* Allocate irq through gpio, so that we can use qtest */
> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
> +
> +    for (i = 0; i < clic->num_harts; i++) {
> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
> +                                         &cpu->env, 1);
> +        qdev_connect_gpio_out(dev, i, irq);
> +        cpu->env.clic = clic;
> +    }
> +}
> +
> +static Property riscv_clic_properties[] = {
> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
> 0),
> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = riscv_clic_realize;
> +    device_class_set_props(dc, riscv_clic_properties);
> +}
> +
> +static const TypeInfo riscv_clic_info = {
> +    .name          = TYPE_RISCV_CLIC,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RISCVCLICState),
> +    .class_init    = riscv_clic_class_init,
> +};
> +
> +static void riscv_clic_register_types(void)
> +{
> +    type_register_static(&riscv_clic_info);
> +}
> +
> +type_init(riscv_clic_register_types)
> +
> +/*
> + * riscv_clic_create:
> + *
> + * @addr: base address of M-Mode CLIC memory-mapped registers
> + * @prv_s: have smode region
> + * @prv_u: have umode region
> + * @num_harts: number of CPU harts
> + * @num_sources: number of interrupts supporting by each aperture
> + * @clicintctlbits: bits are actually implemented in the clicintctl
> registers
> + * @version: clic version, such as "v0.9"
> + *
> + * Returns: the device object
> + */
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version)
> +{
> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
> +
> +    assert(num_sources <= 4096);
> +    assert(num_harts <= 1024);
> +    assert(clicintctlbits <= 8);
> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
> +
> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
> +    qdev_prop_set_string(dev, "version", version);
> +
> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
> +    return dev;
> +}
> +
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
> +{
> +    RISCVCLICState *clic = opaque;
> +    riscv_clic_next_interrupt(clic, hartid);
> +}
> +
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
> +}
> +
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
> +}
> +
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
> +{
> +    RISCVCLICState *clic = opaque;
> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
> irq);
> +    clic->clicintip[irq_offset] = 0;
> +}
> +
> +/*
> + * The new CLIC interrupt-handling mode is encoded as a new state in
> + * the existing WARL xtvec register, where the low two bits of  are 11.
> + */
> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
> +{
> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
> +    return env->clic && ((xtvec & 0x3) == 3);
> +}
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
> +                               int *il, int *irq)
> +{
> +    *irq = extract32(exccode, 0, 12);
> +    *mode = extract32(exccode, 12, 2);
> +    *il = extract32(exccode, 14, 8);
> +}
> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
> new file mode 100644
> index 0000000000..e5f89672a6
> --- /dev/null
> +++ b/include/hw/intc/riscv_clic.h
> @@ -0,0 +1,103 @@
> +/*
> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
> + *
> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2 or later, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef RISCV_CLIC_H
> +#define RISCV_CLIC_H
> +
> +#include "hw/irq.h"
> +#include "hw/sysbus.h"
> +
> +#define TYPE_RISCV_CLIC "riscv_clic"
> +#define RISCV_CLIC(obj) \
> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
> +
> +/*
> + * CLIC per hart active interrupts
> + *
> + * We maintain per hart lists of enabled interrupts sorted by
> + * mode+level+priority. The sorting is done on the configuration path
> + * so that the interrupt delivery fastpath can linear scan enabled
> + * interrupts in priority order.
> + */
> +typedef struct CLICActiveInterrupt {
> +    uint16_t intcfg;
> +    uint16_t irq;
> +} CLICActiveInterrupt;
> +
> +typedef enum TRIG_TYPE {
> +    POSITIVE_LEVEL,
> +    POSITIVE_EDGE,
> +    NEG_LEVEL,
> +    NEG_EDGE,
> +} TRIG_TYPE;
> +
> +typedef struct RISCVCLICState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +
> +    /*< public >*/
> +
> +    /* Implementaion parameters */
> +    bool prv_s;
> +    bool prv_u;
> +    uint32_t num_harts;
> +    uint32_t num_sources;
> +    uint32_t clic_size;
> +    uint32_t clic_mmode_base;
> +    uint32_t clicintctlbits;
> +    uint64_t mclicbase;
> +    char *version;
> +
> +    /* Global configuration */
> +    uint8_t nmbits;
> +    uint8_t nlbits;
> +    uint8_t nvbits;
> +    uint32_t clicinfo;
> +    uint32_t clicinttrig[32];
> +
> +    /* Aperture configuration */
> +    uint8_t *clicintip;
> +    uint8_t *clicintie;
> +    uint8_t *clicintattr;
> +    uint8_t *clicintctl;
> +
> +    /* Complatible with v0.8 */
> +    uint32_t mintthresh;
> +    uint32_t sintthresh;
> +    uint32_t uintthresh;
> +
> +    /* QEMU implementaion related fields */
> +    uint32_t *exccode;
> +    CLICActiveInterrupt *active_list;
> +    size_t *active_count;
> +    MemoryRegion mmio;
> +    qemu_irq *cpu_irqs;
> +} RISCVCLICState;
> +
> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
> +                               uint32_t num_harts, uint32_t num_sources,
> +                               uint8_t clicintctlbits,
> +                               const char *version);
> +
> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
> *irq);
> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
> irq);
> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
> irq);
> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
> +#endif
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index a5eab26a69..9e389d7bbf 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -33,6 +33,7 @@
>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>
>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
> @@ -247,6 +248,7 @@ struct CPURISCVState {
>      /* Fields from here on are preserved across CPU reset. */
>      QEMUTimer *timer; /* Internal timer */
>      void *clic;       /* clic interrupt controller */
> +    uint32_t exccode; /* clic irq encode */
>  };
>
>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
> --
> 2.25.1
>
>
>
Frank Chang July 13, 2021, 6:57 a.m. UTC | #33
Frank Chang <frank.chang@sifive.com> 於 2021年7月13日 週二 下午2:53寫道:

> LIU Zhiwei <zhiwei_liu@c-sky.com> 於 2021年4月9日 週五 下午3:57寫道:
>
>> The Core-Local Interrupt Controller (CLIC) provides low-latency,
>> vectored, pre-emptive interrupts for RISC-V systems.
>>
>> The CLIC also supports a new Selective Hardware Vectoring feature
>> that allow users to optimize each interrupt for either faster
>> response or smaller code size.
>>
>> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
>> ---
>>  default-configs/devices/riscv32-softmmu.mak |   1 +
>>  default-configs/devices/riscv64-softmmu.mak |   1 +
>>  hw/intc/Kconfig                             |   3 +
>>  hw/intc/meson.build                         |   1 +
>>  hw/intc/riscv_clic.c                        | 835 ++++++++++++++++++++
>>  include/hw/intc/riscv_clic.h                | 103 +++
>>  target/riscv/cpu.h                          |   2 +
>>  7 files changed, 946 insertions(+)
>>  create mode 100644 hw/intc/riscv_clic.c
>>  create mode 100644 include/hw/intc/riscv_clic.h
>>
>> diff --git a/default-configs/devices/riscv32-softmmu.mak
>> b/default-configs/devices/riscv32-softmmu.mak
>> index d847bd5692..1430c30588 100644
>> --- a/default-configs/devices/riscv32-softmmu.mak
>> +++ b/default-configs/devices/riscv32-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/default-configs/devices/riscv64-softmmu.mak
>> b/default-configs/devices/riscv64-softmmu.mak
>> index d5eec75f05..396800bbbd 100644
>> --- a/default-configs/devices/riscv64-softmmu.mak
>> +++ b/default-configs/devices/riscv64-softmmu.mak
>> @@ -5,6 +5,7 @@
>>  #CONFIG_PCI_DEVICES=n
>>  CONFIG_SEMIHOSTING=y
>>  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
>> +CONFIG_RISCV_CLIC=y
>>
>>  # Boards:
>>  #
>> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
>> index f4694088a4..5bf492b48f 100644
>> --- a/hw/intc/Kconfig
>> +++ b/hw/intc/Kconfig
>> @@ -68,6 +68,9 @@ config SIFIVE_CLINT
>>  config SIFIVE_PLIC
>>      bool
>>
>> +config RISCV_CLIC
>> +    bool
>> +
>>  config GOLDFISH_PIC
>>      bool
>>
>> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
>> index 1c299039f6..2aa71b6738 100644
>> --- a/hw/intc/meson.build
>> +++ b/hw/intc/meson.build
>> @@ -50,6 +50,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true:
>> files('s390_flic_kvm.c'))
>>  specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true:
>> files('sifive_clint.c'))
>>  specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true:
>> files('sifive_plic.c'))
>> +specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true:
>> files('riscv_clic.c'))
>>  specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
>>  specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
>>                 if_true: files('xics_kvm.c'))
>> diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
>> new file mode 100644
>> index 0000000000..8ad534c506
>> --- /dev/null
>> +++ b/hw/intc/riscv_clic.c
>> @@ -0,0 +1,835 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "sysemu/qtest.h"
>> +#include "target/riscv/cpu.h"
>> +#include "hw/qdev-properties.h"
>> +#include "hw/intc/riscv_clic.h"
>> +
>> +/*
>> + * The 2-bit trig WARL field specifies the trigger type and polarity for
>> each
>> + * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
>> + * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is
>> defined as
>> + * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
>> + */
>> +
>> +static inline TRIG_TYPE
>> +riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
>> +}
>> +
>> +static inline bool
>> +riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
>> +{
>> +    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int nlbits = clic->nlbits;
>> +
>> +    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
>> +    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
>> +    /* unused level bits are set to 1 */
>> +    return (intctl & mask_il) | mask_padding;
>> +}
>> +
>> +static uint8_t
>> +riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
>> +{
>> +    int npbits = clic->clicintctlbits - clic->nlbits;
>> +    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
>> +    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
>> +
>> +    if (npbits < 0) {
>> +        return UINT8_MAX;
>> +    }
>> +    /* unused priority bits are set to 1 */
>> +    return (intctl & mask_priority) | mask_padding;
>> +}
>> +
>> +static void
>> +riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
>> +                         uint8_t *mode, uint8_t *level,
>> +                         uint8_t *priority)
>> +{
>> +    *mode = intcfg >> 8;
>> +    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
>> +    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
>> +}
>> +
>> +/*
>> + * In a system with multiple harts, the M-mode CLIC regions for all the
>> harts
>> + * are placed contiguously in the memory space, followed by the S-mode
>> CLIC
>> + * regions for all harts. (Section 3.11)
>> + */
>> +static size_t
>> +riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t mode_offset = 0;
>> +    size_t unit = clic->num_harts * clic->num_sources;
>> +
>> +    switch (mode) {
>> +    case PRV_M:
>> +        mode_offset = 0;
>> +        break;
>> +    case PRV_S:
>> +        mode_offset = unit;
>> +        break;
>> +    case PRV_U:
>> +        mode_offset = clic->prv_s ? 2 * unit : unit;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid mode %d\n", mode);
>> +        exit(1);
>> +    }
>> +    return mode_offset + hartid * clic->num_sources + irq;
>> +}
>> +
>> +static void riscv_clic_next_interrupt(void *opaque, int hartid)
>> +{
>> +    /*
>> +     * Scan active list for highest priority pending interrupts
>> +     * comparing against this harts mintstatus register and interrupt
>> +     * the core if we have a higher priority interrupt to deliver
>> +     */
>> +    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
>> +    CPURISCVState *env = &cpu->env;
>> +    RISCVCLICState *clic = (RISCVCLICState *)opaque;
>> +
>> +    int il[4] = {
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
>> +            clic->mintthresh), /* PRV_U */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
>> +            clic->sintthresh), /* PRV_S */
>> +        0,                     /* reserverd */
>> +        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
>> +            clic->uintthresh)  /* PRV_M */
>> +    };
>> +
>> +    /* Get sorted list of enabled interrupts for this hart */
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
>> +    size_t active_count = clic->active_count[hartid];
>> +    uint8_t mode, level, priority;
>> +
>> +    /* Loop through the enabled interrupts sorted by mode+priority+level
>> */
>> +    while (active_count) {
>> +        size_t irq_offset;
>> +        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
>> +                                 &priority);
>> +        if (mode < env->priv || (mode == env->priv && level <=
>> il[mode])) {
>> +            /*
>> +             * No pending interrupts with high enough mode+priority+level
>> +             * break and clear pending interrupt for this hart
>> +             */
>> +            break;
>> +        }
>> +        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> active->irq);
>> +        /* Check pending interrupt with high enough mode+priority+level
>> */
>> +        if (clic->clicintip[irq_offset]) {
>> +            /* Clean vector edge-triggered pending */
>> +            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
>> +                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
>> +                clic->clicintip[irq_offset] = 0;
>> +            }
>> +            /* Post pending interrupt for this hart */
>> +            clic->exccode[hartid] = active->irq | mode << 12 | level <<
>> 14;
>> +            qemu_set_irq(clic->cpu_irqs[hartid], 1);
>> +            return;
>> +        }
>> +        /* Check next enabled interrupt */
>> +        active_count--;
>> +        active++;
>> +    }
>> +}
>>
>
> Hi Zhiwei,
>
> The global interrupt may be disabled (xstatus.xie = 0) at the time active
> interrupt i
> is both enabled and pending (i.e. clicintie[i] = 1 && clicintip[i] = 1)
> Therefore, interrupt i will not be taken right away until xstatus.xie is
> set to 1.
>
> However, during global interrupt is disabled,
> SW/HW may either disable or change pending interrupt i to non-pending.
> Interrupt i should not be active anymore.
> But current implementation can't reflect that because
> CPU_INTERRUPT_CLIC bit won't be unset.
>
> To solve this issue, in my own implementation, I tried to:
>
> In riscv_clic_update_intip(), if interrupt i turns to non-pending,
> check if interrupt i is an active pending interrupt.
> (by decoding IRQ number from env->exccode and check if
> cpu->interrupt_request flag also has CPU_INTERRUPT_CLIC bit set.)
>
> If so, interrupt i is current active pending interrupt to be disabled, we
> have to
> unset CPU_INTERRUPT_CLIC bit from interrupt request flag for the hart
> to prevent interrupt i been taken later as it's not pending anymore.
> Then call riscv_clic_next_interrupt() as usual to pick next interrupt.
>
> Same behavior is also applied in riscv_clic_update_intie() when
> interrupt i is disabled and interrupt i is also the active pending
> interrupt.
>
> Also, I think the effective interrupt level check should be moved
> from riscv_clic_update_intip() to riscv_cpu_local_irq_mode_enabled() in
> cpu_helper.c.
>

Sorry, I mean from riscv_clic_next_interrupt() to
riscv_cpu_local_irq_mode_enabled().


>
> Consider the following scenario:
>     a. Active pending interrupt i is not been handled due to global
> interrupt is disabled.
>     b. Software accesses xnxti CSR with write side effect (xstatus.xie is
> still set to 0).
>         xintstatus.xil will be updated to the interrupt level of interrupt
> i.
>     c. Interrupt i turns to non-pending/disabled.
>     d. Interrupt j is enabled and pending.
>     e. Interrupt j won't be selected in riscv_clic_next_interrupt() if its
> interrupt level
>         is lower than interrupt i (xinitstatus.xil still holds the
> interrupt level of interrupt i),
>         which is incorrect because interrupt i is not pending and active
> anymore.
>
> By moving effective interrupt level check from
> riscv_clic_update_intip() to riscv_cpu_local_irq_mode_enabled()
>

Should be riscv_clic_next_interrupt(), not riscv_clic_update_intip().

Regards,
Frank Chang


> can eliminate the issue of lower-priority interrupt been masked out
> by higher-interrupt-level non active interrupt.
>
> Let me know if I miss anything.
>
> Regards,
> Frank Chang
>
>
>> +
>> +/*
>> + * Any interrupt i that is not accessible to S-mode or U-Mode
>> + * appears as hard-wired zeros in clicintip[i], clicintie[i],
>> + * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
>> + */
>> +static bool
>> +riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int
>> irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    if (!clic->prv_s && !clic->prv_u) { /* M */
>> +        return mode == PRV_M;
>> +    } else if (!clic->prv_s) { /* M/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_U);
>> +        default:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 for M/U hart");
>> +            exit(1);
>> +        }
>> +    } else { /* M/S/U */
>> +        switch (clic->nmbits) {
>> +        case 0:
>> +            return mode == PRV_M;
>> +        case 1:
>> +            return clic->clicintattr[irq_offset] & 0x80 ? (mode ==
>> PRV_M) :
>> +                                                          (mode ==
>> PRV_S);
>> +        case 2:
>> +            return mode == clic->clicintattr[irq_offset];
>> +        case 3:
>> +            qemu_log_mask(LOG_GUEST_ERROR,
>> +                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
>> +            exit(1);
>> +        }
>> +    }
>> +    return false;
>> +}
>> +
>> +/*
>> + * For level-triggered interrupts, software writes to pending bits are
>> + * ignored completely. (Section 3.4)
>> + */
>> +static bool
>> +riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid,
>> int irq)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t value)
>> +{
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = !!value;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +/*
>> + * For security purpose, the field can only be set to a privilege
>> + * level that is equal mode to or lower than the currently running
>> + * privilege level.(Section 3.6)
>> + */
>> +
>> +static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t
>> value)
>> +{
>> +    int mode = extract64(value, 6, 2);
>> +
>> +    if (!qtest_enabled()) {
>> +        CPURISCVState *env = current_cpu->env_ptr;
>> +        if (env->priv < mode) {
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static inline int riscv_clic_encode_priority(const CLICActiveInterrupt
>> *i)
>> +{
>> +    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority
>> */
>> +           (i->irq & 0xfff);             /* Highest irq number */
>> +}
>> +
>> +static int riscv_clic_active_compare(const void *a, const void *b)
>> +{
>> +    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
>> +}
>> +
>> +static void
>> +riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
>> +                        int irq, uint64_t new_intie)
>> +{
>> +    size_t hart_offset = hartid * clic->num_sources;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
>> +    size_t *active_count = &clic->active_count[hartid];
>> +
>> +    uint8_t old_intie = clic->clicintie[irq_offset];
>> +    clic->clicintie[irq_offset] = !!new_intie;
>> +
>> +    /* Add to or remove from list of active interrupts */
>> +    if (new_intie && !old_intie) {
>> +        active_list[*active_count].intcfg = (mode << 8) |
>> +                                            clic->clicintctl[irq_offset];
>> +        active_list[*active_count].irq = irq;
>> +        (*active_count)++;
>> +    } else if (!new_intie && old_intie) {
>> +        CLICActiveInterrupt key = {
>> +            (mode << 8) | clic->clicintctl[irq_offset], irq
>> +        };
>> +        CLICActiveInterrupt *result = bsearch(&key,
>> +                                              active_list, *active_count,
>> +
>> sizeof(CLICActiveInterrupt),
>> +                                              riscv_clic_active_compare);
>> +        size_t elem = (result - active_list) /
>> sizeof(CLICActiveInterrupt);
>> +        size_t sz = (--(*active_count) - elem) *
>> sizeof(CLICActiveInterrupt);
>> +        assert(result);
>> +        memmove(&result[0], &result[1], sz);
>> +    }
>> +
>> +    /* Sort list of active interrupts */
>> +    qsort(active_list, *active_count,
>> +          sizeof(CLICActiveInterrupt),
>> +          riscv_clic_active_compare);
>> +
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +static void
>> +riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
>> +                      uint64_t value, unsigned size,
>> +                      int mode, int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
>> +            /*
>> +             * The actual pending bit is located at bit 0 (i.e., the
>> +             * leastsignificant bit). In case future extensions expand
>> the bit
>> +             * field, from FW perspective clicintip[i]=zero means no
>> interrupt
>> +             * pending, and clicintip[i]!=0 (not just 1) indicates an
>> +             * interrupt is pending. (Section 3.4)
>> +             */
>> +            if (value != clic->clicintip[irq_offset]) {
>> +                riscv_clic_update_intip(clic, mode, hartid, irq, value);
>> +            }
>> +        }
>> +        break;
>> +    case 1: /* clicintie[i] */
>> +        if (clic->clicintie[irq_offset] != value) {
>> +            riscv_clic_update_intie(clic, mode, hartid, irq, value);
>> +        }
>> +        break;
>> +    case 2: /* clicintattr[i] */
>> +        if (riscv_clic_validate_intattr(clic, value)) {
>> +            if (clic->clicintattr[irq_offset] != value) {
>> +                /* When nmbits=2, check WARL */
>> +                bool invalid = (clic->nmbits == 2) &&
>> +                               (extract64(value, 6, 2) == 0b10);
>> +                if (invalid) {
>> +                    uint8_t old_mode =
>> extract32(clic->clicintattr[irq_offset],
>> +                                                 6, 2);
>> +                    value = deposit32(value, 6, 2, old_mode);
>> +                }
>> +                clic->clicintattr[irq_offset] = value;
>> +                riscv_clic_next_interrupt(clic, hartid);
>> +            }
>> +        }
>> +        break;
>> +    case 3: /* clicintctl[i] */
>> +        if (value != clic->clicintctl[irq_offset]) {
>> +            clic->clicintctl[irq_offset] = value;
>> +            riscv_clic_next_interrupt(clic, hartid);
>> +        }
>> +        break;
>> +    }
>> +}
>> +
>> +static uint64_t
>> +riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
>> +                     int hartid, int irq)
>> +{
>> +    int req = extract32(addr, 0, 2);
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +
>> +    if (hartid >= clic->num_harts) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
>> +                      hartid, addr);
>> +        return 0;
>> +    }
>> +
>> +    if (irq >= clic->num_sources) {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq,
>> addr);
>> +        return 0;
>> +    }
>> +
>> +    switch (req) {
>> +    case 0: /* clicintip[i] */
>> +        return clic->clicintip[irq_offset];
>> +    case 1: /* clicintie[i] */
>> +        return clic->clicintie[irq_offset];
>> +    case 2: /* clicintattr[i] */
>> +        /*
>> +         * clicintattr register layout
>> +         * Bits Field
>> +         * 7:6 mode
>> +         * 5:3 reserved (WPRI 0)
>> +         * 2:1 trig
>> +         * 0 shv
>> +         */
>> +        return clic->clicintattr[irq_offset] & ~0x38;
>> +    case 3: /* clicintctrl */
>> +        /*
>> +         * The implemented bits are kept left-justified in the
>> most-significant
>> +         * bits of each 8-bit clicintctl[i] register, with the lower
>> +         * unimplemented bits treated as hardwired to 1.(Section 3.7)
>> +         */
>> +        return clic->clicintctl[irq_offset] |
>> +               ((1 << (8 - clic->clicintctlbits)) - 1);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/* Return target interrupt mode */
>> +static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode = addr / (4 * clic->num_harts * clic->num_sources);
>> +    switch (mode) {
>> +    case 0:
>> +        return PRV_M;
>> +    case 1:
>> +        assert(clic->prv_s || clic->prv_u);
>> +        return clic->prv_s ? PRV_S : PRV_U;
>> +    case 2:
>> +        assert(clic->prv_s && clic->prv_u);
>> +        return PRV_U;
>> +    default:
>> +        g_assert_not_reached();
>> +        break;
>> +    }
>> +}
>> +
>> +/* Return target hart id */
>> +static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int mode_unit = 4 * clic->num_harts * clic->num_sources;
>> +    int hart_unit = 4 * clic->num_sources;
>> +
>> +    return (addr % mode_unit) / hart_unit;
>> +}
>> +
>> +/* Return target interrupt number */
>> +static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
>> +{
>> +    int hart_unit = 4 * clic->num_sources;
>> +    return (addr % hart_unit) / 4;
>> +}
>> +
>> +static void
>> +riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned
>> size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                {
>> +                    uint8_t nlbits = extract32(value, 1, 4);
>> +                    uint8_t nmbits = extract32(value, 5, 2);
>> +
>> +                    /*
>> +                     * The 4-bit cliccfg.nlbits WARL field.
>> +                     * Valid values are 0—8.
>> +                     */
>> +                    if (nlbits <= 8) {
>> +                        clic->nlbits = nlbits;
>> +                    }
>> +                    /* Valid values are given by implemented priviledges
>> */
>> +                    if (clic->prv_s && clic->prv_u) {
>> +                        if (nmbits <= 2) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else if (clic->prv_u) {
>> +                        if (nmbits <= 1) {
>> +                            clic->nmbits = nmbits;
>> +                        }
>> +                    } else {
>> +                        assert(!clic->prv_s);
>> +                        if (nmbits == 0) {
>> +                            clic->nmbits = 0;
>> +                        }
>> +                    }
>> +                    clic->nvbits = extract32(value, 0, 1);
>> +                    break;
>> +                }
>> +            case 1: /* clicinfo, read-only register */
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: write read-only clicinfo.\n");
>> +                break;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                {
>> +                    uint32_t interrupt_number = value &
>> MAKE_64BIT_MASK(0, 13);
>> +                    if (interrupt_number <= clic->num_sources) {
>> +                        value &= ~MAKE_64BIT_MASK(13, 18);
>> +                        clic->clicinttrig[index - 0x10] = value;
>> +                    }
>> +                    break;
>> +                }
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    clic->mintthresh = value;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid write addr: 0x%"
>> HWADDR_PRIx "\n",
>> +                              addr);
>> +                return;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                riscv_clic_hart_write(clic, addr, value, size, mode,
>> +                                      hartid, irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +}
>> +
>> +static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    hwaddr clic_size = clic->clic_size;
>> +    int hartid, mode, irq;
>> +
>> +    if (addr < clic_size) {
>> +        if (addr < 0x1000) {
>> +            assert(addr % 4 == 0);
>> +            int index = addr / 4;
>> +            switch (index) {
>> +            case 0: /* cliccfg */
>> +                return clic->nvbits |
>> +                       (clic->nlbits << 1) |
>> +                       (clic->nmbits << 5);
>> +            case 1: /* clicinfo */
>> +                /*
>> +                 * clicinfo register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 reserved (WARL 0)
>> +                 * 30:25 num_trigger
>> +                 * 24:21 CLICINTCTLBITS
>> +                 * 20:13 version (for version control)
>> +                 * 12:0 num_interrupt
>> +                 */
>> +                return clic->clicinfo & ~INT32_MAX;
>> +            case 0x10 ... 0x2F: /* clicinttrig */
>> +                /*
>> +                 * clicinttrig register layout
>> +                 *
>> +                 * Bits Field
>> +                 * 31 enable
>> +                 * 30:13 reserved (WARL 0)
>> +                 * 12:0 interrupt_number
>> +                 */
>> +                return clic->clicinttrig[index - 0x10] &
>> +                       ~MAKE_64BIT_MASK(13, 18);
>> +            case 2: /* mintthresh */
>> +                if (!strcmp(clic->version, "v0.8")) {
>> +                    return clic->mintthresh;
>> +                    break;
>> +                }
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            default:
>> +                qemu_log_mask(LOG_GUEST_ERROR,
>> +                              "clic: invalid read : 0x%" HWADDR_PRIx
>> "\n",
>> +                              addr);
>> +                break;
>> +            }
>> +        } else {
>> +            addr -= 0x1000;
>> +            hartid = riscv_clic_get_hartid(clic, addr);
>> +            mode = riscv_clic_get_mode(clic, addr);
>> +            irq = riscv_clic_get_irq(clic, addr);
>> +
>> +            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
>> +                return riscv_clic_hart_read(clic, addr, mode, hartid,
>> irq);
>> +            }
>> +        }
>> +    } else {
>> +        qemu_log_mask(LOG_GUEST_ERROR,
>> +                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void riscv_clic_set_irq(void *opaque, int id, int level)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    int irq, hartid, mode;
>> +    hwaddr addr = 4 * id;
>> +    TRIG_TYPE type;
>> +
>> +    hartid = riscv_clic_get_hartid(clic, addr);
>> +    mode = riscv_clic_get_mode(clic, addr);
>> +    irq = riscv_clic_get_irq(clic, addr);
>> +    type = riscv_clic_get_trigger_type(clic, id);
>> +
>> +    /*
>> +     * In general, the edge-triggered interrupt state should be kept in
>> pending
>> +     * bit, while the level-triggered interrupt should be kept in the
>> level
>> +     * state of the incoming wire.
>> +     *
>> +     * For CLIC, model the level-triggered interrupt by read-only
>> pending bit.
>> +     */
>> +    if (level) {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +        case POSITIVE_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case NEG_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        case NEG_EDGE:
>> +            break;
>> +        }
>> +    } else {
>> +        switch (type) {
>> +        case POSITIVE_LEVEL:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, level);
>> +            break;
>> +        case POSITIVE_EDGE:
>> +            break;
>> +        case NEG_LEVEL:
>> +        case NEG_EDGE:
>> +            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
>> +            break;
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
>> +{
>> +    CPURISCVState *env = (CPURISCVState *)opaque;
>> +    RISCVCLICState *clic = env->clic;
>> +    CPUState *cpu = env_cpu(env);
>> +
>> +    if (level) {
>> +        env->exccode = clic->exccode[cpu->cpu_index];
>> +        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps riscv_clic_ops = {
>> +    .read = riscv_clic_read,
>> +    .write = riscv_clic_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 8
>> +    }
>> +};
>> +
>> +static void riscv_clic_realize(DeviceState *dev, Error **errp)
>> +{
>> +    RISCVCLICState *clic = RISCV_CLIC(dev);
>> +    size_t harts_x_sources = clic->num_harts * clic->num_sources;
>> +    int irqs, i;
>> +
>> +    if (clic->prv_s && clic->prv_u) {
>> +        irqs = 3 * harts_x_sources;
>> +    } else if (clic->prv_s || clic->prv_u) {
>> +        irqs = 2 * harts_x_sources;
>> +    } else {
>> +        irqs = harts_x_sources;
>> +    }
>> +
>> +    clic->clic_size = irqs * 4 + 0x1000;
>> +    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops,
>> clic,
>> +                          TYPE_RISCV_CLIC, clic->clic_size);
>> +
>> +    clic->clicintip = g_new0(uint8_t, irqs);
>> +    clic->clicintie = g_new0(uint8_t, irqs);
>> +    clic->clicintattr = g_new0(uint8_t, irqs);
>> +    clic->clicintctl = g_new0(uint8_t, irqs);
>> +    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
>> +    clic->active_count = g_new0(size_t, clic->num_harts);
>> +    clic->exccode = g_new0(uint32_t, clic->num_harts);
>> +    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
>> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
>> +
>> +    /* Allocate irq through gpio, so that we can use qtest */
>> +    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
>> +    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
>> +
>> +    for (i = 0; i < clic->num_harts; i++) {
>> +        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
>> +        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
>> +                                         &cpu->env, 1);
>> +        qdev_connect_gpio_out(dev, i, irq);
>> +        cpu->env.clic = clic;
>> +    }
>> +}
>> +
>> +static Property riscv_clic_properties[] = {
>> +    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
>> +    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
>> +    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
>> +    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
>> +    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits,
>> 0),
>> +    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
>> +    DEFINE_PROP_STRING("version", RISCVCLICState, version),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void riscv_clic_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = riscv_clic_realize;
>> +    device_class_set_props(dc, riscv_clic_properties);
>> +}
>> +
>> +static const TypeInfo riscv_clic_info = {
>> +    .name          = TYPE_RISCV_CLIC,
>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(RISCVCLICState),
>> +    .class_init    = riscv_clic_class_init,
>> +};
>> +
>> +static void riscv_clic_register_types(void)
>> +{
>> +    type_register_static(&riscv_clic_info);
>> +}
>> +
>> +type_init(riscv_clic_register_types)
>> +
>> +/*
>> + * riscv_clic_create:
>> + *
>> + * @addr: base address of M-Mode CLIC memory-mapped registers
>> + * @prv_s: have smode region
>> + * @prv_u: have umode region
>> + * @num_harts: number of CPU harts
>> + * @num_sources: number of interrupts supporting by each aperture
>> + * @clicintctlbits: bits are actually implemented in the clicintctl
>> registers
>> + * @version: clic version, such as "v0.9"
>> + *
>> + * Returns: the device object
>> + */
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version)
>> +{
>> +    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
>> +
>> +    assert(num_sources <= 4096);
>> +    assert(num_harts <= 1024);
>> +    assert(clicintctlbits <= 8);
>> +    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
>> +
>> +    qdev_prop_set_bit(dev, "prv-s", prv_s);
>> +    qdev_prop_set_bit(dev, "prv-u", prv_u);
>> +    qdev_prop_set_uint32(dev, "num-harts", num_harts);
>> +    qdev_prop_set_uint32(dev, "num-sources", num_sources);
>> +    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
>> +    qdev_prop_set_uint64(dev, "mclicbase", addr);
>> +    qdev_prop_set_string(dev, "version", version);
>> +
>> +    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
>> +    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
>> +    return dev;
>> +}
>> +
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    riscv_clic_next_interrupt(clic, hartid);
>> +}
>> +
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_shv_interrupt(clic, irq_offset);
>> +}
>> +
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    return riscv_clic_is_edge_triggered(clic, irq_offset);
>> +}
>> +
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq)
>> +{
>> +    RISCVCLICState *clic = opaque;
>> +    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid,
>> irq);
>> +    clic->clicintip[irq_offset] = 0;
>> +}
>> +
>> +/*
>> + * The new CLIC interrupt-handling mode is encoded as a new state in
>> + * the existing WARL xtvec register, where the low two bits of  are 11.
>> + */
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env)
>> +{
>> +    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
>> +    return env->clic && ((xtvec & 0x3) == 3);
>> +}
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
>> +                               int *il, int *irq)
>> +{
>> +    *irq = extract32(exccode, 0, 12);
>> +    *mode = extract32(exccode, 12, 2);
>> +    *il = extract32(exccode, 14, 8);
>> +}
>> diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
>> new file mode 100644
>> index 0000000000..e5f89672a6
>> --- /dev/null
>> +++ b/include/hw/intc/riscv_clic.h
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RISC-V CLIC(Core Local Interrupt Controller) interface.
>> + *
>> + * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2 or later, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
>> for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef RISCV_CLIC_H
>> +#define RISCV_CLIC_H
>> +
>> +#include "hw/irq.h"
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_RISCV_CLIC "riscv_clic"
>> +#define RISCV_CLIC(obj) \
>> +    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
>> +
>> +/*
>> + * CLIC per hart active interrupts
>> + *
>> + * We maintain per hart lists of enabled interrupts sorted by
>> + * mode+level+priority. The sorting is done on the configuration path
>> + * so that the interrupt delivery fastpath can linear scan enabled
>> + * interrupts in priority order.
>> + */
>> +typedef struct CLICActiveInterrupt {
>> +    uint16_t intcfg;
>> +    uint16_t irq;
>> +} CLICActiveInterrupt;
>> +
>> +typedef enum TRIG_TYPE {
>> +    POSITIVE_LEVEL,
>> +    POSITIVE_EDGE,
>> +    NEG_LEVEL,
>> +    NEG_EDGE,
>> +} TRIG_TYPE;
>> +
>> +typedef struct RISCVCLICState {
>> +    /*< private >*/
>> +    SysBusDevice parent_obj;
>> +
>> +    /*< public >*/
>> +
>> +    /* Implementaion parameters */
>> +    bool prv_s;
>> +    bool prv_u;
>> +    uint32_t num_harts;
>> +    uint32_t num_sources;
>> +    uint32_t clic_size;
>> +    uint32_t clic_mmode_base;
>> +    uint32_t clicintctlbits;
>> +    uint64_t mclicbase;
>> +    char *version;
>> +
>> +    /* Global configuration */
>> +    uint8_t nmbits;
>> +    uint8_t nlbits;
>> +    uint8_t nvbits;
>> +    uint32_t clicinfo;
>> +    uint32_t clicinttrig[32];
>> +
>> +    /* Aperture configuration */
>> +    uint8_t *clicintip;
>> +    uint8_t *clicintie;
>> +    uint8_t *clicintattr;
>> +    uint8_t *clicintctl;
>> +
>> +    /* Complatible with v0.8 */
>> +    uint32_t mintthresh;
>> +    uint32_t sintthresh;
>> +    uint32_t uintthresh;
>> +
>> +    /* QEMU implementaion related fields */
>> +    uint32_t *exccode;
>> +    CLICActiveInterrupt *active_list;
>> +    size_t *active_count;
>> +    MemoryRegion mmio;
>> +    qemu_irq *cpu_irqs;
>> +} RISCVCLICState;
>> +
>> +DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
>> +                               uint32_t num_harts, uint32_t num_sources,
>> +                               uint8_t clicintctlbits,
>> +                               const char *version);
>> +
>> +void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int
>> *irq);
>> +void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int
>> irq);
>> +bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int
>> irq);
>> +void riscv_clic_get_next_interrupt(void *opaque, int hartid);
>> +bool riscv_clic_is_clic_mode(CPURISCVState *env);
>> +#endif
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index a5eab26a69..9e389d7bbf 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -33,6 +33,7 @@
>>  #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
>>  #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
>>  #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
>> +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
>>
>>  #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
>>  #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
>> @@ -247,6 +248,7 @@ struct CPURISCVState {
>>      /* Fields from here on are preserved across CPU reset. */
>>      QEMUTimer *timer; /* Internal timer */
>>      void *clic;       /* clic interrupt controller */
>> +    uint32_t exccode; /* clic irq encode */
>>  };
>>
>>  OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,
>> --
>> 2.25.1
>>
>>
>>
diff mbox series

Patch

diff --git a/default-configs/devices/riscv32-softmmu.mak b/default-configs/devices/riscv32-softmmu.mak
index d847bd5692..1430c30588 100644
--- a/default-configs/devices/riscv32-softmmu.mak
+++ b/default-configs/devices/riscv32-softmmu.mak
@@ -5,6 +5,7 @@ 
 #CONFIG_PCI_DEVICES=n
 CONFIG_SEMIHOSTING=y
 CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
+CONFIG_RISCV_CLIC=y
 
 # Boards:
 #
diff --git a/default-configs/devices/riscv64-softmmu.mak b/default-configs/devices/riscv64-softmmu.mak
index d5eec75f05..396800bbbd 100644
--- a/default-configs/devices/riscv64-softmmu.mak
+++ b/default-configs/devices/riscv64-softmmu.mak
@@ -5,6 +5,7 @@ 
 #CONFIG_PCI_DEVICES=n
 CONFIG_SEMIHOSTING=y
 CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
+CONFIG_RISCV_CLIC=y
 
 # Boards:
 #
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index f4694088a4..5bf492b48f 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -68,6 +68,9 @@  config SIFIVE_CLINT
 config SIFIVE_PLIC
     bool
 
+config RISCV_CLIC
+    bool
+
 config GOLDFISH_PIC
     bool
 
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index 1c299039f6..2aa71b6738 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -50,6 +50,7 @@  specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c'))
 specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c'))
 specific_ss.add(when: 'CONFIG_SIFIVE_CLINT', if_true: files('sifive_clint.c'))
 specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c'))
+specific_ss.add(when: 'CONFIG_RISCV_CLIC', if_true: files('riscv_clic.c'))
 specific_ss.add(when: 'CONFIG_XICS', if_true: files('xics.c'))
 specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XICS'],
 		if_true: files('xics_kvm.c'))
diff --git a/hw/intc/riscv_clic.c b/hw/intc/riscv_clic.c
new file mode 100644
index 0000000000..8ad534c506
--- /dev/null
+++ b/hw/intc/riscv_clic.c
@@ -0,0 +1,835 @@ 
+/*
+ * RISC-V CLIC(Core Local Interrupt Controller) for QEMU.
+ *
+ * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/sysbus.h"
+#include "sysemu/qtest.h"
+#include "target/riscv/cpu.h"
+#include "hw/qdev-properties.h"
+#include "hw/intc/riscv_clic.h"
+
+/*
+ * The 2-bit trig WARL field specifies the trigger type and polarity for each
+ * interrupt input. Bit 1, trig[0], is defined as "edge-triggered"
+ * (0: level-triggered, 1: edge-triggered); while bit 2, trig[1], is defined as
+ * "negative-edge" (0: positive-edge, 1: negative-edge). (Section 3.6)
+ */
+
+static inline TRIG_TYPE
+riscv_clic_get_trigger_type(RISCVCLICState *clic, size_t irq_offset)
+{
+    return (clic->clicintattr[irq_offset] >> 1) & 0x3;
+}
+
+static inline bool
+riscv_clic_is_edge_triggered(RISCVCLICState *clic, size_t irq_offset)
+{
+    return (clic->clicintattr[irq_offset] >> 1) & 0x1;
+}
+
+static inline bool
+riscv_clic_is_shv_interrupt(RISCVCLICState *clic, size_t irq_offset)
+{
+    return (clic->clicintattr[irq_offset] & 0x1) && clic->nvbits;
+}
+
+static uint8_t
+riscv_clic_get_interrupt_level(RISCVCLICState *clic, uint8_t intctl)
+{
+    int nlbits = clic->nlbits;
+
+    uint8_t mask_il = ((1 << nlbits) - 1) << (8 - nlbits);
+    uint8_t mask_padding = (1 << (8 - nlbits)) - 1;
+    /* unused level bits are set to 1 */
+    return (intctl & mask_il) | mask_padding;
+}
+
+static uint8_t
+riscv_clic_get_interrupt_priority(RISCVCLICState *clic, uint8_t intctl)
+{
+    int npbits = clic->clicintctlbits - clic->nlbits;
+    uint8_t mask_priority = ((1 << npbits) - 1) << (8 - npbits);
+    uint8_t mask_padding = (1 << (8 - npbits)) - 1;
+
+    if (npbits < 0) {
+        return UINT8_MAX;
+    }
+    /* unused priority bits are set to 1 */
+    return (intctl & mask_priority) | mask_padding;
+}
+
+static void
+riscv_clic_intcfg_decode(RISCVCLICState *clic, uint16_t intcfg,
+                         uint8_t *mode, uint8_t *level,
+                         uint8_t *priority)
+{
+    *mode = intcfg >> 8;
+    *level = riscv_clic_get_interrupt_level(clic, intcfg & 0xff);
+    *priority = riscv_clic_get_interrupt_priority(clic, intcfg & 0xff);
+}
+
+/*
+ * In a system with multiple harts, the M-mode CLIC regions for all the harts
+ * are placed contiguously in the memory space, followed by the S-mode CLIC
+ * regions for all harts. (Section 3.11)
+ */
+static size_t
+riscv_clic_get_irq_offset(RISCVCLICState *clic, int mode, int hartid, int irq)
+{
+    size_t mode_offset = 0;
+    size_t unit = clic->num_harts * clic->num_sources;
+
+    switch (mode) {
+    case PRV_M:
+        mode_offset = 0;
+        break;
+    case PRV_S:
+        mode_offset = unit;
+        break;
+    case PRV_U:
+        mode_offset = clic->prv_s ? 2 * unit : unit;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid mode %d\n", mode);
+        exit(1);
+    }
+    return mode_offset + hartid * clic->num_sources + irq;
+}
+
+static void riscv_clic_next_interrupt(void *opaque, int hartid)
+{
+    /*
+     * Scan active list for highest priority pending interrupts
+     * comparing against this harts mintstatus register and interrupt
+     * the core if we have a higher priority interrupt to deliver
+     */
+    RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid));
+    CPURISCVState *env = &cpu->env;
+    RISCVCLICState *clic = (RISCVCLICState *)opaque;
+
+    int il[4] = {
+        MAX(get_field(env->mintstatus, MINTSTATUS_UIL),
+            clic->mintthresh), /* PRV_U */
+        MAX(get_field(env->mintstatus, MINTSTATUS_SIL),
+            clic->sintthresh), /* PRV_S */
+        0,                     /* reserverd */
+        MAX(get_field(env->mintstatus, MINTSTATUS_MIL),
+            clic->uintthresh)  /* PRV_M */
+    };
+
+    /* Get sorted list of enabled interrupts for this hart */
+    size_t hart_offset = hartid * clic->num_sources;
+    CLICActiveInterrupt *active = &clic->active_list[hart_offset];
+    size_t active_count = clic->active_count[hartid];
+    uint8_t mode, level, priority;
+
+    /* Loop through the enabled interrupts sorted by mode+priority+level */
+    while (active_count) {
+        size_t irq_offset;
+        riscv_clic_intcfg_decode(clic, active->intcfg, &mode, &level,
+                                 &priority);
+        if (mode < env->priv || (mode == env->priv && level <= il[mode])) {
+            /*
+             * No pending interrupts with high enough mode+priority+level
+             * break and clear pending interrupt for this hart
+             */
+            break;
+        }
+        irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, active->irq);
+        /* Check pending interrupt with high enough mode+priority+level */
+        if (clic->clicintip[irq_offset]) {
+            /* Clean vector edge-triggered pending */
+            if (riscv_clic_is_edge_triggered(clic, irq_offset) &&
+                riscv_clic_is_shv_interrupt(clic, irq_offset)) {
+                clic->clicintip[irq_offset] = 0;
+            }
+            /* Post pending interrupt for this hart */
+            clic->exccode[hartid] = active->irq | mode << 12 | level << 14;
+            qemu_set_irq(clic->cpu_irqs[hartid], 1);
+            return;
+        }
+        /* Check next enabled interrupt */
+        active_count--;
+        active++;
+    }
+}
+
+/*
+ * Any interrupt i that is not accessible to S-mode or U-Mode
+ * appears as hard-wired zeros in clicintip[i], clicintie[i],
+ * clicintattr[i], and clicintctl[i].(Section 3.9)(Section 3.10)
+ */
+static bool
+riscv_clic_check_visible(RISCVCLICState *clic, int mode, int hartid, int irq)
+{
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    if (!clic->prv_s && !clic->prv_u) { /* M */
+        return mode == PRV_M;
+    } else if (!clic->prv_s) { /* M/U */
+        switch (clic->nmbits) {
+        case 0:
+            return mode == PRV_M;
+        case 1:
+            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
+                                                          (mode == PRV_U);
+        default:
+            qemu_log_mask(LOG_GUEST_ERROR,
+                "clic: nmbits can only be 0 or 1 for M/U hart");
+            exit(1);
+        }
+    } else { /* M/S/U */
+        switch (clic->nmbits) {
+        case 0:
+            return mode == PRV_M;
+        case 1:
+            return clic->clicintattr[irq_offset] & 0x80 ? (mode == PRV_M) :
+                                                          (mode == PRV_S);
+        case 2:
+            return mode == clic->clicintattr[irq_offset];
+        case 3:
+            qemu_log_mask(LOG_GUEST_ERROR,
+                "clic: nmbits can only be 0 or 1 or 2 for M/S/U hart");
+            exit(1);
+        }
+    }
+    return false;
+}
+
+/*
+ * For level-triggered interrupts, software writes to pending bits are
+ * ignored completely. (Section 3.4)
+ */
+static bool
+riscv_clic_validate_intip(RISCVCLICState *clic, int mode, int hartid, int irq)
+{
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    return riscv_clic_is_edge_triggered(clic, irq_offset);
+}
+
+static void
+riscv_clic_update_intip(RISCVCLICState *clic, int mode, int hartid,
+                        int irq, uint64_t value)
+{
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    clic->clicintip[irq_offset] = !!value;
+    riscv_clic_next_interrupt(clic, hartid);
+}
+
+/*
+ * For security purpose, the field can only be set to a privilege
+ * level that is equal mode to or lower than the currently running
+ * privilege level.(Section 3.6)
+ */
+
+static bool riscv_clic_validate_intattr(RISCVCLICState *clic, uint64_t value)
+{
+    int mode = extract64(value, 6, 2);
+
+    if (!qtest_enabled()) {
+        CPURISCVState *env = current_cpu->env_ptr;
+        if (env->priv < mode) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static inline int riscv_clic_encode_priority(const CLICActiveInterrupt *i)
+{
+    return ((i->intcfg & 0x3ff) << 12) | /* Highest mode+level+priority */
+           (i->irq & 0xfff);             /* Highest irq number */
+}
+
+static int riscv_clic_active_compare(const void *a, const void *b)
+{
+    return riscv_clic_encode_priority(b) - riscv_clic_encode_priority(a);
+}
+
+static void
+riscv_clic_update_intie(RISCVCLICState *clic, int mode, int hartid,
+                        int irq, uint64_t new_intie)
+{
+    size_t hart_offset = hartid * clic->num_sources;
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    CLICActiveInterrupt *active_list = &clic->active_list[hart_offset];
+    size_t *active_count = &clic->active_count[hartid];
+
+    uint8_t old_intie = clic->clicintie[irq_offset];
+    clic->clicintie[irq_offset] = !!new_intie;
+
+    /* Add to or remove from list of active interrupts */
+    if (new_intie && !old_intie) {
+        active_list[*active_count].intcfg = (mode << 8) |
+                                            clic->clicintctl[irq_offset];
+        active_list[*active_count].irq = irq;
+        (*active_count)++;
+    } else if (!new_intie && old_intie) {
+        CLICActiveInterrupt key = {
+            (mode << 8) | clic->clicintctl[irq_offset], irq
+        };
+        CLICActiveInterrupt *result = bsearch(&key,
+                                              active_list, *active_count,
+                                              sizeof(CLICActiveInterrupt),
+                                              riscv_clic_active_compare);
+        size_t elem = (result - active_list) / sizeof(CLICActiveInterrupt);
+        size_t sz = (--(*active_count) - elem) * sizeof(CLICActiveInterrupt);
+        assert(result);
+        memmove(&result[0], &result[1], sz);
+    }
+
+    /* Sort list of active interrupts */
+    qsort(active_list, *active_count,
+          sizeof(CLICActiveInterrupt),
+          riscv_clic_active_compare);
+
+    riscv_clic_next_interrupt(clic, hartid);
+}
+
+static void
+riscv_clic_hart_write(RISCVCLICState *clic, hwaddr addr,
+                      uint64_t value, unsigned size,
+                      int mode, int hartid, int irq)
+{
+    int req = extract32(addr, 0, 2);
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+
+    if (hartid >= clic->num_harts) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
+                      hartid, addr);
+        return;
+    }
+
+    if (irq >= clic->num_sources) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
+        return;
+    }
+
+    switch (req) {
+    case 0: /* clicintip[i] */
+        if (riscv_clic_validate_intip(clic, mode, hartid, irq)) {
+            /*
+             * The actual pending bit is located at bit 0 (i.e., the
+             * leastsignificant bit). In case future extensions expand the bit
+             * field, from FW perspective clicintip[i]=zero means no interrupt
+             * pending, and clicintip[i]!=0 (not just 1) indicates an
+             * interrupt is pending. (Section 3.4)
+             */
+            if (value != clic->clicintip[irq_offset]) {
+                riscv_clic_update_intip(clic, mode, hartid, irq, value);
+            }
+        }
+        break;
+    case 1: /* clicintie[i] */
+        if (clic->clicintie[irq_offset] != value) {
+            riscv_clic_update_intie(clic, mode, hartid, irq, value);
+        }
+        break;
+    case 2: /* clicintattr[i] */
+        if (riscv_clic_validate_intattr(clic, value)) {
+            if (clic->clicintattr[irq_offset] != value) {
+                /* When nmbits=2, check WARL */
+                bool invalid = (clic->nmbits == 2) &&
+                               (extract64(value, 6, 2) == 0b10);
+                if (invalid) {
+                    uint8_t old_mode = extract32(clic->clicintattr[irq_offset],
+                                                 6, 2);
+                    value = deposit32(value, 6, 2, old_mode);
+                }
+                clic->clicintattr[irq_offset] = value;
+                riscv_clic_next_interrupt(clic, hartid);
+            }
+        }
+        break;
+    case 3: /* clicintctl[i] */
+        if (value != clic->clicintctl[irq_offset]) {
+            clic->clicintctl[irq_offset] = value;
+            riscv_clic_next_interrupt(clic, hartid);
+        }
+        break;
+    }
+}
+
+static uint64_t
+riscv_clic_hart_read(RISCVCLICState *clic, hwaddr addr, int mode,
+                     int hartid, int irq)
+{
+    int req = extract32(addr, 0, 2);
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+
+    if (hartid >= clic->num_harts) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n",
+                      hartid, addr);
+        return 0;
+    }
+
+    if (irq >= clic->num_sources) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr);
+        return 0;
+    }
+
+    switch (req) {
+    case 0: /* clicintip[i] */
+        return clic->clicintip[irq_offset];
+    case 1: /* clicintie[i] */
+        return clic->clicintie[irq_offset];
+    case 2: /* clicintattr[i] */
+        /*
+         * clicintattr register layout
+         * Bits Field
+         * 7:6 mode
+         * 5:3 reserved (WPRI 0)
+         * 2:1 trig
+         * 0 shv
+         */
+        return clic->clicintattr[irq_offset] & ~0x38;
+    case 3: /* clicintctrl */
+        /*
+         * The implemented bits are kept left-justified in the most-significant
+         * bits of each 8-bit clicintctl[i] register, with the lower
+         * unimplemented bits treated as hardwired to 1.(Section 3.7)
+         */
+        return clic->clicintctl[irq_offset] |
+               ((1 << (8 - clic->clicintctlbits)) - 1);
+    }
+
+    return 0;
+}
+
+/* Return target interrupt mode */
+static int riscv_clic_get_mode(RISCVCLICState *clic, hwaddr addr)
+{
+    int mode = addr / (4 * clic->num_harts * clic->num_sources);
+    switch (mode) {
+    case 0:
+        return PRV_M;
+    case 1:
+        assert(clic->prv_s || clic->prv_u);
+        return clic->prv_s ? PRV_S : PRV_U;
+    case 2:
+        assert(clic->prv_s && clic->prv_u);
+        return PRV_U;
+    default:
+        g_assert_not_reached();
+        break;
+    }
+}
+
+/* Return target hart id */
+static int riscv_clic_get_hartid(RISCVCLICState *clic, hwaddr addr)
+{
+    int mode_unit = 4 * clic->num_harts * clic->num_sources;
+    int hart_unit = 4 * clic->num_sources;
+
+    return (addr % mode_unit) / hart_unit;
+}
+
+/* Return target interrupt number */
+static int riscv_clic_get_irq(RISCVCLICState *clic, hwaddr addr)
+{
+    int hart_unit = 4 * clic->num_sources;
+    return (addr % hart_unit) / 4;
+}
+
+static void
+riscv_clic_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
+{
+    RISCVCLICState *clic = opaque;
+    hwaddr clic_size = clic->clic_size;
+    int hartid, mode, irq;
+
+    if (addr < clic_size) {
+        if (addr < 0x1000) {
+            assert(addr % 4 == 0);
+            int index = addr / 4;
+            switch (index) {
+            case 0: /* cliccfg */
+                {
+                    uint8_t nlbits = extract32(value, 1, 4);
+                    uint8_t nmbits = extract32(value, 5, 2);
+
+                    /*
+                     * The 4-bit cliccfg.nlbits WARL field.
+                     * Valid values are 0—8.
+                     */
+                    if (nlbits <= 8) {
+                        clic->nlbits = nlbits;
+                    }
+                    /* Valid values are given by implemented priviledges */
+                    if (clic->prv_s && clic->prv_u) {
+                        if (nmbits <= 2) {
+                            clic->nmbits = nmbits;
+                        }
+                    } else if (clic->prv_u) {
+                        if (nmbits <= 1) {
+                            clic->nmbits = nmbits;
+                        }
+                    } else {
+                        assert(!clic->prv_s);
+                        if (nmbits == 0) {
+                            clic->nmbits = 0;
+                        }
+                    }
+                    clic->nvbits = extract32(value, 0, 1);
+                    break;
+                }
+            case 1: /* clicinfo, read-only register */
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "clic: write read-only clicinfo.\n");
+                break;
+            case 0x10 ... 0x2F: /* clicinttrig */
+                {
+                    uint32_t interrupt_number = value & MAKE_64BIT_MASK(0, 13);
+                    if (interrupt_number <= clic->num_sources) {
+                        value &= ~MAKE_64BIT_MASK(13, 18);
+                        clic->clicinttrig[index - 0x10] = value;
+                    }
+                    break;
+                }
+            case 2: /* mintthresh */
+                if (!strcmp(clic->version, "v0.8")) {
+                    clic->mintthresh = value;
+                    break;
+                }
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
+                              addr);
+                break;
+            default:
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "clic: invalid write addr: 0x%" HWADDR_PRIx "\n",
+                              addr);
+                return;
+            }
+        } else {
+            addr -= 0x1000;
+            hartid = riscv_clic_get_hartid(clic, addr);
+            mode = riscv_clic_get_mode(clic, addr);
+            irq = riscv_clic_get_irq(clic, addr);
+
+            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
+                riscv_clic_hart_write(clic, addr, value, size, mode,
+                                      hartid, irq);
+            }
+        }
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr);
+    }
+}
+
+static uint64_t riscv_clic_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RISCVCLICState *clic = opaque;
+    hwaddr clic_size = clic->clic_size;
+    int hartid, mode, irq;
+
+    if (addr < clic_size) {
+        if (addr < 0x1000) {
+            assert(addr % 4 == 0);
+            int index = addr / 4;
+            switch (index) {
+            case 0: /* cliccfg */
+                return clic->nvbits |
+                       (clic->nlbits << 1) |
+                       (clic->nmbits << 5);
+            case 1: /* clicinfo */
+                /*
+                 * clicinfo register layout
+                 *
+                 * Bits Field
+                 * 31 reserved (WARL 0)
+                 * 30:25 num_trigger
+                 * 24:21 CLICINTCTLBITS
+                 * 20:13 version (for version control)
+                 * 12:0 num_interrupt
+                 */
+                return clic->clicinfo & ~INT32_MAX;
+            case 0x10 ... 0x2F: /* clicinttrig */
+                /*
+                 * clicinttrig register layout
+                 *
+                 * Bits Field
+                 * 31 enable
+                 * 30:13 reserved (WARL 0)
+                 * 12:0 interrupt_number
+                 */
+                return clic->clicinttrig[index - 0x10] &
+                       ~MAKE_64BIT_MASK(13, 18);
+            case 2: /* mintthresh */
+                if (!strcmp(clic->version, "v0.8")) {
+                    return clic->mintthresh;
+                    break;
+                }
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
+                              addr);
+                break;
+            default:
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "clic: invalid read : 0x%" HWADDR_PRIx "\n",
+                              addr);
+                break;
+            }
+        } else {
+            addr -= 0x1000;
+            hartid = riscv_clic_get_hartid(clic, addr);
+            mode = riscv_clic_get_mode(clic, addr);
+            irq = riscv_clic_get_irq(clic, addr);
+
+            if (riscv_clic_check_visible(clic, mode, hartid, irq)) {
+                return riscv_clic_hart_read(clic, addr, mode, hartid, irq);
+            }
+        }
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr);
+    }
+    return 0;
+}
+
+static void riscv_clic_set_irq(void *opaque, int id, int level)
+{
+    RISCVCLICState *clic = opaque;
+    int irq, hartid, mode;
+    hwaddr addr = 4 * id;
+    TRIG_TYPE type;
+
+    hartid = riscv_clic_get_hartid(clic, addr);
+    mode = riscv_clic_get_mode(clic, addr);
+    irq = riscv_clic_get_irq(clic, addr);
+    type = riscv_clic_get_trigger_type(clic, id);
+
+    /*
+     * In general, the edge-triggered interrupt state should be kept in pending
+     * bit, while the level-triggered interrupt should be kept in the level
+     * state of the incoming wire.
+     *
+     * For CLIC, model the level-triggered interrupt by read-only pending bit.
+     */
+    if (level) {
+        switch (type) {
+        case POSITIVE_LEVEL:
+        case POSITIVE_EDGE:
+            riscv_clic_update_intip(clic, mode, hartid, irq, level);
+            break;
+        case NEG_LEVEL:
+            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
+            break;
+        case NEG_EDGE:
+            break;
+        }
+    } else {
+        switch (type) {
+        case POSITIVE_LEVEL:
+            riscv_clic_update_intip(clic, mode, hartid, irq, level);
+            break;
+        case POSITIVE_EDGE:
+            break;
+        case NEG_LEVEL:
+        case NEG_EDGE:
+            riscv_clic_update_intip(clic, mode, hartid, irq, !level);
+            break;
+        }
+    }
+}
+
+static void riscv_clic_cpu_irq_handler(void *opaque, int irq, int level)
+{
+    CPURISCVState *env = (CPURISCVState *)opaque;
+    RISCVCLICState *clic = env->clic;
+    CPUState *cpu = env_cpu(env);
+
+    if (level) {
+        env->exccode = clic->exccode[cpu->cpu_index];
+        cpu_interrupt(env_cpu(env), CPU_INTERRUPT_CLIC);
+    }
+}
+
+static const MemoryRegionOps riscv_clic_ops = {
+    .read = riscv_clic_read,
+    .write = riscv_clic_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8
+    }
+};
+
+static void riscv_clic_realize(DeviceState *dev, Error **errp)
+{
+    RISCVCLICState *clic = RISCV_CLIC(dev);
+    size_t harts_x_sources = clic->num_harts * clic->num_sources;
+    int irqs, i;
+
+    if (clic->prv_s && clic->prv_u) {
+        irqs = 3 * harts_x_sources;
+    } else if (clic->prv_s || clic->prv_u) {
+        irqs = 2 * harts_x_sources;
+    } else {
+        irqs = harts_x_sources;
+    }
+
+    clic->clic_size = irqs * 4 + 0x1000;
+    memory_region_init_io(&clic->mmio, OBJECT(dev), &riscv_clic_ops, clic,
+                          TYPE_RISCV_CLIC, clic->clic_size);
+
+    clic->clicintip = g_new0(uint8_t, irqs);
+    clic->clicintie = g_new0(uint8_t, irqs);
+    clic->clicintattr = g_new0(uint8_t, irqs);
+    clic->clicintctl = g_new0(uint8_t, irqs);
+    clic->active_list = g_new0(CLICActiveInterrupt, irqs);
+    clic->active_count = g_new0(size_t, clic->num_harts);
+    clic->exccode = g_new0(uint32_t, clic->num_harts);
+    clic->cpu_irqs = g_new0(qemu_irq, clic->num_harts);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio);
+
+    /* Allocate irq through gpio, so that we can use qtest */
+    qdev_init_gpio_in(dev, riscv_clic_set_irq, irqs);
+    qdev_init_gpio_out(dev, clic->cpu_irqs, clic->num_harts);
+
+    for (i = 0; i < clic->num_harts; i++) {
+        RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
+        qemu_irq irq = qemu_allocate_irq(riscv_clic_cpu_irq_handler,
+                                         &cpu->env, 1);
+        qdev_connect_gpio_out(dev, i, irq);
+        cpu->env.clic = clic;
+    }
+}
+
+static Property riscv_clic_properties[] = {
+    DEFINE_PROP_BOOL("prv-s", RISCVCLICState, prv_s, false),
+    DEFINE_PROP_BOOL("prv-u", RISCVCLICState, prv_u, false),
+    DEFINE_PROP_UINT32("num-harts", RISCVCLICState, num_harts, 0),
+    DEFINE_PROP_UINT32("num-sources", RISCVCLICState, num_sources, 0),
+    DEFINE_PROP_UINT32("clicintctlbits", RISCVCLICState, clicintctlbits, 0),
+    DEFINE_PROP_UINT64("mclicbase", RISCVCLICState, mclicbase, 0),
+    DEFINE_PROP_STRING("version", RISCVCLICState, version),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void riscv_clic_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = riscv_clic_realize;
+    device_class_set_props(dc, riscv_clic_properties);
+}
+
+static const TypeInfo riscv_clic_info = {
+    .name          = TYPE_RISCV_CLIC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RISCVCLICState),
+    .class_init    = riscv_clic_class_init,
+};
+
+static void riscv_clic_register_types(void)
+{
+    type_register_static(&riscv_clic_info);
+}
+
+type_init(riscv_clic_register_types)
+
+/*
+ * riscv_clic_create:
+ *
+ * @addr: base address of M-Mode CLIC memory-mapped registers
+ * @prv_s: have smode region
+ * @prv_u: have umode region
+ * @num_harts: number of CPU harts
+ * @num_sources: number of interrupts supporting by each aperture
+ * @clicintctlbits: bits are actually implemented in the clicintctl registers
+ * @version: clic version, such as "v0.9"
+ *
+ * Returns: the device object
+ */
+DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
+                               uint32_t num_harts, uint32_t num_sources,
+                               uint8_t clicintctlbits,
+                               const char *version)
+{
+    DeviceState *dev = qdev_new(TYPE_RISCV_CLIC);
+
+    assert(num_sources <= 4096);
+    assert(num_harts <= 1024);
+    assert(clicintctlbits <= 8);
+    assert(!strcmp(version, "v0.8") || !strcmp(version, "v0.9"));
+
+    qdev_prop_set_bit(dev, "prv-s", prv_s);
+    qdev_prop_set_bit(dev, "prv-u", prv_u);
+    qdev_prop_set_uint32(dev, "num-harts", num_harts);
+    qdev_prop_set_uint32(dev, "num-sources", num_sources);
+    qdev_prop_set_uint32(dev, "clicintctlbits", clicintctlbits);
+    qdev_prop_set_uint64(dev, "mclicbase", addr);
+    qdev_prop_set_string(dev, "version", version);
+
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
+    return dev;
+}
+
+void riscv_clic_get_next_interrupt(void *opaque, int hartid)
+{
+    RISCVCLICState *clic = opaque;
+    riscv_clic_next_interrupt(clic, hartid);
+}
+
+bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq)
+{
+    RISCVCLICState *clic = opaque;
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    return riscv_clic_is_shv_interrupt(clic, irq_offset);
+}
+
+bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq)
+{
+    RISCVCLICState *clic = opaque;
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    return riscv_clic_is_edge_triggered(clic, irq_offset);
+}
+
+void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq)
+{
+    RISCVCLICState *clic = opaque;
+    size_t irq_offset = riscv_clic_get_irq_offset(clic, mode, hartid, irq);
+    clic->clicintip[irq_offset] = 0;
+}
+
+/*
+ * The new CLIC interrupt-handling mode is encoded as a new state in
+ * the existing WARL xtvec register, where the low two bits of  are 11.
+ */
+bool riscv_clic_is_clic_mode(CPURISCVState *env)
+{
+    target_ulong xtvec = (env->priv == PRV_M) ? env->mtvec : env->stvec;
+    return env->clic && ((xtvec & 0x3) == 3);
+}
+
+void riscv_clic_decode_exccode(uint32_t exccode, int *mode,
+                               int *il, int *irq)
+{
+    *irq = extract32(exccode, 0, 12);
+    *mode = extract32(exccode, 12, 2);
+    *il = extract32(exccode, 14, 8);
+}
diff --git a/include/hw/intc/riscv_clic.h b/include/hw/intc/riscv_clic.h
new file mode 100644
index 0000000000..e5f89672a6
--- /dev/null
+++ b/include/hw/intc/riscv_clic.h
@@ -0,0 +1,103 @@ 
+/*
+ * RISC-V CLIC(Core Local Interrupt Controller) interface.
+ *
+ * Copyright (c) 2021 T-Head Semiconductor Co., Ltd. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RISCV_CLIC_H
+#define RISCV_CLIC_H
+
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+
+#define TYPE_RISCV_CLIC "riscv_clic"
+#define RISCV_CLIC(obj) \
+    OBJECT_CHECK(RISCVCLICState, (obj), TYPE_RISCV_CLIC)
+
+/*
+ * CLIC per hart active interrupts
+ *
+ * We maintain per hart lists of enabled interrupts sorted by
+ * mode+level+priority. The sorting is done on the configuration path
+ * so that the interrupt delivery fastpath can linear scan enabled
+ * interrupts in priority order.
+ */
+typedef struct CLICActiveInterrupt {
+    uint16_t intcfg;
+    uint16_t irq;
+} CLICActiveInterrupt;
+
+typedef enum TRIG_TYPE {
+    POSITIVE_LEVEL,
+    POSITIVE_EDGE,
+    NEG_LEVEL,
+    NEG_EDGE,
+} TRIG_TYPE;
+
+typedef struct RISCVCLICState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    /*< public >*/
+
+    /* Implementaion parameters */
+    bool prv_s;
+    bool prv_u;
+    uint32_t num_harts;
+    uint32_t num_sources;
+    uint32_t clic_size;
+    uint32_t clic_mmode_base;
+    uint32_t clicintctlbits;
+    uint64_t mclicbase;
+    char *version;
+
+    /* Global configuration */
+    uint8_t nmbits;
+    uint8_t nlbits;
+    uint8_t nvbits;
+    uint32_t clicinfo;
+    uint32_t clicinttrig[32];
+
+    /* Aperture configuration */
+    uint8_t *clicintip;
+    uint8_t *clicintie;
+    uint8_t *clicintattr;
+    uint8_t *clicintctl;
+
+    /* Complatible with v0.8 */
+    uint32_t mintthresh;
+    uint32_t sintthresh;
+    uint32_t uintthresh;
+
+    /* QEMU implementaion related fields */
+    uint32_t *exccode;
+    CLICActiveInterrupt *active_list;
+    size_t *active_count;
+    MemoryRegion mmio;
+    qemu_irq *cpu_irqs;
+} RISCVCLICState;
+
+DeviceState *riscv_clic_create(hwaddr addr, bool prv_s, bool prv_u,
+                               uint32_t num_harts, uint32_t num_sources,
+                               uint8_t clicintctlbits,
+                               const char *version);
+
+void riscv_clic_decode_exccode(uint32_t exccode, int *mode, int *il, int *irq);
+void riscv_clic_clean_pending(void *opaque, int mode, int hartid, int irq);
+bool riscv_clic_edge_triggered(void *opaque, int mode, int hartid, int irq);
+bool riscv_clic_shv_interrupt(void *opaque, int mode, int hartid, int irq);
+void riscv_clic_get_next_interrupt(void *opaque, int hartid);
+bool riscv_clic_is_clic_mode(CPURISCVState *env);
+#endif
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index a5eab26a69..9e389d7bbf 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -33,6 +33,7 @@ 
 #define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
 #define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
 #define CPU_RESOLVING_TYPE TYPE_RISCV_CPU
+#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0
 
 #define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
 #define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
@@ -247,6 +248,7 @@  struct CPURISCVState {
     /* Fields from here on are preserved across CPU reset. */
     QEMUTimer *timer; /* Internal timer */
     void *clic;       /* clic interrupt controller */
+    uint32_t exccode; /* clic irq encode */
 };
 
 OBJECT_DECLARE_TYPE(RISCVCPU, RISCVCPUClass,