This patch add Loongson-3 Virtual IPI interrupt support in the kernel,
because emulate it in QEMU is too expensive for performance.

Signed-off-by: Huacai Chen <che...@lemote.com>
Co-developed-by: Jiaxun Yang <jiaxun.y...@flygoat.com>
---
 arch/mips/include/asm/kvm_host.h |  32 ++++++
 arch/mips/kvm/Makefile           |   3 +
 arch/mips/kvm/emulate.c          |  23 ++++-
 arch/mips/kvm/loongson_ipi.c     | 214 +++++++++++++++++++++++++++++++++++++++
 arch/mips/kvm/mips.c             |   6 ++
 5 files changed, 277 insertions(+), 1 deletion(-)
 create mode 100644 arch/mips/kvm/loongson_ipi.c

diff --git a/arch/mips/include/asm/kvm_host.h b/arch/mips/include/asm/kvm_host.h
index a7758c0..f165902 100644
--- a/arch/mips/include/asm/kvm_host.h
+++ b/arch/mips/include/asm/kvm_host.h
@@ -23,6 +23,8 @@
 #include <asm/inst.h>
 #include <asm/mipsregs.h>
 
+#include <kvm/iodev.h>
+
 /* MIPS KVM register ids */
 #define MIPS_CP0_32(_R, _S)                                    \
        (KVM_REG_MIPS_CP0 | KVM_REG_SIZE_U32 | (8 * (_R) + (_S)))
