diff mbox series

[v1,35/43] hw/intc: Add LoongArch extioi interrupt controller(EIOINTC)

Message ID 20220415094058.3584233-36-yangxiaojuan@loongson.cn
State New
Headers show
Series Add LoongArch softmmu support | expand

Commit Message

Xiaojuan Yang April 15, 2022, 9:40 a.m. UTC
This patch realize the EIOINTC interrupt controller.

Signed-off-by: Xiaojuan Yang <yangxiaojuan@loongson.cn>
Signed-off-by: Song Gao <gaosong@loongson.cn>
---
 hw/intc/Kconfig                    |   3 +
 hw/intc/loongarch_extioi.c         | 373 +++++++++++++++++++++++++++++
 hw/intc/meson.build                |   1 +
 hw/intc/trace-events               |  11 +
 hw/loongarch/Kconfig               |   1 +
 include/hw/intc/loongarch_extioi.h |  68 ++++++
 6 files changed, 457 insertions(+)
 create mode 100644 hw/intc/loongarch_extioi.c
 create mode 100644 include/hw/intc/loongarch_extioi.h

Comments

Richard Henderson April 18, 2022, 3:48 a.m. UTC | #1
On 4/15/22 02:40, Xiaojuan Yang wrote:
> +        memory_region_init(&s->mmio[cpu], OBJECT(s),
> +                           "loongarch_extioi", EXTIOI_SIZE);
> +
> +        memory_region_init_io(&s->mmio_nodetype[cpu], OBJECT(s),
> +                              &extioi_nodetype_ops, s,
> +                              EXTIOI_LINKNAME(.nodetype),
> +                              IPMAP_OFFSET - APIC_BASE);
> +        memory_region_add_subregion(&s->mmio[cpu], 0, &s->mmio_nodetype[cpu]);
> +
> +        memory_region_init_io(&s->mmio_ipmap_enable[cpu], OBJECT(s),
> +                              &extioi_ipmap_enable_ops, s,
> +                              EXTIOI_LINKNAME(.ipmap_enable),
> +                              BOUNCE_OFFSET - IPMAP_OFFSET);
> +        memory_region_add_subregion(&s->mmio[cpu], IPMAP_OFFSET - APIC_BASE,
> +                                    &s->mmio_ipmap_enable[cpu]);
> +
> +        memory_region_init_io(&s->mmio_bounce_coreisr[cpu], OBJECT(s),
> +                              &extioi_bounce_coreisr_ops, s,
> +                              EXTIOI_LINKNAME(.bounce_coreisr),
> +                              COREMAP_OFFSET - BOUNCE_OFFSET);
> +        memory_region_add_subregion(&s->mmio[cpu], BOUNCE_OFFSET - APIC_BASE,
> +                                    &s->mmio_bounce_coreisr[cpu]);
> +
> +        memory_region_init_io(&s->mmio_coremap[cpu], OBJECT(s),
> +                              &extioi_coremap_ops, s,
> +                              EXTIOI_LINKNAME(.coremap),
> +                              EXTIOI_COREMAP_END);
> +        memory_region_add_subregion(&s->mmio[cpu], COREMAP_OFFSET - APIC_BASE,
> +                                    &s->mmio_coremap[cpu]);

Why are these separate memory regions, instead of one region?  They're certainly described 
in a single table in section 11.2 of the 3A5000 manual...



r~
Mark Cave-Ayland April 18, 2022, 8:57 a.m. UTC | #2
On 18/04/2022 04:48, Richard Henderson wrote:

