RISC-V: KVM: Expose IMSIC registers as attributes of AIA irqchip
authorAnup Patel <apatel@ventanamicro.com>
Thu, 15 Jun 2023 07:33:53 +0000 (13:03 +0530)
committerAnup Patel <anup@brainfault.org>
Mon, 19 Jun 2023 16:57:58 +0000 (22:27 +0530)
We expose IMSIC registers as KVM device attributes of the in-kernel
AIA irqchip device. This will allow KVM user-space to save/restore
IMISC state of each VCPU using KVM device ioctls().

Signed-off-by: Anup Patel <apatel@ventanamicro.com>
Reviewed-by: Atish Patra <atishp@rivosinc.com>
Signed-off-by: Anup Patel <anup@brainfault.org>
arch/riscv/include/asm/kvm_aia.h
arch/riscv/include/uapi/asm/kvm.h
arch/riscv/kvm/aia_device.c
arch/riscv/kvm/aia_imsic.c

index a4f6ebf90e3178acb81c58c9ef8aa5054a8c3a0e..1f37b600ca47925ee45d67e5b4822ff358843119 100644 (file)
@@ -97,6 +97,9 @@ int kvm_riscv_vcpu_aia_imsic_update(struct kvm_vcpu *vcpu);
 int kvm_riscv_vcpu_aia_imsic_rmw(struct kvm_vcpu *vcpu, unsigned long isel,
                                 unsigned long *val, unsigned long new_val,
                                 unsigned long wr_mask);
+int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type,
+                               bool write, unsigned long *val);
+int kvm_riscv_aia_imsic_has_attr(struct kvm *kvm, unsigned long type);
 void kvm_riscv_vcpu_aia_imsic_reset(struct kvm_vcpu *vcpu);
 int kvm_riscv_vcpu_aia_imsic_inject(struct kvm_vcpu *vcpu,
                                    u32 guest_index, u32 offset, u32 iid);
index 9ed822fc558935aed3cf36667d17c911ecce8f56..61d7fecc4899631da1ba17ee1cbd3e1c124cc823 100644 (file)
@@ -255,6 +255,23 @@ enum KVM_RISCV_SBI_EXT_ID {
  */
 #define KVM_DEV_RISCV_AIA_GRP_APLIC            3
 
+/*
+ * The lower 12-bits of the device attribute type contains the iselect
+ * value of the IMSIC register (range 0x70-0xFF) whereas the higher order
+ * bits contains the VCPU id.
+ */
+#define KVM_DEV_RISCV_AIA_GRP_IMSIC            4
+#define KVM_DEV_RISCV_AIA_IMSIC_ISEL_BITS      12
+#define KVM_DEV_RISCV_AIA_IMSIC_ISEL_MASK      \
+               ((1U << KVM_DEV_RISCV_AIA_IMSIC_ISEL_BITS) - 1)
+#define KVM_DEV_RISCV_AIA_IMSIC_MKATTR(__vcpu, __isel) \
+               (((__vcpu) << KVM_DEV_RISCV_AIA_IMSIC_ISEL_BITS) | \
+                ((__isel) & KVM_DEV_RISCV_AIA_IMSIC_ISEL_MASK))
+#define KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(__attr)       \
+               ((__attr) & KVM_DEV_RISCV_AIA_IMSIC_ISEL_MASK)
+#define KVM_DEV_RISCV_AIA_IMSIC_GET_VCPU(__attr)       \
+               ((__attr) >> KVM_DEV_RISCV_AIA_IMSIC_ISEL_BITS)
+
 /* One single KVM irqchip, ie. the AIA */
 #define KVM_NR_IRQCHIPS                        1
 
index c649ad6e8e0ad3cfef4d92dd42886c57932e8fdf..84dae351b6d797bfd35fc79d05cea5dc256bf28e 100644 (file)
@@ -327,7 +327,7 @@ static int aia_set_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
        u32 nr;
        u64 addr;
        int nr_vcpus, r = -ENXIO;
-       unsigned long type = (unsigned long)attr->attr;
+       unsigned long v, type = (unsigned long)attr->attr;
        void __user *uaddr = (void __user *)(long)attr->addr;
 
        switch (attr->group) {
@@ -374,6 +374,15 @@ static int aia_set_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
                r = kvm_riscv_aia_aplic_set_attr(dev->kvm, type, nr);
                mutex_unlock(&dev->kvm->lock);
 
+               break;
+       case KVM_DEV_RISCV_AIA_GRP_IMSIC:
+               if (copy_from_user(&v, uaddr, sizeof(v)))
+                       return -EFAULT;
+
+               mutex_lock(&dev->kvm->lock);
+               r = kvm_riscv_aia_imsic_rw_attr(dev->kvm, type, true, &v);
+               mutex_unlock(&dev->kvm->lock);
+
                break;
        }
 
@@ -386,7 +395,7 @@ static int aia_get_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
        u64 addr;
        int nr_vcpus, r = -ENXIO;
        void __user *uaddr = (void __user *)(long)attr->addr;
-       unsigned long type = (unsigned long)attr->attr;
+       unsigned long v, type = (unsigned long)attr->attr;
 
        switch (attr->group) {
        case KVM_DEV_RISCV_AIA_GRP_CONFIG:
@@ -435,6 +444,20 @@ static int aia_get_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
                if (copy_to_user(uaddr, &nr, sizeof(nr)))
                        return -EFAULT;
 
+               break;
+       case KVM_DEV_RISCV_AIA_GRP_IMSIC:
+               if (copy_from_user(&v, uaddr, sizeof(v)))
+                       return -EFAULT;
+
+               mutex_lock(&dev->kvm->lock);
+               r = kvm_riscv_aia_imsic_rw_attr(dev->kvm, type, false, &v);
+               mutex_unlock(&dev->kvm->lock);
+               if (r)
+                       return r;
+
+               if (copy_to_user(uaddr, &v, sizeof(v)))
+                       return -EFAULT;
+
                break;
        }
 
@@ -473,6 +496,8 @@ static int aia_has_attr(struct kvm_device *dev, struct kvm_device_attr *attr)
                break;
        case KVM_DEV_RISCV_AIA_GRP_APLIC:
                return kvm_riscv_aia_aplic_has_attr(dev->kvm, attr->attr);
+       case KVM_DEV_RISCV_AIA_GRP_IMSIC:
+               return kvm_riscv_aia_imsic_has_attr(dev->kvm, attr->attr);
        }
 
        return -ENXIO;