@@ -181,11 +183,39 @@ struct kvm_vcpu_stat {
 struct kvm_arch_memory_slot {
 };
 
+#ifdef CONFIG_CPU_LOONGSON64
+struct ipi_state {
+       uint32_t status;
+       uint32_t en;
+       uint32_t set;
+       uint32_t clear;
+       uint64_t buf[4];
+};
+
+struct loongson_kvm_ipi;
+
+struct ipi_io_device {
+       int node_id;
+       struct loongson_kvm_ipi *ipi;
+       struct kvm_io_device device;
+};
+
+struct loongson_kvm_ipi {
+       spinlock_t lock;
+       struct kvm *kvm;
+       struct ipi_state ipistate[16];
+       struct ipi_io_device dev_ipi[4];
+};
+#endif
+
 struct kvm_arch {
        /* Guest physical mm */
        struct mm_struct gpa_mm;
        /* Mask of CPUs needing GPA ASID flush */
        cpumask_t asid_flush_mask;
+#ifdef CONFIG_CPU_LOONGSON64
+       struct loongson_kvm_ipi ipi;
+#endif
 };
 
 #define N_MIPS_COPROC_REGS     32
@@ -1133,6 +1163,8 @@ extern int kvm_mips_trans_mtc0(union mips_instruction 
inst, u32 *opc,
 /* Misc */
 extern void kvm_mips_dump_stats(struct kvm_vcpu *vcpu);
 extern unsigned long kvm_mips_get_ramsize(struct kvm *kvm);
+extern int kvm_vcpu_ioctl_interrupt(struct kvm_vcpu *vcpu,
+                            struct kvm_mips_interrupt *irq);
 
 static inline void kvm_arch_hardware_unsetup(void) {}
 static inline void kvm_arch_sync_events(struct kvm *kvm) {}
diff --git a/arch/mips/kvm/Makefile b/arch/mips/kvm/Makefile
index 0a3cef6..506c4ac 100644
--- a/arch/mips/kvm/Makefile
+++ b/arch/mips/kvm/Makefile
@@ -13,6 +13,9 @@ kvm-objs := $(common-objs-y) mips.o emulate.o entry.o \
            fpu.o
 kvm-objs += hypcall.o
 kvm-objs += mmu.o
+ifdef CONFIG_CPU_LOONGSON64
+kvm-objs += loongson_ipi.o
+endif
 
 ifdef CONFIG_KVM_MIPS_VZ
 kvm-objs               += vz.o
diff --git a/arch/mips/kvm/emulate.c b/arch/mips/kvm/emulate.c
index 754094b..3946499 100644
--- a/arch/mips/kvm/emulate.c
+++ b/arch/mips/kvm/emulate.c
@@ -1600,6 +1600,7 @@ enum emulation_result kvm_mips_emulate_store(union 
mips_instruction inst,
                                             struct kvm_run *run,
                                             struct kvm_vcpu *vcpu)
 {
+       int r;
        enum emulation_result er;
        u32 rt;
        void *data = run->mmio.data;
@@ -1666,9 +1667,18 @@ enum emulation_result kvm_mips_emulate_store(union 
mips_instruction inst,
                goto out_fail;
        }
 
-       run->mmio.is_write = 1;
        vcpu->mmio_needed = 1;
+       run->mmio.is_write = 1;
        vcpu->mmio_is_write = 1;
+
+       r = kvm_io_bus_write(vcpu, KVM_MMIO_BUS,
+                       run->mmio.phys_addr, run->mmio.len, data);
+
+       if (!r) {
+               vcpu->mmio_needed = 0;
+               return EMULATE_DONE;
+       }
+
        return EMULATE_DO_MMIO;
 
 out_fail:
@@ -1681,6 +1691,7 @@ enum emulation_result kvm_mips_emulate_load(union 
mips_instruction inst,
                                            u32 cause, struct kvm_run *run,
                                            struct kvm_vcpu *vcpu)
 {
+       int r;
        enum emulation_result er;
        unsigned long curr_pc;
        u32 op, rt;
@@ -1745,6 +1756,16 @@ enum emulation_result kvm_mips_emulate_load(union 
mips_instruction inst,
 
        run->mmio.is_write = 0;
        vcpu->mmio_is_write = 0;
+
+       r = kvm_io_bus_read(vcpu, KVM_MMIO_BUS,
+                       run->mmio.phys_addr, run->mmio.len, run->mmio.data);
+
+       if (!r) {
+               kvm_mips_complete_mmio_load(vcpu, run);
+               vcpu->mmio_needed = 0;
+               return EMULATE_DONE;
+       }
+
        return EMULATE_DO_MMIO;
 }
 
diff --git a/arch/mips/kvm/loongson_ipi.c b/arch/mips/kvm/loongson_ipi.c
new file mode 100644
index 00000000..3681fc8
--- /dev/null
+++ b/arch/mips/kvm/loongson_ipi.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Loongson-3 Virtual IPI interrupt support.
+ *
+ * Copyright (C) 2019  Loongson Technologies, Inc.  All rights reserved.
+ *
+ * Authors: Chen Zhu <zhuc...@loongson.cn>
+ * Authors: Huacai Chen <che...@lemote.com>
+ */
+
+#include <linux/kvm_host.h>
+
+#define IPI_BASE            0x3ff01000ULL
+
+#define CORE0_STATUS_OFF       0x000
+#define CORE0_EN_OFF           0x004
+#define CORE0_SET_OFF          0x008
+#define CORE0_CLEAR_OFF        0x00c
+#define CORE0_BUF_20           0x020
+#define CORE0_BUF_28           0x028
+#define CORE0_BUF_30           0x030
+#define CORE0_BUF_38           0x038
+
+#define CORE1_STATUS_OFF       0x100
+#define CORE1_EN_OFF           0x104
+#define CORE1_SET_OFF          0x108
+#define CORE1_CLEAR_OFF        0x10c
+#define CORE1_BUF_20           0x120
+#define CORE1_BUF_28           0x128
+#define CORE1_BUF_30           0x130
+#define CORE1_BUF_38           0x138
+
+#define CORE2_STATUS_OFF       0x200
+#define CORE2_EN_OFF           0x204
+#define CORE2_SET_OFF          0x208
+#define CORE2_CLEAR_OFF        0x20c
+#define CORE2_BUF_20           0x220
+#define CORE2_BUF_28           0x228
+#define CORE2_BUF_30           0x230
+#define CORE2_BUF_38           0x238
+
+#define CORE3_STATUS_OFF       0x300
+#define CORE3_EN_OFF           0x304
+#define CORE3_SET_OFF          0x308
+#define CORE3_CLEAR_OFF        0x30c
+#define CORE3_BUF_20           0x320
+#define CORE3_BUF_28           0x328
+#define CORE3_BUF_30           0x330
+#define CORE3_BUF_38           0x338
+
+static int loongson_vipi_read(struct loongson_kvm_ipi *ipi,
+                               gpa_t addr, int len, void *val)
+{
+       uint32_t core = (addr >> 8) & 3;
+       uint32_t node = (addr >> 44) & 3;
+       uint32_t id = core + node * 4;
+       uint64_t offset = addr & 0xff;
+       void *pbuf;
+       struct ipi_state *s = &(ipi->ipistate[id]);
+
+       BUG_ON(offset & (len - 1));
+
+       switch (offset) {
+       case CORE0_STATUS_OFF:
+               *(uint64_t *)val = s->status;
+               break;
+
+       case CORE0_EN_OFF:
+               *(uint64_t *)val = s->en;
+               break;
+
+       case CORE0_SET_OFF:
+               *(uint64_t *)val = 0;
+               break;
+
+       case CORE0_CLEAR_OFF:
+               *(uint64_t *)val = 0;
+               break;
+
+       case CORE0_BUF_20 ... CORE0_BUF_38:
+               pbuf = (void *)s->buf + (offset - 0x20);
+               if (len == 8)
+                       *(uint64_t *)val = *(uint64_t *)pbuf;
+               else /* Assume len == 4 */
+                       *(uint32_t *)val = *(uint32_t *)pbuf;
+               break;
+
+       default:
+               pr_notice("%s with unknown addr %llx\n", __func__, addr);
+               break;
+       }
+
+       return 0;
+}
+
+static int loongson_vipi_write(struct loongson_kvm_ipi *ipi,
+                               gpa_t addr, int len, const void *val)
+{
+       uint32_t core = (addr >> 8) & 3;
+       uint32_t node = (addr >> 44) & 3;
+       uint32_t id = core + node * 4;
+       uint64_t data, offset = addr & 0xff;
+       void *pbuf;
+       struct kvm *kvm = ipi->kvm;
+       struct kvm_mips_interrupt irq;
+       struct ipi_state *s = &(ipi->ipistate[id]);
+
+       data = *(uint64_t *)val;
+       BUG_ON(offset & (len - 1));
+
+       switch (offset) {
+       case CORE0_STATUS_OFF:
+               break;
+
+       case CORE0_EN_OFF:
+               s->en = data;
+               break;
+
+       case CORE0_SET_OFF:
+               s->status |= data;
+               irq.cpu = id;
+               irq.irq = 6;
+               kvm_vcpu_ioctl_interrupt(kvm->vcpus[id], &irq);
+               break;
+
+       case CORE0_CLEAR_OFF:
+               s->status &= ~data;
+               if (!s->status) {
+                       irq.cpu = id;
+                       irq.irq = -6;
+                       kvm_vcpu_ioctl_interrupt(kvm->vcpus[id], &irq);
+               }
+               break;
+
+       case CORE0_BUF_20 ... CORE0_BUF_38:
+               pbuf = (void *)s->buf + (offset - 0x20);
+               if (len == 8)
+                       *(uint64_t *)pbuf = (uint64_t)data;
+               else /* Assume len == 4 */
+                       *(uint32_t *)pbuf = (uint32_t)data;
+               break;
+
+       default:
+               pr_notice("%s with unknown addr %llx\n", __func__, addr);
+               break;
+       }
+
+       return 0;
+}
+
+static int kvm_ipi_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
+                       gpa_t addr, int len, void *val)
+{
+       unsigned long flags;
+       struct loongson_kvm_ipi *ipi;
+       struct ipi_io_device *ipi_device;
+
+       ipi_device = container_of(dev, struct ipi_io_device, device);
+       ipi = ipi_device->ipi;
+
+       spin_lock_irqsave(&ipi->lock, flags);
+       loongson_vipi_read(ipi, addr, len, val);
+       spin_unlock_irqrestore(&ipi->lock, flags);
+
+       return 0;
+}
+
+static int kvm_ipi_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
+                       gpa_t addr, int len, const void *val)
+{
+       unsigned long flags;
+       struct loongson_kvm_ipi *ipi;
+       struct ipi_io_device *ipi_device;
+
+       ipi_device = container_of(dev, struct ipi_io_device, device);
+       ipi = ipi_device->ipi;
+
+       spin_lock_irqsave(&ipi->lock, flags);
+       loongson_vipi_write(ipi, addr, len, val);
+       spin_unlock_irqrestore(&ipi->lock, flags);
+
+       return 0;
+}
+
+static const struct kvm_io_device_ops kvm_ipi_ops = {
+       .read     = kvm_ipi_read,
+       .write    = kvm_ipi_write,
+};
+
+void kvm_init_loongson_ipi(struct kvm *kvm)
+{
+       int i;
+       unsigned long addr;
+       struct loongson_kvm_ipi *s;
+       struct kvm_io_device *device;
+
+       s = &kvm->arch.ipi;
+       s->kvm = kvm;
+       spin_lock_init(&s->lock);
+
+       /*
+        * Initialize IPI device
+        */
+       for (i = 0; i < 4; i++) {
+               device = &s->dev_ipi[i].device;
+               kvm_iodevice_init(device, &kvm_ipi_ops);
+               addr = (((unsigned long)i) << 44) + IPI_BASE;
+               mutex_lock(&kvm->slots_lock);
+               kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, addr, 0x400, device);
+               mutex_unlock(&kvm->slots_lock);
+               s->dev_ipi[i].ipi = s;
+               s->dev_ipi[i].node_id = i;
+       }
+}
diff --git a/arch/mips/kvm/mips.c b/arch/mips/kvm/mips.c
index 5ca122c..ed989ef 100644
--- a/arch/mips/kvm/mips.c
+++ b/arch/mips/kvm/mips.c
@@ -128,6 +128,8 @@ int kvm_arch_check_processor_compat(void *opaque)
        return 0;
 }
 
+extern void kvm_init_loongson_ipi(struct kvm *kvm);
+
 int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
 {
        switch (type) {
@@ -147,6 +149,10 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
        if (!kvm->arch.gpa_mm.pgd)
                return -ENOMEM;
 
+#ifdef CONFIG_CPU_LOONGSON64
+       kvm_init_loongson_ipi(kvm);
+#endif
+
        return 0;
 }
 
-- 
2.7.0


Reply via email to