> On 4/15/22 02:40, Xiaojuan Yang wrote:
>> +        memory_region_init(&s->mmio[cpu], OBJECT(s),
>> +                           "loongarch_extioi", EXTIOI_SIZE);
>> +
>> +        memory_region_init_io(&s->mmio_nodetype[cpu], OBJECT(s),
>> +                              &extioi_nodetype_ops, s,
>> +                              EXTIOI_LINKNAME(.nodetype),
>> +                              IPMAP_OFFSET - APIC_BASE);
>> +        memory_region_add_subregion(&s->mmio[cpu], 0, &s->mmio_nodetype[cpu]);
>> +
>> +        memory_region_init_io(&s->mmio_ipmap_enable[cpu], OBJECT(s),
>> +                              &extioi_ipmap_enable_ops, s,
>> +                              EXTIOI_LINKNAME(.ipmap_enable),
>> +                              BOUNCE_OFFSET - IPMAP_OFFSET);
>> +        memory_region_add_subregion(&s->mmio[cpu], IPMAP_OFFSET - APIC_BASE,
>> +                                    &s->mmio_ipmap_enable[cpu]);
>> +
>> +        memory_region_init_io(&s->mmio_bounce_coreisr[cpu], OBJECT(s),
>> +                              &extioi_bounce_coreisr_ops, s,
>> +                              EXTIOI_LINKNAME(.bounce_coreisr),
>> +                              COREMAP_OFFSET - BOUNCE_OFFSET);
>> +        memory_region_add_subregion(&s->mmio[cpu], BOUNCE_OFFSET - APIC_BASE,
>> +                                    &s->mmio_bounce_coreisr[cpu]);
>> +
>> +        memory_region_init_io(&s->mmio_coremap[cpu], OBJECT(s),
>> +                              &extioi_coremap_ops, s,
>> +                              EXTIOI_LINKNAME(.coremap),
>> +                              EXTIOI_COREMAP_END);
>> +        memory_region_add_subregion(&s->mmio[cpu], COREMAP_OFFSET - APIC_BASE,
>> +                                    &s->mmio_coremap[cpu]);
> 
> Why are these separate memory regions, instead of one region?  They're certainly 
> described in a single table in section 11.2 of the 3A5000 manual...

The reason it was done like this is because there were different access sizes in the 
relevant _ops definitions. Certainly when I looked at the patches previously I wasn't 
able to easily see how these could be consolidated without digging deeper into the 
documentation.


ATB,

Mark.
Xiaojuan Yang April 19, 2022, 1:50 a.m. UTC | #3
On 2022/4/18 下午4:57, Mark Cave-Ayland wrote:
> On 18/04/2022 04:48, Richard Henderson wrote:
>
>> On 4/15/22 02:40, Xiaojuan Yang wrote:
>>> + memory_region_init(&s->mmio[cpu], OBJECT(s),
>>> +                           "loongarch_extioi", EXTIOI_SIZE);
>>> +
>>> +        memory_region_init_io(&s->mmio_nodetype[cpu], OBJECT(s),
>>> +                              &extioi_nodetype_ops, s,
>>> +                              EXTIOI_LINKNAME(.nodetype),
>>> +                              IPMAP_OFFSET - APIC_BASE);
>>> +        memory_region_add_subregion(&s->mmio[cpu], 0, 
>>> &s->mmio_nodetype[cpu]);
>>> +
>>> + memory_region_init_io(&s->mmio_ipmap_enable[cpu], OBJECT(s),
>>> +                              &extioi_ipmap_enable_ops, s,
>>> +                              EXTIOI_LINKNAME(.ipmap_enable),
>>> +                              BOUNCE_OFFSET - IPMAP_OFFSET);
>>> +        memory_region_add_subregion(&s->mmio[cpu], IPMAP_OFFSET - 
>>> APIC_BASE,
>>> + &s->mmio_ipmap_enable[cpu]);
>>> +
>>> + memory_region_init_io(&s->mmio_bounce_coreisr[cpu], OBJECT(s),
>>> +                              &extioi_bounce_coreisr_ops, s,
>>> + EXTIOI_LINKNAME(.bounce_coreisr),
>>> +                              COREMAP_OFFSET - BOUNCE_OFFSET);
>>> +        memory_region_add_subregion(&s->mmio[cpu], BOUNCE_OFFSET - 
>>> APIC_BASE,
>>> + &s->mmio_bounce_coreisr[cpu]);
>>> +
>>> +        memory_region_init_io(&s->mmio_coremap[cpu], OBJECT(s),
>>> +                              &extioi_coremap_ops, s,
>>> +                              EXTIOI_LINKNAME(.coremap),
>>> +                              EXTIOI_COREMAP_END);
>>> +        memory_region_add_subregion(&s->mmio[cpu], COREMAP_OFFSET - 
>>> APIC_BASE,
>>> + &s->mmio_coremap[cpu]);
>>
>> Why are these separate memory regions, instead of one region? They're 
>> certainly described in a single table in section 11.2 of the 3A5000 
>> manual...
>
> The reason it was done like this is because there were different 
> access sizes in the relevant _ops definitions. Certainly when I looked 
> at the patches previously I wasn't able to easily see how these could 
> be consolidated without digging deeper into the documentation.
>
Would it be better to keep consistent with the content of the 3A5000 
manual document? And only one memory region is used to represent the 
extioi iocsr region

