diff mbox series

[RFC,V2,3/5] hw/loongarch: Add KVM extioi device support

Message ID f359346bb865fcc4d52552c8c0fc27123c858aad.1725969898.git.lixianglai@loongson.cn (mailing list archive)
State New, archived
Headers show
Series Added Interrupt controller emulation for loongarch kvm | expand

Commit Message

lixianglai Sept. 10, 2024, 12:18 p.m. UTC
Added extioi interrupt controller for kvm emulation.
The main process is to send the command word for
creating an extioi device to the kernel.
When the VM is saved, the ioctl obtains the related
data of the extioi interrupt controller in the kernel
and saves it. When the VM is recovered, the saved data
is sent to the kernel.

Signed-off-by: Tianrui Zhao <zhaotianrui@loongson.cn>
Signed-off-by: Xianglai Li <lixianglai@loongson.cn>
---
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Song Gao <gaosong@loongson.cn>
Cc: Jiaxun Yang <jiaxun.yang@flygoat.com>
Cc: Huacai Chen <chenhuacai@kernel.org>
Cc: "Michael S. Tsirkin" <mst@redhat.com>
Cc: Cornelia Huck <cohuck@redhat.com>
Cc: kvm@vger.kernel.org
Cc: Bibo Mao <maobibo@loongson.cn>
Cc: Xianglai Li <lixianglai@loongson.cn>

 hw/intc/Kconfig                |   3 +
 hw/intc/loongarch_extioi_kvm.c | 250 +++++++++++++++++++++++++++++++++
 hw/intc/meson.build            |   1 +
 hw/loongarch/Kconfig           |   1 +
 hw/loongarch/virt.c            |  51 ++++---
 5 files changed, 285 insertions(+), 21 deletions(-)
 create mode 100644 hw/intc/loongarch_extioi_kvm.c

Comments

