Commit 5463091a authored by Anup Patel's avatar Anup Patel Committed by Anup Patel
Browse files

RISC-V: KVM: Expose IMSIC registers as attributes of AIA irqchip



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: default avatarAnup Patel <apatel@ventanamicro.com>
Reviewed-by: default avatarAtish Patra <atishp@rivosinc.com>
Signed-off-by: default avatarAnup Patel <anup@brainfault.org>
parent db8b7e97
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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);
+17 −0
Original line number Diff line number Diff line
@@ -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

+27 −2
Original line number Diff line number Diff line
@@ -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;
+170 −0
Original line number Diff line number Diff line
@@ -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;