Thanks.
Xiaojuan
>
> ATB,
>
> Mark.
Mark Cave-Ayland April 19, 2022, 11:10 a.m. UTC | #4
On 19/04/2022 02:50, yangxiaojuan wrote:

> On 2022/4/18 下午4:57, Mark Cave-Ayland wrote:
>> On 18/04/2022 04:48, Richard Henderson wrote:
>>
>>> On 4/15/22 02:40, Xiaojuan Yang wrote:
>>>> + memory_region_init(&s->mmio[cpu], OBJECT(s),
>>>> +                           "loongarch_extioi", EXTIOI_SIZE);
>>>> +
>>>> +        memory_region_init_io(&s->mmio_nodetype[cpu], OBJECT(s),
>>>> +                              &extioi_nodetype_ops, s,
>>>> +                              EXTIOI_LINKNAME(.nodetype),
>>>> +                              IPMAP_OFFSET - APIC_BASE);
>>>> +        memory_region_add_subregion(&s->mmio[cpu], 0, &s->mmio_nodetype[cpu]);
>>>> +
>>>> + memory_region_init_io(&s->mmio_ipmap_enable[cpu], OBJECT(s),
>>>> +                              &extioi_ipmap_enable_ops, s,
>>>> +                              EXTIOI_LINKNAME(.ipmap_enable),
>>>> +                              BOUNCE_OFFSET - IPMAP_OFFSET);
>>>> +        memory_region_add_subregion(&s->mmio[cpu], IPMAP_OFFSET - APIC_BASE,
>>>> + &s->mmio_ipmap_enable[cpu]);
>>>> +
>>>> + memory_region_init_io(&s->mmio_bounce_coreisr[cpu], OBJECT(s),
>>>> +                              &extioi_bounce_coreisr_ops, s,
>>>> + EXTIOI_LINKNAME(.bounce_coreisr),
>>>> +                              COREMAP_OFFSET - BOUNCE_OFFSET);
>>>> +        memory_region_add_subregion(&s->mmio[cpu], BOUNCE_OFFSET - APIC_BASE,
>>>> + &s->mmio_bounce_coreisr[cpu]);
>>>> +
>>>> +        memory_region_init_io(&s->mmio_coremap[cpu], OBJECT(s),
>>>> +                              &extioi_coremap_ops, s,
>>>> +                              EXTIOI_LINKNAME(.coremap),
>>>> +                              EXTIOI_COREMAP_END);
>>>> +        memory_region_add_subregion(&s->mmio[cpu], COREMAP_OFFSET - APIC_BASE,
>>>> + &s->mmio_coremap[cpu]);
>>>
>>> Why are these separate memory regions, instead of one region? They're certainly 
>>> described in a single table in section 11.2 of the 3A5000 manual...
>>
>> The reason it was done like this is because there were different access sizes in 
>> the relevant _ops definitions. Certainly when I looked at the patches previously I 
>> wasn't able to easily see how these could be consolidated without digging deeper 
>> into the documentation.
>>
> Would it be better to keep consistent with the content of the 3A5000 manual 
> document? And only one memory region is used to represent the extioi iocsr region

The reason that different memory regions are required is because you've specified 
different access size requirements for each region:

static const MemoryRegionOps extioi_nodetype_ops = {
     .read = extioi_nodetype_readw,
     .write = extioi_nodetype_writew,
     .impl.min_access_size = 4,
     .impl.max_access_size = 4,
     .valid.min_access_size = 4,
     .valid.max_access_size = 8,
     .endianness = DEVICE_LITTLE_ENDIAN,
};

static const MemoryRegionOps extioi_ipmap_enable_ops = {
     .read = extioi_ipmap_enable_read,
     .write = extioi_ipmap_enable_write,
     .impl.min_access_size = 1,
     .impl.max_access_size = 1,
     .valid.min_access_size = 1,
     .valid.max_access_size = 8,
     .endianness = DEVICE_LITTLE_ENDIAN,
};

static const MemoryRegionOps extioi_bounce_coreisr_ops = {
     .read = extioi_bounce_coreisr_readw,
     .write = extioi_bounce_coreisr_writew,
     .impl.min_access_size = 4,
     .impl.max_access_size = 4,
     .valid.min_access_size = 4,
     .valid.max_access_size = 8,
     .endianness = DEVICE_LITTLE_ENDIAN,
};

static const MemoryRegionOps extioi_coremap_ops = {
     .read = extioi_coremap_read,
     .write = extioi_coremap_write,
     .impl.min_access_size = 1,
     .impl.max_access_size = 1,
     .valid.min_access_size = 1,
     .valid.max_access_size = 8,
     .endianness = DEVICE_LITTLE_ENDIAN,
};

Certainly this is unusual (and for a given device I'd expect that its registers would 
all have the same access requirements), but it's not something that can be tested 
without access to real hardware.

As Richard suggests though, if it is found that all of the device registers have the 
same access requirements then they could potentially be collapsed into a single 
memory region.


ATB,

Mark.
diff mbox series

Patch

diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index 71c04c328e..28bd1f185d 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -96,3 +96,6 @@  config LOONGARCH_PCH_MSI
     select MSI_NONBROKEN
     bool
     select UNIMP