bibo mao Sept. 12, 2024, 3:25 a.m. UTC | #1
On 2024/9/10 下午8:18, Xianglai Li wrote:
> Added extioi interrupt controller for kvm emulation.
> The main process is to send the command word for
> creating an extioi device to the kernel.
> When the VM is saved, the ioctl obtains the related
> data of the extioi interrupt controller in the kernel
> and saves it. When the VM is recovered, the saved data
> is sent to the kernel.
> 
> Signed-off-by: Tianrui Zhao <zhaotianrui@loongson.cn>
> Signed-off-by: Xianglai Li <lixianglai@loongson.cn>
> ---
> Cc: Paolo Bonzini <pbonzini@redhat.com>
> Cc: Song Gao <gaosong@loongson.cn>
> Cc: Jiaxun Yang <jiaxun.yang@flygoat.com>
> Cc: Huacai Chen <chenhuacai@kernel.org>
> Cc: "Michael S. Tsirkin" <mst@redhat.com>
> Cc: Cornelia Huck <cohuck@redhat.com>
> Cc: kvm@vger.kernel.org
> Cc: Bibo Mao <maobibo@loongson.cn>
> Cc: Xianglai Li <lixianglai@loongson.cn>
> 
>   hw/intc/Kconfig                |   3 +
>   hw/intc/loongarch_extioi_kvm.c | 250 +++++++++++++++++++++++++++++++++
>   hw/intc/meson.build            |   1 +
>   hw/loongarch/Kconfig           |   1 +
>   hw/loongarch/virt.c            |  51 ++++---
>   5 files changed, 285 insertions(+), 21 deletions(-)
>   create mode 100644 hw/intc/loongarch_extioi_kvm.c
> 
> diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
> index 5201505f23..df9352d41d 100644
> --- a/hw/intc/Kconfig
> +++ b/hw/intc/Kconfig
> @@ -112,3 +112,6 @@ config LOONGARCH_PCH_MSI
>   
>   config LOONGARCH_EXTIOI
>       bool
> +
> +config LOONGARCH_EXTIOI_KVM
> +    bool
> diff --git a/hw/intc/loongarch_extioi_kvm.c b/hw/intc/loongarch_extioi_kvm.c
> new file mode 100644
> index 0000000000..139a00ac2a
> --- /dev/null
> +++ b/hw/intc/loongarch_extioi_kvm.c
> @@ -0,0 +1,250 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * LoongArch kvm extioi interrupt support
> + *
> + * Copyright (C) 2024 Loongson Technology Corporation Limited
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/qdev-properties.h"
> +#include "qemu/typedefs.h"
> +#include "hw/intc/loongarch_extioi.h"
> +#include "hw/sysbus.h"
> +#include "linux/kvm.h"
> +#include "migration/vmstate.h"
> +#include "qapi/error.h"
> +#include "sysemu/kvm.h"
> +
> +static void kvm_extioi_access_regs(int fd, uint64_t addr,
> +                                       void *val, bool is_write)
> +{
> +        kvm_device_access(fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_REGS,
> +                          addr, val, is_write, &error_abort);
> +}
> +
> +static void kvm_extioi_access_sw_status(int fd, uint64_t addr,
> +                                       void *val, bool is_write)
> +{
> +        kvm_device_access(fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_SW_STATUS,
> +                          addr, val, is_write, &error_abort);
> +}
> +
> +static void kvm_extioi_save_load_sw_status(void *opaque, bool is_write)
> +{
> +    KVMLoongArchExtIOI *s = (KVMLoongArchExtIOI *)opaque;
> +    KVMLoongArchExtIOIClass *class = KVM_LOONGARCH_EXTIOI_GET_CLASS(s);
> +    int fd = class->dev_fd;
> +    int addr;
> +
> +    addr = KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_NUM_CPU;
> +    kvm_extioi_access_sw_status(fd, addr, (void *)&s->num_cpu, is_write);
> +
> +    addr = KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_FEATURE;
> +    kvm_extioi_access_sw_status(fd, addr, (void *)&s->features, is_write);
> +
> +    addr = KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_STATE;
> +    kvm_extioi_access_sw_status(fd, addr, (void *)&s->status, is_write);
> +}
> +
> +static void kvm_extioi_save_load_regs(void *opaque, bool is_write)
> +{
> +    KVMLoongArchExtIOI *s = (KVMLoongArchExtIOI *)opaque;
> +    KVMLoongArchExtIOIClass *class = KVM_LOONGARCH_EXTIOI_GET_CLASS(s);
> +    int fd = class->dev_fd;
> +    int addr, offset, cpuid;
> +
> +    for (addr = EXTIOI_NODETYPE_START; addr < EXTIOI_NODETYPE_END; addr += 4) {
> +        offset = (addr - EXTIOI_NODETYPE_START) / 4;
> +        kvm_extioi_access_regs(fd, addr,
> +                               (void *)&s->nodetype[offset], is_write);
> +    }
> +
> +    for (addr = EXTIOI_IPMAP_START; addr < EXTIOI_IPMAP_END; addr += 4) {
> +        offset = (addr - EXTIOI_IPMAP_START) / 4;
> +        kvm_extioi_access_regs(fd, addr, (void *)&s->ipmap[offset], is_write);
> +    }
> +
> +    for (addr = EXTIOI_ENABLE_START; addr < EXTIOI_ENABLE_END; addr += 4) {
> +        offset = (addr - EXTIOI_ENABLE_START) / 4;
> +        kvm_extioi_access_regs(fd, addr,
> +                               (void *)&s->enable[offset], is_write);
> +    }
> +
> +    for (addr = EXTIOI_BOUNCE_START; addr < EXTIOI_BOUNCE_END; addr += 4) {
> +        offset = (addr - EXTIOI_BOUNCE_START) / 4;
> +        kvm_extioi_access_regs(fd, addr,
> +                               (void *)&s->bounce[offset], is_write);
> +    }
> +
> +    for (addr = EXTIOI_ISR_START; addr < EXTIOI_ISR_END; addr += 4) {
> +        offset = (addr - EXTIOI_ISR_START) / 4;
> +        kvm_extioi_access_regs(fd, addr,
> +                               (void *)&s->isr[offset], is_write);
> +    }
> +
> +    for (addr = EXTIOI_COREMAP_START; addr < EXTIOI_COREMAP_END; addr += 4) {
> +        offset = (addr - EXTIOI_COREMAP_START) / 4;
> +        kvm_extioi_access_regs(fd, addr,
> +                               (void *)&s->coremap[offset], is_write);
> +    }
> +
> +    for (cpuid = 0; cpuid < s->num_cpu; cpuid++) {
> +        for (addr = EXTIOI_COREISR_START;
> +             addr < EXTIOI_COREISR_END; addr += 4) {
> +            offset = (addr - EXTIOI_COREISR_START) / 4;
> +            addr = (cpuid << 16) | addr;
> +            kvm_extioi_access_regs(fd, addr,
> +                              (void *)&s->coreisr[cpuid][offset], is_write);
> +        }
> +    }
> +}
> +
> +static int kvm_loongarch_extioi_pre_save(void *opaque)
> +{
> +    kvm_extioi_save_load_regs(opaque, false);
> +    kvm_extioi_save_load_sw_status(opaque, false);
> +    return 0;
> +}
> +
> +static int kvm_loongarch_extioi_post_load(void *opaque, int version_id)
> +{
> +    KVMLoongArchExtIOI *s = (KVMLoongArchExtIOI *)opaque;
> +    KVMLoongArchExtIOIClass *class = KVM_LOONGARCH_EXTIOI_GET_CLASS(s);
> +    int fd = class->dev_fd;
> +
> +    kvm_extioi_save_load_regs(opaque, true);
> +    kvm_extioi_save_load_sw_status(opaque, true);
> +
> +    kvm_device_access(fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL,
> +                      KVM_DEV_LOONGARCH_EXTIOI_CTRL_LOAD_FINISHED,
> +                      NULL, true, &error_abort);
> +    return 0;
> +}
> +
> +static void kvm_loongarch_extioi_realize(DeviceState *dev, Error **errp)
> +{
> +    KVMLoongArchExtIOIClass *extioi_class = KVM_LOONGARCH_EXTIOI_GET_CLASS(dev);
> +    KVMLoongArchExtIOI *s = KVM_LOONGARCH_EXTIOI(dev);
> +    struct kvm_create_device cd = {0};
> +    Error *err = NULL;
> +    int ret, i;
> +
> +    extioi_class->parent_realize(dev, &err);
> +    if (err) {
> +        error_propagate(errp, err);
> +        return;
> +    }
> +
> +    if (s->num_cpu == 0) {
> +        error_setg(errp, "num-cpu must be at least 1");
> +        return;
> +    }
> +
> +
> +    if (extioi_class->is_created) {
> +        error_setg(errp, "extioi had be created");
> +        return;
> +    }
> +
> +    if (s->features & BIT(EXTIOI_HAS_VIRT_EXTENSION)) {
> +        s->features |= EXTIOI_VIRT_HAS_FEATURES;
> +    }
> +
> +    cd.type = KVM_DEV_TYPE_LOONGARCH_EXTIOI;
> +    ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd);
> +    if (ret < 0) {
> +        error_setg_errno(errp, errno,
> +                         "Creating the KVM extioi device failed");
> +        return;
> +    }
> +    extioi_class->is_created = true;
> +    extioi_class->dev_fd = cd.fd;
> +
> +    ret = kvm_device_access(cd.fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL,
> +                            KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_NUM_CPU,
> +                            &s->num_cpu, true, NULL);
> +    if (ret < 0) {
> +        error_setg_errno(errp, errno,
> +                         "KVM EXTIOI: failed to set the num-cpu of EXTIOI");
> +        exit(1);
> +    }
> +
> +    ret = kvm_device_access(cd.fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL,
> +                            KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_FEATURE,
> +                            &s->features, true, NULL);
> +    if (ret < 0) {
> +        error_setg_errno(errp, errno,
> +                         "KVM EXTIOI: failed to set the feature of EXTIOI");
> +        exit(1);
> +    }
> +
> +    fprintf(stdout, "Create LoongArch extioi irqchip in KVM done!\n");
> +
> +    kvm_async_interrupts_allowed = true;
> +    kvm_msi_via_irqfd_allowed = kvm_irqfds_enabled();
> +    if (kvm_has_gsi_routing()) {
> +        for (i = 0; i < 64; ++i) {
> +            kvm_irqchip_add_irq_route(kvm_state, i, 0, i);
> +        }
> +        kvm_gsi_routing_allowed = true;
> +    }
Does memory region need be created to extioi with irqchip_in_kernel mode?