index 0e6ecf067757466aa03ec7002241e68bd847dfa9..a0c9dbce2b9975f5ec111f50a576d918e39f8b08 100644 (file)
@@ -278,6 +278,33 @@ static u32 imsic_mrif_topei(struct imsic_mrif *mrif, u32 nr_eix, u32 nr_msis)
        return 0;
 }
 
+static int imsic_mrif_isel_check(u32 nr_eix, unsigned long isel)
+{
+       u32 num = 0;
+
+       switch (isel) {
+       case IMSIC_EIDELIVERY:
+       case IMSIC_EITHRESHOLD:
+               break;
+       case IMSIC_EIP0 ... IMSIC_EIP63:
+               num = isel - IMSIC_EIP0;
+               break;
+       case IMSIC_EIE0 ... IMSIC_EIE63:
+               num = isel - IMSIC_EIE0;
+               break;
+       default:
+               return -ENOENT;
+       };
+#ifndef CONFIG_32BIT
+       if (num & 0x1)
+               return -EINVAL;
+#endif
+       if ((num / 2) >= nr_eix)
+               return -EINVAL;
+
+       return 0;
+}
+
 static int imsic_mrif_rmw(struct imsic_mrif *mrif, u32 nr_eix,
                          unsigned long isel, unsigned long *val,
                          unsigned long new_val, unsigned long wr_mask)
@@ -408,6 +435,86 @@ static void imsic_vsfile_read(int vsfile_hgei, int vsfile_cpu, u32 nr_eix,
                         imsic_vsfile_local_read, &idata, 1);
 }
 