+
+config LOONGARCH_EXTIOI
+    bool
diff --git a/hw/intc/loongarch_extioi.c b/hw/intc/loongarch_extioi.c
new file mode 100644
index 0000000000..67be990672
--- /dev/null
+++ b/hw/intc/loongarch_extioi.c
@@ -0,0 +1,373 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Loongson 3A5000 ext interrupt controller emulation
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "hw/loongarch/loongarch.h"
+#include "hw/qdev-properties.h"
+#include "exec/address-spaces.h"
+#include "hw/intc/loongarch_extioi.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static void extioi_update_irq(LoongArchExtIOI *s, int irq_num, int level)
+{
+    uint8_t ipnum, cpu;
+
+    /*
+     * Routing in group of 32 interrupts.
+     * The default value of csr[0x420][49]
+     * is 0 and nobody will change it,
+     * so 'ipmap' use bitmap function.
+     */
+    ipnum = s->ipmap[irq_num / 32] & 0xf;
+    ipnum = find_first_bit((unsigned long *)&ipnum, 4);
+    ipnum = (ipnum == 4) ? 0 : ipnum;
+
+    cpu = s->coremap[irq_num] & 0xf;
+    cpu = find_first_bit((unsigned long *)&cpu, 4);
+    cpu = (cpu == 4) ? 0 : cpu;
+
+    /* qemu_set_irq needs integer variable, so level is 'int' */
+    if (level) {
+        if (test_bit(irq_num, (unsigned long *)s->enable) == false) {
+            return;
+        }
+        bitmap_set((unsigned long *)s->coreisr[cpu], irq_num, 1);
+        qemu_set_irq(s->parent_irq[cpu][ipnum], level);
+    } else {
+        bitmap_clear((unsigned long *)s->coreisr[cpu], irq_num, 1);
+        qemu_set_irq(s->parent_irq[cpu][ipnum], level);
+    }
+}
+
+static void extioi_setirq(void *opaque, int irq, int level)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    trace_loongarch_extioi_setirq(irq, level);
+    extioi_update_irq(s, irq, level);
+}
+
+static uint64_t extioi_nodetype_readw(void *opaque, hwaddr addr, unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    unsigned long offset = addr & 0xffff;
+    uint32_t index, ret = 0;
+
+    switch (offset) {
+    case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
+        index = (offset - EXTIOI_NODETYPE_START) >> 2;
+        ret = s->nodetype[index];
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_extioi_nodetype_readw((uint32_t)addr, ret);
+    return ret;
+}
+
+static void extioi_nodetype_writew(void *opaque, hwaddr addr,
+                                   uint64_t val, unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    int index;
+    uint32_t offset;
+    trace_loongarch_extioi_nodetype_writew(size, (uint32_t)addr, val);
+
+    offset = addr & 0xffff;
+
+    switch (offset) {
+    case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
+        index = (offset - EXTIOI_NODETYPE_START) >> 2;
+        s->nodetype[index] = val;
+        break;
+    default:
+        break;
+    }
+}
+
+static uint64_t extioi_ipmap_enable_read(void *opaque, hwaddr addr,
+                                         unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    uint8_t ret = 0;
+
+    switch (addr) {
+    case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
+        ret = s->ipmap[addr];
+        break;
+    case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
+        addr -= EXTIOI_ENABLE_START;
+        ret = s->enable[addr];
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_extioi_ipmap_enable_read((uint8_t)addr, ret);
+    return ret;
+}
+
+static void extioi_ipmap_enable_write(void *opaque, hwaddr addr,
+                                      uint64_t value, unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    uint8_t old_data, val = value & 0xff;
+    int i, level;
+    trace_loongarch_extioi_ipmap_enable_write(size, (uint8_t)addr, val);
+
+    switch (addr) {
+    case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
+        s->ipmap[addr] = val;
+        break;
+    case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
+        addr -= EXTIOI_ENABLE_START;
+        old_data = s->enable[addr];
+        if (old_data != val) {
+            s->enable[addr] = val;
+            old_data = old_data ^ val;
+
+            while ((i = find_first_bit((unsigned long *)&old_data, 8)) != 8) {
+                level = test_bit(i, (unsigned long *)&val);
+                if (!level) {
+                    extioi_update_irq(s, i + addr * 8, level);
+                }
+                clear_bit(i, (unsigned long *)&old_data);
+            }
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static uint64_t extioi_bounce_coreisr_readw(void *opaque, hwaddr addr,
+                                            unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    unsigned long offset = addr & 0xffff;
+    uint32_t index, ret = 0;
+    int cpu;
+
+    switch (offset) {
+    case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
+        index = (offset - EXTIOI_BOUNCE_START) >> 2;
+        ret = s->bounce[index];
+        break;
+    case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
+        index = ((offset - EXTIOI_COREISR_START) & 0x1f) >> 2;
+        cpu = ((offset - EXTIOI_COREISR_START) >> 8) & 0x3;
+        ret = s->coreisr[cpu][index];
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_extioi_bounce_coreisr_readw((uint32_t)addr, ret);
+    return ret;
+}
+
+static void extioi_bounce_coreisr_writew(void *opaque, hwaddr addr,
+                                         uint64_t val, unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    int cpu, index;
+    uint32_t offset, old_data, i, j, bits;
+
+    offset = addr & 0xffff;
+    trace_loongarch_extioi_bounce_coreisr_writew(size, (uint32_t)addr, val);
+    switch (offset) {
+    case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
+        index = (offset - EXTIOI_BOUNCE_START) >> 2;
+        s->bounce[index] = val;
+        break;
+    case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
+        index = ((offset - EXTIOI_COREISR_START) & 0x1f) >> 2;
+        cpu = ((offset - EXTIOI_COREISR_START) >> 8) & 0x3;
+        old_data = s->coreisr[cpu][index];
+        s->coreisr[cpu][index] = old_data & ~val;
+        if (old_data != s->coreisr[cpu][index]) {
+            bits = size * 8;
+            while ((i = find_first_bit((unsigned long *)&val, bits)) != bits) {
+                j = test_bit(i, (unsigned long *)&old_data);
+                if (j) {
+                    extioi_update_irq(s, i + index * 32, 0);
+                }
+                clear_bit(i, (unsigned long *)&val);
+            }
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static uint64_t extioi_coremap_read(void *opaque, hwaddr addr, unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    uint8_t ret = 0;
+
+    switch (addr) {
+    case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
+        ret = s->coremap[addr];
+        break;
+    default:
+        break;
+    }
+
+    trace_loongarch_extioi_coremap_read((uint8_t)addr, ret);
+    return ret;
+}
+
+static void extioi_coremap_write(void *opaque, hwaddr addr,
+                                 uint64_t value, unsigned size)
+{
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+    uint8_t val = value & 0xff;
+
+    trace_loongarch_extioi_coremap_write(size, (uint8_t)addr, val);
+    switch (addr) {
+    case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
+        s->coremap[addr] = val;
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps extioi_nodetype_ops = {
+    .read = extioi_nodetype_readw,
+    .write = extioi_nodetype_writew,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 8,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps extioi_ipmap_enable_ops = {
+    .read = extioi_ipmap_enable_read,
+    .write = extioi_ipmap_enable_write,
+    .impl.min_access_size = 1,
+    .impl.max_access_size = 1,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 8,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps extioi_bounce_coreisr_ops = {
+    .read = extioi_bounce_coreisr_readw,
+    .write = extioi_bounce_coreisr_writew,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 8,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps extioi_coremap_ops = {
+    .read = extioi_coremap_read,
+    .write = extioi_coremap_write,
+    .impl.min_access_size = 1,
+    .impl.max_access_size = 1,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 8,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_loongarch_extioi = {
+    .name = TYPE_LOONGARCH_EXTIOI,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(bounce, LoongArchExtIOI, EXTIOI_IRQS_GROUP_COUNT),
+        VMSTATE_UINT32_2DARRAY(coreisr, LoongArchExtIOI, MAX_CORES,
+                               EXTIOI_IRQS_GROUP_COUNT),
+        VMSTATE_UINT32_ARRAY(nodetype, LoongArchExtIOI,
+                             EXTIOI_IRQS_NODETYPE_COUNT / 2),
+        VMSTATE_UINT8_ARRAY(enable, LoongArchExtIOI, EXTIOI_IRQS / 8),
+        VMSTATE_UINT8_ARRAY(ipmap, LoongArchExtIOI, 8),
+        VMSTATE_UINT8_ARRAY(coremap, LoongArchExtIOI, EXTIOI_IRQS),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void loongarch_extioi_instance_init(Object *obj)
+{
+    SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+    LoongArchExtIOI *s = LOONGARCH_EXTIOI(obj);
+    int i, cpu, pin;
+
+    for (i = 0; i < EXTIOI_IRQS; i++) {
+        sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]);
+    }
+
+    qdev_init_gpio_in(DEVICE(obj), extioi_setirq, EXTIOI_IRQS);
+
+    for (cpu = 0; cpu < MAX_CORES; cpu++) {
+        sysbus_init_mmio(dev, &s->mmio[cpu]);
+        for (pin = 0; pin < LS3A_INTC_IP; pin++) {
+            qdev_init_gpio_out(DEVICE(obj), &s->parent_irq[cpu][pin], 1);
+        }
+
+        memory_region_init(&s->mmio[cpu], OBJECT(s),
+                           "loongarch_extioi", EXTIOI_SIZE);
+
+        memory_region_init_io(&s->mmio_nodetype[cpu], OBJECT(s),
+                              &extioi_nodetype_ops, s,
+                              EXTIOI_LINKNAME(.nodetype),
+                              IPMAP_OFFSET - APIC_BASE);
+        memory_region_add_subregion(&s->mmio[cpu], 0, &s->mmio_nodetype[cpu]);
+
+        memory_region_init_io(&s->mmio_ipmap_enable[cpu], OBJECT(s),
+                              &extioi_ipmap_enable_ops, s,
+                              EXTIOI_LINKNAME(.ipmap_enable),
+                              BOUNCE_OFFSET - IPMAP_OFFSET);
+        memory_region_add_subregion(&s->mmio[cpu], IPMAP_OFFSET - APIC_BASE,
+                                    &s->mmio_ipmap_enable[cpu]);
+
+        memory_region_init_io(&s->mmio_bounce_coreisr[cpu], OBJECT(s),
+                              &extioi_bounce_coreisr_ops, s,
+                              EXTIOI_LINKNAME(.bounce_coreisr),
+                              COREMAP_OFFSET - BOUNCE_OFFSET);
+        memory_region_add_subregion(&s->mmio[cpu], BOUNCE_OFFSET - APIC_BASE,
+                                    &s->mmio_bounce_coreisr[cpu]);
+
+        memory_region_init_io(&s->mmio_coremap[cpu], OBJECT(s),
+                              &extioi_coremap_ops, s,
+                              EXTIOI_LINKNAME(.coremap),
+                              EXTIOI_COREMAP_END);
+        memory_region_add_subregion(&s->mmio[cpu], COREMAP_OFFSET - APIC_BASE,
+                                    &s->mmio_coremap[cpu]);
+    }
+}
+
+static void loongarch_extioi_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &vmstate_loongarch_extioi;
+}
+
+static const TypeInfo loongarch_extioi_info = {
+    .name          = TYPE_LOONGARCH_EXTIOI,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_init = loongarch_extioi_instance_init,
+    .instance_size = sizeof(struct LoongArchExtIOI),
+    .class_init    = loongarch_extioi_class_init,
+};
+
+static void loongarch_extioi_register_types(void)
+{
+    type_register_static(&loongarch_extioi_info);
+}
+
+type_init(loongarch_extioi_register_types)
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index 77a30cec33..405e18f4bb 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -65,3 +65,4 @@  specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
 specific_ss.add(when: 'CONFIG_LOONGARCH_IPI', if_true: files('loongarch_ipi.c'))
 specific_ss.add(when: 'CONFIG_LOONGARCH_PCH_PIC', if_true: files('loongarch_pch_pic.c'))
 specific_ss.add(when: 'CONFIG_LOONGARCH_PCH_MSI', if_true: files('loongarch_pch_msi.c'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_EXTIOI', if_true: files('loongarch_extioi.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index aeee1e03de..69dfdf8eca 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -291,3 +291,14 @@  loongarch_pch_pic_writeb(unsigned size, uint32_t addr, unsigned long val) "size:
 
 # loongarch_pch_msi.c
 loongarch_msi_set_irq(int irq_num) "set msi irq %d"
+
+# loongarch_extioi.c
+loongarch_extioi_setirq(int irq, int level) "set extirq irq %d level %d"
+loongarch_extioi_nodetype_readw(uint32_t addr, uint32_t val) "addr: 0x%"PRIx32 "val: 0x%" PRIx32
+loongarch_extioi_nodetype_writew(unsigned size, uint32_t addr, uint32_t val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx32
+loongarch_extioi_ipmap_enable_read(uint8_t addr, uint8_t val) "addr: 0x%"PRIu8 "val: 0x%" PRIu8
+loongarch_extioi_ipmap_enable_write(unsigned size, uint8_t addr, uint8_t val) "size: %u addr: 0x%"PRIu8 "val: 0x%" PRIu8
+loongarch_extioi_bounce_coreisr_readw(uint32_t addr, uint32_t val) "addr: 0x%"PRIx32 "val: 0x%" PRIx32
+loongarch_extioi_bounce_coreisr_writew(unsigned size, uint32_t addr, uint32_t val) "size: %u addr: 0x%"PRIx32 "val: 0x%" PRIx32
+loongarch_extioi_coremap_read(uint8_t addr, uint8_t val) "addr: 0x%"PRIu8 "val: 0x%" PRIu8
+loongarch_extioi_coremap_write(unsigned size, uint8_t addr, uint8_t val) "size: %u addr: 0x%"PRIu8 "val: 0x%" PRIu8
diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig
index d814fc6103..f779087416 100644
--- a/hw/loongarch/Kconfig
+++ b/hw/loongarch/Kconfig
@@ -5,3 +5,4 @@  config LOONGARCH_VIRT
     select LOONGARCH_IPI
     select LOONGARCH_PCH_PIC
     select LOONGARCH_PCH_MSI
+    select LOONGARCH_EXTIOI
diff --git a/include/hw/intc/loongarch_extioi.h b/include/hw/intc/loongarch_extioi.h
new file mode 100644
index 0000000000..46c5116bf5
--- /dev/null
+++ b/include/hw/intc/loongarch_extioi.h
@@ -0,0 +1,68 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * LoongArch 3A5000 ext interrupt controller definitions
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "hw/sysbus.h"
+#include "hw/loongarch/loongarch.h"
+
+#ifndef LOONGARCH_EXTIOI_H
+#define LOONGARCH_EXTIOI_H
+
+#define LS3A_INTC_IP                 8
+#define MAX_CORES                    LOONGARCH_MAX_VCPUS
+#define EXTIOI_IRQS                  (256)
+/* 32 irqs belong to a group */
+#define EXTIOI_IRQS_GROUP_COUNT      (256 / 32)
+/* map to ipnum per 32 irqs */
+#define EXTIOI_IRQS_NODETYPE_COUNT   16
+
+#define APIC_BASE                    0x1400
+#define ENABLE_OFFSET                0x1600
+#define IPMAP_OFFSET                 0x14c0
+#define COREMAP_OFFSET               0x1c00
+#define NODETYPE_OFFSET              0x14a0
+#define BOUNCE_OFFSET                0x1680
+#define COREISR_OFFSET               0x1800
+
+#define EXTIOI_NODETYPE_START        (0x14a0 - APIC_BASE)
+#define EXTIOI_NODETYPE_END          (0x14c0 - APIC_BASE)
+#define EXTIOI_BOUNCE_START          0
+#define EXTIOI_BOUNCE_END            (0x16a0 - BOUNCE_OFFSET)
+#define EXTIOI_COREISR_START         (0x1800 - BOUNCE_OFFSET)
+#define EXTIOI_COREISR_END           (0x1B20 - BOUNCE_OFFSET)
+
+#define EXTIOI_IPMAP_START           0
+#define EXTIOI_IPMAP_END             (0x14c8 - IPMAP_OFFSET)
+#define EXTIOI_ENABLE_START          (0x1600 - IPMAP_OFFSET)
+#define EXTIOI_ENABLE_END            (0x1618 - IPMAP_OFFSET)
+
+#define EXTIOI_COREMAP_START         0
+#define EXTIOI_COREMAP_END           (0x1d00 - COREMAP_OFFSET)
+#define EXTIOI_SIZE                  0x900
+
+#define TYPE_LOONGARCH_EXTIOI "loongarch_extioi"
+#define EXTIOI_LINKNAME(name) TYPE_LOONGARCH_EXTIOI#name
+OBJECT_DECLARE_SIMPLE_TYPE(LoongArchExtIOI, LOONGARCH_EXTIOI)
+struct LoongArchExtIOI {
+    SysBusDevice parent_obj;
+    /* hardware state */
+    uint32_t nodetype[EXTIOI_IRQS_NODETYPE_COUNT / 2];
+    uint32_t bounce[EXTIOI_IRQS_GROUP_COUNT];
+    uint32_t coreisr[MAX_CORES][EXTIOI_IRQS_GROUP_COUNT];
+
+    uint8_t enable[EXTIOI_IRQS / 8];
+    uint8_t ipmap[8];
+    uint8_t coremap[EXTIOI_IRQS];
+    qemu_irq parent_irq[MAX_CORES][LS3A_INTC_IP];
+    qemu_irq irq[EXTIOI_IRQS];
+    MemoryRegion mmio[MAX_CORES];
+    MemoryRegion mmio_nodetype[MAX_CORES];
+    MemoryRegion mmio_ipmap_enable[MAX_CORES];
+    MemoryRegion mmio_bounce_coreisr[MAX_CORES];
+    MemoryRegion mmio_coremap[MAX_CORES];
+};
+
+#endif /* LOONGARCH_EXTIOI_H */