> +}
> +
> +static const VMStateDescription vmstate_kvm_extioi_core = {
> +    .name = "kvm-extioi-single",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .pre_save = kvm_loongarch_extioi_pre_save,
> +    .post_load = kvm_loongarch_extioi_post_load,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(nodetype, KVMLoongArchExtIOI,
> +                             EXTIOI_IRQS_NODETYPE_COUNT / 2),
> +        VMSTATE_UINT32_ARRAY(bounce, KVMLoongArchExtIOI,
> +                             EXTIOI_IRQS_GROUP_COUNT),
> +        VMSTATE_UINT32_ARRAY(isr, KVMLoongArchExtIOI, EXTIOI_IRQS / 32),
> +        VMSTATE_UINT32_2DARRAY(coreisr, KVMLoongArchExtIOI, EXTIOI_CPUS,
> +                               EXTIOI_IRQS_GROUP_COUNT),
> +        VMSTATE_UINT32_ARRAY(enable, KVMLoongArchExtIOI, EXTIOI_IRQS / 32),
> +        VMSTATE_UINT32_ARRAY(ipmap, KVMLoongArchExtIOI,
> +                             EXTIOI_IRQS_IPMAP_SIZE / 4),
> +        VMSTATE_UINT32_ARRAY(coremap, KVMLoongArchExtIOI, EXTIOI_IRQS / 4),
> +        VMSTATE_UINT8_ARRAY(sw_coremap, KVMLoongArchExtIOI, EXTIOI_IRQS),
> +        VMSTATE_UINT32(features, KVMLoongArchExtIOI),
> +        VMSTATE_UINT32(status, KVMLoongArchExtIOI),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
It is duplicated with structure vmstate_loongarch_extioi in file
hw/intc/loongarch_extioi.c

> +
> +static Property extioi_properties[] = {
> +    DEFINE_PROP_UINT32("num-cpu", KVMLoongArchExtIOI, num_cpu, 1),
> +    DEFINE_PROP_BIT("has-virtualization-extension", KVMLoongArchExtIOI,
> +                    features, EXTIOI_HAS_VIRT_EXTENSION, 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
Ditto, it is duplicated.

> +
> +static void kvm_loongarch_extioi_class_init(ObjectClass *oc, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(oc);
> +    KVMLoongArchExtIOIClass *extioi_class = KVM_LOONGARCH_EXTIOI_CLASS(oc);
> +
> +    extioi_class->parent_realize = dc->realize;
> +    dc->realize = kvm_loongarch_extioi_realize;
> +    extioi_class->is_created = false;
> +    device_class_set_props(dc, extioi_properties);
> +    dc->vmsd = &vmstate_kvm_extioi_core;
Do we need reset interface for irqchip_in_kernel mode?

Regards
Bibo Mao

> +}
> +
> +static const TypeInfo kvm_loongarch_extioi_info = {
> +    .name = TYPE_KVM_LOONGARCH_EXTIOI,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(KVMLoongArchExtIOI),
> +    .class_size = sizeof(KVMLoongArchExtIOIClass),
> +    .class_init = kvm_loongarch_extioi_class_init,
> +};
> +
> +static void kvm_loongarch_extioi_register_types(void)
> +{
> +    type_register_static(&kvm_loongarch_extioi_info);
> +}
> +
> +type_init(kvm_loongarch_extioi_register_types)
> diff --git a/hw/intc/meson.build b/hw/intc/meson.build
> index f55eb1b80b..85174d1af1 100644
> --- a/hw/intc/meson.build
> +++ b/hw/intc/meson.build
> @@ -76,3 +76,4 @@ specific_ss.add(when: 'CONFIG_LOONGARCH_IPI_KVM', if_true: files('loongarch_ipi_
>   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'))
> +specific_ss.add(when: 'CONFIG_LOONGARCH_EXTIOI_KVM', if_true: files('loongarch_extioi_kvm.c'))
> diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig
> index f8fcac3e7b..99a523171f 100644
> --- a/hw/loongarch/Kconfig
> +++ b/hw/loongarch/Kconfig
> @@ -17,6 +17,7 @@ config LOONGARCH_VIRT
>       select LOONGARCH_PCH_MSI
>       select LOONGARCH_EXTIOI
>       select LOONGARCH_IPI_KVM if KVM
> +    select LOONGARCH_EXTIOI_KVM if KVM
>       select LS7A_RTC
>       select SMBIOS
>       select ACPI_PCI
> diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c
> index 3b28e8e671..8ca7c09016 100644
> --- a/hw/loongarch/virt.c
> +++ b/hw/loongarch/virt.c
> @@ -828,28 +828,37 @@ static void virt_irq_init(LoongArchVirtMachineState *lvms)
>       }
>   
>       /* Create EXTIOI device */
> -    extioi = qdev_new(TYPE_LOONGARCH_EXTIOI);
> -    qdev_prop_set_uint32(extioi, "num-cpu", ms->smp.cpus);
> -    if (virt_is_veiointc_enabled(lvms)) {
> -        qdev_prop_set_bit(extioi, "has-virtualization-extension", true);
> -    }
> -    sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
> -    memory_region_add_subregion(&lvms->system_iocsr, APIC_BASE,
> -                    sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 0));
> -    if (virt_is_veiointc_enabled(lvms)) {
> -        memory_region_add_subregion(&lvms->system_iocsr, EXTIOI_VIRT_BASE,
> -                    sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 1));
> -    }
> +    if (kvm_enabled() && kvm_irqchip_in_kernel()) {
> +        extioi = qdev_new(TYPE_KVM_LOONGARCH_EXTIOI);
> +        qdev_prop_set_uint32(extioi, "num-cpu", ms->smp.cpus);
> +        if (virt_is_veiointc_enabled(lvms)) {
> +            qdev_prop_set_bit(extioi, "has-virtualization-extension", true);
> +        }
> +        sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
> +    } else {
> +        extioi = qdev_new(TYPE_LOONGARCH_EXTIOI);
> +        qdev_prop_set_uint32(extioi, "num-cpu", ms->smp.cpus);
> +        if (virt_is_veiointc_enabled(lvms)) {
> +            qdev_prop_set_bit(extioi, "has-virtualization-extension", true);
> +        }
> +        sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
> +        memory_region_add_subregion(&lvms->system_iocsr, APIC_BASE,
> +                       sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 0));
> +        if (virt_is_veiointc_enabled(lvms)) {
> +            memory_region_add_subregion(&lvms->system_iocsr, EXTIOI_VIRT_BASE,
> +                        sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 1));
> +        }
>   
> -    /*
> -     * connect ext irq to the cpu irq
> -     * cpu_pin[9:2] <= intc_pin[7:0]
> -     */
> -    for (cpu = 0; cpu < ms->smp.cpus; cpu++) {
> -        cpudev = DEVICE(qemu_get_cpu(cpu));
> -        for (pin = 0; pin < LS3A_INTC_IP; pin++) {
> -            qdev_connect_gpio_out(extioi, (cpu * 8 + pin),
> -                                  qdev_get_gpio_in(cpudev, pin + 2));
> +        /*
> +         * connect ext irq to the cpu irq
> +         * cpu_pin[9:2] <= intc_pin[7:0]
> +         */
> +        for (cpu = 0; cpu < ms->smp.cpus; cpu++) {
> +            cpudev = DEVICE(qemu_get_cpu(cpu));
> +            for (pin = 0; pin < LS3A_INTC_IP; pin++) {
> +                qdev_connect_gpio_out(extioi, (cpu * 8 + pin),
> +                                      qdev_get_gpio_in(cpudev, pin + 2));
> +            }
>           }
>       }
>   
>
diff mbox series

Patch

diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index 5201505f23..df9352d41d 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -112,3 +112,6 @@  config LOONGARCH_PCH_MSI
 
 config LOONGARCH_EXTIOI
     bool
+
+config LOONGARCH_EXTIOI_KVM
+    bool
diff --git a/hw/intc/loongarch_extioi_kvm.c b/hw/intc/loongarch_extioi_kvm.c
new file mode 100644
index 0000000000..139a00ac2a
--- /dev/null
+++ b/hw/intc/loongarch_extioi_kvm.c
@@ -0,0 +1,250 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * LoongArch kvm extioi interrupt support
+ *
+ * Copyright (C) 2024 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "hw/qdev-properties.h"
+#include "qemu/typedefs.h"
+#include "hw/intc/loongarch_extioi.h"
+#include "hw/sysbus.h"
+#include "linux/kvm.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "sysemu/kvm.h"
+
+static void kvm_extioi_access_regs(int fd, uint64_t addr,
+                                       void *val, bool is_write)
+{
+        kvm_device_access(fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_REGS,
+                          addr, val, is_write, &error_abort);
+}
+
+static void kvm_extioi_access_sw_status(int fd, uint64_t addr,
+                                       void *val, bool is_write)
+{
+        kvm_device_access(fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_SW_STATUS,
+                          addr, val, is_write, &error_abort);
+}
+
+static void kvm_extioi_save_load_sw_status(void *opaque, bool is_write)
+{
+    KVMLoongArchExtIOI *s = (KVMLoongArchExtIOI *)opaque;
+    KVMLoongArchExtIOIClass *class = KVM_LOONGARCH_EXTIOI_GET_CLASS(s);
+    int fd = class->dev_fd;
+    int addr;
+
+    addr = KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_NUM_CPU;
+    kvm_extioi_access_sw_status(fd, addr, (void *)&s->num_cpu, is_write);
+
+    addr = KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_FEATURE;
+    kvm_extioi_access_sw_status(fd, addr, (void *)&s->features, is_write);
+
+    addr = KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_STATE;
+    kvm_extioi_access_sw_status(fd, addr, (void *)&s->status, is_write);
+}
+
+static void kvm_extioi_save_load_regs(void *opaque, bool is_write)
+{
+    KVMLoongArchExtIOI *s = (KVMLoongArchExtIOI *)opaque;
+    KVMLoongArchExtIOIClass *class = KVM_LOONGARCH_EXTIOI_GET_CLASS(s);
+    int fd = class->dev_fd;
+    int addr, offset, cpuid;
+
+    for (addr = EXTIOI_NODETYPE_START; addr < EXTIOI_NODETYPE_END; addr += 4) {
+        offset = (addr - EXTIOI_NODETYPE_START) / 4;
+        kvm_extioi_access_regs(fd, addr,
+                               (void *)&s->nodetype[offset], is_write);
+    }
+
+    for (addr = EXTIOI_IPMAP_START; addr < EXTIOI_IPMAP_END; addr += 4) {
+        offset = (addr - EXTIOI_IPMAP_START) / 4;
+        kvm_extioi_access_regs(fd, addr, (void *)&s->ipmap[offset], is_write);
+    }
+
+    for (addr = EXTIOI_ENABLE_START; addr < EXTIOI_ENABLE_END; addr += 4) {
+        offset = (addr - EXTIOI_ENABLE_START) / 4;
+        kvm_extioi_access_regs(fd, addr,
+                               (void *)&s->enable[offset], is_write);
+    }
+
+    for (addr = EXTIOI_BOUNCE_START; addr < EXTIOI_BOUNCE_END; addr += 4) {
+        offset = (addr - EXTIOI_BOUNCE_START) / 4;
+        kvm_extioi_access_regs(fd, addr,
+                               (void *)&s->bounce[offset], is_write);
+    }
+
+    for (addr = EXTIOI_ISR_START; addr < EXTIOI_ISR_END; addr += 4) {
+        offset = (addr - EXTIOI_ISR_START) / 4;
+        kvm_extioi_access_regs(fd, addr,
+                               (void *)&s->isr[offset], is_write);
+    }
+
+    for (addr = EXTIOI_COREMAP_START; addr < EXTIOI_COREMAP_END; addr += 4) {
+        offset = (addr - EXTIOI_COREMAP_START) / 4;
+        kvm_extioi_access_regs(fd, addr,
+                               (void *)&s->coremap[offset], is_write);
+    }
+
+    for (cpuid = 0; cpuid < s->num_cpu; cpuid++) {
+        for (addr = EXTIOI_COREISR_START;
+             addr < EXTIOI_COREISR_END; addr += 4) {
+            offset = (addr - EXTIOI_COREISR_START) / 4;
+            addr = (cpuid << 16) | addr;
+            kvm_extioi_access_regs(fd, addr,
+                              (void *)&s->coreisr[cpuid][offset], is_write);
+        }
+    }
+}
+
+static int kvm_loongarch_extioi_pre_save(void *opaque)
+{
+    kvm_extioi_save_load_regs(opaque, false);
+    kvm_extioi_save_load_sw_status(opaque, false);
+    return 0;
+}
+
+static int kvm_loongarch_extioi_post_load(void *opaque, int version_id)
+{
+    KVMLoongArchExtIOI *s = (KVMLoongArchExtIOI *)opaque;
+    KVMLoongArchExtIOIClass *class = KVM_LOONGARCH_EXTIOI_GET_CLASS(s);
+    int fd = class->dev_fd;
+
+    kvm_extioi_save_load_regs(opaque, true);
+    kvm_extioi_save_load_sw_status(opaque, true);
+
+    kvm_device_access(fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL,
+                      KVM_DEV_LOONGARCH_EXTIOI_CTRL_LOAD_FINISHED,
+                      NULL, true, &error_abort);
+    return 0;
+}
+
+static void kvm_loongarch_extioi_realize(DeviceState *dev, Error **errp)
+{
+    KVMLoongArchExtIOIClass *extioi_class = KVM_LOONGARCH_EXTIOI_GET_CLASS(dev);
+    KVMLoongArchExtIOI *s = KVM_LOONGARCH_EXTIOI(dev);
+    struct kvm_create_device cd = {0};
+    Error *err = NULL;
+    int ret, i;
+
+    extioi_class->parent_realize(dev, &err);
+    if (err) {
+        error_propagate(errp, err);
+        return;
+    }
+
+    if (s->num_cpu == 0) {
+        error_setg(errp, "num-cpu must be at least 1");
+        return;
+    }
+
+
+    if (extioi_class->is_created) {
+        error_setg(errp, "extioi had be created");
+        return;
+    }
+
+    if (s->features & BIT(EXTIOI_HAS_VIRT_EXTENSION)) {
+        s->features |= EXTIOI_VIRT_HAS_FEATURES;
+    }
+
+    cd.type = KVM_DEV_TYPE_LOONGARCH_EXTIOI;
+    ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd);
+    if (ret < 0) {
+        error_setg_errno(errp, errno,
+                         "Creating the KVM extioi device failed");
+        return;
+    }
+    extioi_class->is_created = true;
+    extioi_class->dev_fd = cd.fd;
+
+    ret = kvm_device_access(cd.fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL,
+                            KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_NUM_CPU,
+                            &s->num_cpu, true, NULL);
+    if (ret < 0) {
+        error_setg_errno(errp, errno,
+                         "KVM EXTIOI: failed to set the num-cpu of EXTIOI");
+        exit(1);
+    }
+
+    ret = kvm_device_access(cd.fd, KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL,
+                            KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_FEATURE,
+                            &s->features, true, NULL);
+    if (ret < 0) {
+        error_setg_errno(errp, errno,
+                         "KVM EXTIOI: failed to set the feature of EXTIOI");
+        exit(1);
+    }
+
+    fprintf(stdout, "Create LoongArch extioi irqchip in KVM done!\n");
+
+    kvm_async_interrupts_allowed = true;
+    kvm_msi_via_irqfd_allowed = kvm_irqfds_enabled();
+    if (kvm_has_gsi_routing()) {
+        for (i = 0; i < 64; ++i) {
+            kvm_irqchip_add_irq_route(kvm_state, i, 0, i);
+        }
+        kvm_gsi_routing_allowed = true;
+    }
+}
+
+static const VMStateDescription vmstate_kvm_extioi_core = {
+    .name = "kvm-extioi-single",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .pre_save = kvm_loongarch_extioi_pre_save,
+    .post_load = kvm_loongarch_extioi_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(nodetype, KVMLoongArchExtIOI,
+                             EXTIOI_IRQS_NODETYPE_COUNT / 2),
+        VMSTATE_UINT32_ARRAY(bounce, KVMLoongArchExtIOI,
+                             EXTIOI_IRQS_GROUP_COUNT),
+        VMSTATE_UINT32_ARRAY(isr, KVMLoongArchExtIOI, EXTIOI_IRQS / 32),
+        VMSTATE_UINT32_2DARRAY(coreisr, KVMLoongArchExtIOI, EXTIOI_CPUS,
+                               EXTIOI_IRQS_GROUP_COUNT),
+        VMSTATE_UINT32_ARRAY(enable, KVMLoongArchExtIOI, EXTIOI_IRQS / 32),
+        VMSTATE_UINT32_ARRAY(ipmap, KVMLoongArchExtIOI,
+                             EXTIOI_IRQS_IPMAP_SIZE / 4),
+        VMSTATE_UINT32_ARRAY(coremap, KVMLoongArchExtIOI, EXTIOI_IRQS / 4),
+        VMSTATE_UINT8_ARRAY(sw_coremap, KVMLoongArchExtIOI, EXTIOI_IRQS),
+        VMSTATE_UINT32(features, KVMLoongArchExtIOI),
+        VMSTATE_UINT32(status, KVMLoongArchExtIOI),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property extioi_properties[] = {
+    DEFINE_PROP_UINT32("num-cpu", KVMLoongArchExtIOI, num_cpu, 1),
+    DEFINE_PROP_BIT("has-virtualization-extension", KVMLoongArchExtIOI,
+                    features, EXTIOI_HAS_VIRT_EXTENSION, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void kvm_loongarch_extioi_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    KVMLoongArchExtIOIClass *extioi_class = KVM_LOONGARCH_EXTIOI_CLASS(oc);
+
+    extioi_class->parent_realize = dc->realize;
+    dc->realize = kvm_loongarch_extioi_realize;
+    extioi_class->is_created = false;
+    device_class_set_props(dc, extioi_properties);
+    dc->vmsd = &vmstate_kvm_extioi_core;
+}
+
+static const TypeInfo kvm_loongarch_extioi_info = {
+    .name = TYPE_KVM_LOONGARCH_EXTIOI,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(KVMLoongArchExtIOI),
+    .class_size = sizeof(KVMLoongArchExtIOIClass),
+    .class_init = kvm_loongarch_extioi_class_init,
+};
+
+static void kvm_loongarch_extioi_register_types(void)
+{
+    type_register_static(&kvm_loongarch_extioi_info);
+}
+
+type_init(kvm_loongarch_extioi_register_types)
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index f55eb1b80b..85174d1af1 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -76,3 +76,4 @@  specific_ss.add(when: 'CONFIG_LOONGARCH_IPI_KVM', if_true: files('loongarch_ipi_
 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'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_EXTIOI_KVM', if_true: files('loongarch_extioi_kvm.c'))
diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig
index f8fcac3e7b..99a523171f 100644
--- a/hw/loongarch/Kconfig
+++ b/hw/loongarch/Kconfig
@@ -17,6 +17,7 @@  config LOONGARCH_VIRT
     select LOONGARCH_PCH_MSI
     select LOONGARCH_EXTIOI
     select LOONGARCH_IPI_KVM if KVM
+    select LOONGARCH_EXTIOI_KVM if KVM
     select LS7A_RTC
     select SMBIOS
     select ACPI_PCI
diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c
index 3b28e8e671..8ca7c09016 100644
--- a/hw/loongarch/virt.c
+++ b/hw/loongarch/virt.c
@@ -828,28 +828,37 @@  static void virt_irq_init(LoongArchVirtMachineState *lvms)
     }
 
     /* Create EXTIOI device */
-    extioi = qdev_new(TYPE_LOONGARCH_EXTIOI);
-    qdev_prop_set_uint32(extioi, "num-cpu", ms->smp.cpus);
-    if (virt_is_veiointc_enabled(lvms)) {
-        qdev_prop_set_bit(extioi, "has-virtualization-extension", true);
-    }
-    sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
-    memory_region_add_subregion(&lvms->system_iocsr, APIC_BASE,
-                    sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 0));
-    if (virt_is_veiointc_enabled(lvms)) {
-        memory_region_add_subregion(&lvms->system_iocsr, EXTIOI_VIRT_BASE,
-                    sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 1));
-    }
+    if (kvm_enabled() && kvm_irqchip_in_kernel()) {
+        extioi = qdev_new(TYPE_KVM_LOONGARCH_EXTIOI);
+        qdev_prop_set_uint32(extioi, "num-cpu", ms->smp.cpus);
+        if (virt_is_veiointc_enabled(lvms)) {
+            qdev_prop_set_bit(extioi, "has-virtualization-extension", true);
+        }
+        sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
+    } else {
+        extioi = qdev_new(TYPE_LOONGARCH_EXTIOI);
+        qdev_prop_set_uint32(extioi, "num-cpu", ms->smp.cpus);
+        if (virt_is_veiointc_enabled(lvms)) {
+            qdev_prop_set_bit(extioi, "has-virtualization-extension", true);
+        }
+        sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
+        memory_region_add_subregion(&lvms->system_iocsr, APIC_BASE,
+                       sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 0));
+        if (virt_is_veiointc_enabled(lvms)) {
+            memory_region_add_subregion(&lvms->system_iocsr, EXTIOI_VIRT_BASE,
+                        sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi), 1));
+        }
 
-    /*
-     * connect ext irq to the cpu irq
-     * cpu_pin[9:2] <= intc_pin[7:0]
-     */
-    for (cpu = 0; cpu < ms->smp.cpus; cpu++) {
-        cpudev = DEVICE(qemu_get_cpu(cpu));
-        for (pin = 0; pin < LS3A_INTC_IP; pin++) {
-            qdev_connect_gpio_out(extioi, (cpu * 8 + pin),
-                                  qdev_get_gpio_in(cpudev, pin + 2));
+        /*
+         * connect ext irq to the cpu irq
+         * cpu_pin[9:2] <= intc_pin[7:0]
+         */
+        for (cpu = 0; cpu < ms->smp.cpus; cpu++) {
+            cpudev = DEVICE(qemu_get_cpu(cpu));
+            for (pin = 0; pin < LS3A_INTC_IP; pin++) {
+                qdev_connect_gpio_out(extioi, (cpu * 8 + pin),
+                                      qdev_get_gpio_in(cpudev, pin + 2));
+            }
         }
     }