+struct imsic_vsfile_rw_data {
+       int hgei;
+       int isel;
+       bool write;
+       unsigned long val;
+};
+
+static void imsic_vsfile_local_rw(void *data)
+{
+       struct imsic_vsfile_rw_data *idata = data;
+       unsigned long new_hstatus, old_hstatus, old_vsiselect;
+
+       old_vsiselect = csr_read(CSR_VSISELECT);
+       old_hstatus = csr_read(CSR_HSTATUS);
+       new_hstatus = old_hstatus & ~HSTATUS_VGEIN;
+       new_hstatus |= ((unsigned long)idata->hgei) << HSTATUS_VGEIN_SHIFT;
+       csr_write(CSR_HSTATUS, new_hstatus);
+
+       switch (idata->isel) {
+       case IMSIC_EIDELIVERY:
+               if (idata->write)
+                       imsic_vs_csr_write(IMSIC_EIDELIVERY, idata->val);
+               else
+                       idata->val = imsic_vs_csr_read(IMSIC_EIDELIVERY);
+               break;
+       case IMSIC_EITHRESHOLD:
+               if (idata->write)
+                       imsic_vs_csr_write(IMSIC_EITHRESHOLD, idata->val);
+               else
+                       idata->val = imsic_vs_csr_read(IMSIC_EITHRESHOLD);
+               break;
+       case IMSIC_EIP0 ... IMSIC_EIP63:
+       case IMSIC_EIE0 ... IMSIC_EIE63:
+#ifndef CONFIG_32BIT
+               if (idata->isel & 0x1)
+                       break;
+#endif
+               if (idata->write)
+                       imsic_eix_write(idata->isel, idata->val);
+               else
+                       idata->val = imsic_eix_read(idata->isel);
+               break;
+       default:
+               break;
+       }
+
+       csr_write(CSR_HSTATUS, old_hstatus);
+       csr_write(CSR_VSISELECT, old_vsiselect);
+}
+
+static int imsic_vsfile_rw(int vsfile_hgei, int vsfile_cpu, u32 nr_eix,
+                          unsigned long isel, bool write,
+                          unsigned long *val)
+{
+       int rc;
+       struct imsic_vsfile_rw_data rdata;
+
+       /* We can only access register if we have a IMSIC VS-file */
+       if (vsfile_cpu < 0 || vsfile_hgei <= 0)
+               return -EINVAL;
+
+       /* Check IMSIC register iselect */
+       rc = imsic_mrif_isel_check(nr_eix, isel);
+       if (rc)
+               return rc;
+
+       /* We can only access register on local CPU */
+       rdata.hgei = vsfile_hgei;
+       rdata.isel = isel;
+       rdata.write = write;
+       rdata.val = (write) ? *val : 0;
+       on_each_cpu_mask(cpumask_of(vsfile_cpu),
+                        imsic_vsfile_local_rw, &rdata, 1);
+
+       if (!write)
+               *val = rdata.val;
+
+       return 0;
+}
+
 static void imsic_vsfile_local_clear(int vsfile_hgei, u32 nr_eix)
 {
        u32 i;
@@ -759,6 +866,69 @@ int kvm_riscv_vcpu_aia_imsic_rmw(struct kvm_vcpu *vcpu, unsigned long isel,
        return rc;
 }
 
+int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type,
+                               bool write, unsigned long *val)
+{
+       u32 isel, vcpu_id;
+       unsigned long flags;
+       struct imsic *imsic;
+       struct kvm_vcpu *vcpu;
+       int rc, vsfile_hgei, vsfile_cpu;
+
+       if (!kvm_riscv_aia_initialized(kvm))
+               return -ENODEV;
+
+       vcpu_id = KVM_DEV_RISCV_AIA_IMSIC_GET_VCPU(type);
+       vcpu = kvm_get_vcpu_by_id(kvm, vcpu_id);
+       if (!vcpu)
+               return -ENODEV;
+
+       isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type);
+       imsic = vcpu->arch.aia_context.imsic_state;
+
+       read_lock_irqsave(&imsic->vsfile_lock, flags);
+
+       rc = 0;
+       vsfile_hgei = imsic->vsfile_hgei;
+       vsfile_cpu = imsic->vsfile_cpu;
+       if (vsfile_cpu < 0) {
+               if (write) {
+                       rc = imsic_mrif_rmw(imsic->swfile, imsic->nr_eix,
+                                           isel, NULL, *val, -1UL);
+                       imsic_swfile_extirq_update(vcpu);
+               } else
+                       rc = imsic_mrif_rmw(imsic->swfile, imsic->nr_eix,
+                                           isel, val, 0, 0);
+       }
+
+       read_unlock_irqrestore(&imsic->vsfile_lock, flags);
+
+       if (!rc && vsfile_cpu >= 0)
+               rc = imsic_vsfile_rw(vsfile_hgei, vsfile_cpu, imsic->nr_eix,
+                                    isel, write, val);
+
+       return rc;
+}
+
+int kvm_riscv_aia_imsic_has_attr(struct kvm *kvm, unsigned long type)
+{
+       u32 isel, vcpu_id;
+       struct imsic *imsic;
+       struct kvm_vcpu *vcpu;
+
+       if (!kvm_riscv_aia_initialized(kvm))
+               return -ENODEV;
+
+       vcpu_id = KVM_DEV_RISCV_AIA_IMSIC_GET_VCPU(type);
+       vcpu = kvm_get_vcpu_by_id(kvm, vcpu_id);
+       if (!vcpu)
+               return -ENODEV;
+
+       isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type);
+       imsic = vcpu->arch.aia_context.imsic_state;
+       return imsic_mrif_isel_check(imsic->nr_eix, isel);
+}
+
 void kvm_riscv_vcpu_aia_imsic_reset(struct kvm_vcpu *vcpu)
 {
        struct imsic *imsic = vcpu->arch.aia_context.imsic_state;