Commit 031d5914 authored by Song Gao's avatar Song Gao Committed by Xianglai Li
Browse files

LoongArch: KVM: Add PMU support for guest

mainline inclusion
from mainline-v6.12-rc1
commit f4e40ea9f78fed585e953bf38575e47d24922e1a
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/IAZJDO


CVE: NA

--------------------------------

On LoongArch, the host and guest have their own PMU CSRs registers and
they share PMU hardware resources. A set of PMU CSRs consists of a CTRL
register and a CNTR register. We can set which PMU CSRs are used by the
guest by writing to the GCFG register [24:26] bits.

On KVM side:
- Save the host PMU CSRs into structure kvm_context.
- If the host supports the PMU feature.
  - When entering guest mode, save the host PMU CSRs and restore the guest PMU CSRs.
  - When exiting guest mode, save the guest PMU CSRs and restore the host PMU CSRs.

Reviewed-by: default avatarBibo Mao <maobibo@loongson.cn>
Signed-off-by: default avatarSong Gao <gaosong@loongson.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
Signed-off-by: default avatarXianglai Li <lixianglai@loongson.cn>
parent 7a6231f2
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -181,6 +181,7 @@ __BUILD_GCSR_OP(tlbidx)

#define kvm_save_hw_gcsr(csr, gid)	(csr->csrs[gid] = gcsr_read(gid))
#define kvm_restore_hw_gcsr(csr, gid)	(gcsr_write(csr->csrs[gid], gid))

#define kvm_read_clear_hw_gcsr(csr, gid)	(csr->csrs[gid] = gcsr_write(0, gid))

int kvm_emu_iocsr(larch_inst inst, struct kvm_run *run, struct kvm_vcpu *vcpu);
@@ -210,9 +211,7 @@ static __always_inline void kvm_change_sw_gcsr(struct loongarch_csrs *csr,
	csr->csrs[gid] |= val & _mask;
}

#define KVM_PMU_EVENT_ENABLED	(CSR_PERFCTRL_PLV0 |		\
					CSR_PERFCTRL_PLV1 |	\
					CSR_PERFCTRL_PLV2 |	\
					CSR_PERFCTRL_PLV3)
#define KVM_PMU_EVENT_ENABLED	(CSR_PERFCTRL_PLV0 | CSR_PERFCTRL_PLV1 | \
					CSR_PERFCTRL_PLV2 | CSR_PERFCTRL_PLV3)

#endif	/* __ASM_LOONGARCH_KVM_CSR_H__ */
+11 −10
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@
#define KVM_HALT_POLL_NS_DEFAULT	500000
#define KVM_REQ_TLB_FLUSH_GPA		KVM_ARCH_REQ(0)
#define KVM_REQ_STEAL_UPDATE		KVM_ARCH_REQ(1)
#define KVM_REQ_PMU			KVM_ARCH_REQ(2)

#define KVM_GUESTDBG_SW_BP_MASK		\
	(KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP)
@@ -90,12 +91,11 @@ struct kvm_arch_memory_slot {
	unsigned long flags;
};

#define KVM_REQ_PMU			KVM_ARCH_REQ(0)
#define HOST_MAX_PMNUM			16
struct kvm_context {
	unsigned long vpid_cache;
	struct kvm_vcpu *last_vcpu;
	/* Save host pmu csr */
	/* Host PMU CSR */
	u64 perf_ctrl[HOST_MAX_PMNUM];
	u64 perf_cntr[HOST_MAX_PMNUM];
};
@@ -171,8 +171,9 @@ enum emulation_result {
#define KVM_LARCH_LSX		(0x1 << 1)
#define KVM_LARCH_LASX		(0x1 << 2)
#define KVM_LARCH_LBT		(0x1 << 3)
#define KVM_LARCH_SWCSR_LATEST	(0x1 << 4)
#define KVM_LARCH_HWCSR_USABLE	(0x1 << 5)
#define KVM_LARCH_PMU		(0x1 << 4)
#define KVM_LARCH_SWCSR_LATEST	(0x1 << 5)
#define KVM_LARCH_HWCSR_USABLE	(0x1 << 6)

struct kvm_vcpu_arch {
	/*
@@ -282,19 +283,19 @@ static inline bool kvm_guest_has_lasx(struct kvm_vcpu_arch *arch)
	return arch->cpucfg[2] & CPUCFG2_LASX;
}

static inline bool kvm_guest_has_pmu(struct kvm_vcpu_arch *arch)
static inline bool kvm_guest_has_lbt(struct kvm_vcpu_arch *arch)
{
	return arch->cpucfg[LOONGARCH_CPUCFG6] & CPUCFG6_PMP;
	return arch->cpucfg[2] & (CPUCFG2_X86BT | CPUCFG2_ARMBT | CPUCFG2_MIPSBT);
}

static inline int kvm_get_pmu_num(struct kvm_vcpu_arch *arch)
static inline bool kvm_guest_has_pmu(struct kvm_vcpu_arch *arch)
{
	return (arch->cpucfg[LOONGARCH_CPUCFG6] & CPUCFG6_PMNUM) >> CPUCFG6_PMNUM_SHIFT;
	return arch->cpucfg[6] & CPUCFG6_PMP;
}

static inline bool kvm_guest_has_lbt(struct kvm_vcpu_arch *arch)
static inline int kvm_get_pmu_num(struct kvm_vcpu_arch *arch)
{
	return arch->cpucfg[2] & (CPUCFG2_X86BT | CPUCFG2_ARMBT | CPUCFG2_MIPSBT);
	return (arch->cpucfg[6] & CPUCFG6_PMNUM) >> CPUCFG6_PMNUM_SHIFT;
}

/* Debug: dump vcpu state */
+1 −0
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ struct kvm_fpu {
#define  KVM_LOONGARCH_VM_FEAT_X86BT	2
#define  KVM_LOONGARCH_VM_FEAT_ARMBT	3
#define  KVM_LOONGARCH_VM_FEAT_MIPSBT	4
#define  KVM_LOONGARCH_VM_FEAT_PMU	5

/* Device Control API on vcpu fd */
#define KVM_LOONGARCH_VCPU_CPUCFG	0
+90 −153
Original line number Diff line number Diff line
@@ -32,120 +32,19 @@ const struct kvm_stats_header kvm_vcpu_stats_header = {
		       sizeof(kvm_vcpu_stats_desc),
};

static void kvm_update_stolen_time(struct kvm_vcpu *vcpu)
{
	u32 version;
	u64 steal;
	gpa_t gpa;
	struct kvm_memslots *slots;
	struct kvm_steal_time __user *st;
	struct gfn_to_hva_cache *ghc;

	ghc = &vcpu->arch.st.cache;
	gpa = vcpu->arch.st.guest_addr;
	if (!(gpa & KVM_STEAL_PHYS_VALID))
		return;

	gpa &= KVM_STEAL_PHYS_MASK;
	slots = kvm_memslots(vcpu->kvm);
	if (slots->generation != ghc->generation || gpa != ghc->gpa) {
		if (kvm_gfn_to_hva_cache_init(vcpu->kvm, ghc, gpa, sizeof(*st))) {
			ghc->gpa = INVALID_GPA;
			return;
		}
	}

	st = (struct kvm_steal_time __user *)ghc->hva;
	unsafe_get_user(version, &st->version, out);
	if (version & 1)
		version += 1; /* first time write, random junk */

	version += 1;
	unsafe_put_user(version, &st->version, out);
	smp_wmb();

	unsafe_get_user(steal, &st->steal, out);
	steal += current->sched_info.run_delay - vcpu->arch.st.last_steal;
	vcpu->arch.st.last_steal = current->sched_info.run_delay;
	unsafe_put_user(steal, &st->steal, out);

	smp_wmb();
	version += 1;
	unsafe_put_user(version, &st->version, out);
out:
	mark_page_dirty_in_slot(vcpu->kvm, ghc->memslot, gpa_to_gfn(ghc->gpa));
}

static int kvm_loongarch_pvtime_set_attr(struct kvm_vcpu *vcpu,
					struct kvm_device_attr *attr)
{
	u64 __user *user = (u64 __user *)attr->addr;
	struct kvm *kvm = vcpu->kvm;
	u64 gpa;
	int ret = 0;
	int idx;

	if (!kvm_pvtime_supported() ||
			attr->attr != KVM_LOONGARCH_VCPU_PVTIME_GPA)
		return -ENXIO;

	if (get_user(gpa, user))
		return -EFAULT;

	/* Check the address is in a valid memslot */
	idx = srcu_read_lock(&kvm->srcu);
	if (kvm_is_error_hva(gfn_to_hva(kvm, gpa >> PAGE_SHIFT)))
		ret = -EINVAL;
	srcu_read_unlock(&kvm->srcu, idx);

	if (!ret)
		vcpu->arch.st.guest_addr = gpa;

	return ret;
}

static int kvm_loongarch_pvtime_get_attr(struct kvm_vcpu *vcpu,
					struct kvm_device_attr *attr)
{
	u64 __user *user = (u64 __user *)attr->addr;
	u64 gpa;

	if (!kvm_pvtime_supported() ||
			attr->attr != KVM_LOONGARCH_VCPU_PVTIME_GPA)
		return -ENXIO;

	gpa = vcpu->arch.st.guest_addr;
	if (put_user(gpa, user))
		return -EFAULT;

	return 0;
}

static int kvm_loongarch_pvtime_has_attr(struct kvm_vcpu *vcpu,
					struct kvm_device_attr *attr)
{
	switch (attr->attr) {
	case KVM_LOONGARCH_VCPU_PVTIME_GPA:
		if (kvm_pvtime_supported())
			return 0;
	}

	return -ENXIO;
}

static inline void kvm_save_host_pmu(struct kvm_vcpu *vcpu)
{
	struct kvm_context *context;

	context = this_cpu_ptr(vcpu->kvm->arch.vmcs);
	context->perf_ctrl[0] = write_csr_perfctrl0(0);
	context->perf_ctrl[1] = write_csr_perfctrl1(0);
	context->perf_ctrl[2] = write_csr_perfctrl2(0);
	context->perf_ctrl[3] = write_csr_perfctrl3(0);
	context->perf_cntr[0] = read_csr_perfcntr0();
	context->perf_cntr[1] = read_csr_perfcntr1();
	context->perf_cntr[2] = read_csr_perfcntr2();
	context->perf_cntr[3] = read_csr_perfcntr3();
	context->perf_ctrl[0] = write_csr_perfctrl0(0);
	context->perf_ctrl[1] = write_csr_perfctrl1(0);
	context->perf_ctrl[2] = write_csr_perfctrl2(0);
	context->perf_ctrl[3] = write_csr_perfctrl3(0);
}

static inline void kvm_restore_host_pmu(struct kvm_vcpu *vcpu)
@@ -168,14 +67,14 @@ static inline void kvm_save_guest_pmu(struct kvm_vcpu *vcpu)
{
	struct loongarch_csrs *csr = vcpu->arch.csr;

	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL0);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL1);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL2);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL3);
	kvm_save_hw_gcsr(csr, LOONGARCH_CSR_PERFCNTR0);
	kvm_save_hw_gcsr(csr, LOONGARCH_CSR_PERFCNTR1);
	kvm_save_hw_gcsr(csr, LOONGARCH_CSR_PERFCNTR2);
	kvm_save_hw_gcsr(csr, LOONGARCH_CSR_PERFCNTR3);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL0);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL1);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL2);
	kvm_read_clear_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL3);
}

static inline void kvm_restore_guest_pmu(struct kvm_vcpu *vcpu)
@@ -192,70 +91,109 @@ static inline void kvm_restore_guest_pmu(struct kvm_vcpu *vcpu)
	kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_PERFCTRL3);
}

static int kvm_own_pmu(struct kvm_vcpu *vcpu)
{
	unsigned long val;

	if (!kvm_guest_has_pmu(&vcpu->arch))
		return -EINVAL;

	kvm_save_host_pmu(vcpu);

	/* Set PM0-PM(num) to guest */
	val = read_csr_gcfg() & ~CSR_GCFG_GPERF;
	val |= (kvm_get_pmu_num(&vcpu->arch) + 1) << CSR_GCFG_GPERF_SHIFT;
	write_csr_gcfg(val);

	kvm_restore_guest_pmu(vcpu);

	return 0;
}

static void kvm_lose_pmu(struct kvm_vcpu *vcpu)
{
	unsigned long val;
	struct loongarch_csrs *csr = vcpu->arch.csr;

	if (!(vcpu->arch.aux_inuse & KVM_GUEST_PMU_ENABLE))
		return;
	if (!(vcpu->arch.aux_inuse & KVM_GUEST_PMU_ACTIVE))
	if (!(vcpu->arch.aux_inuse & KVM_LARCH_PMU))
		return;

	kvm_save_guest_pmu(vcpu);

	/* Disable pmu access from guest */
	write_csr_gcfg(read_csr_gcfg() & ~CSR_GCFG_GPERF);

	/*
	 * Clear KVM_GUEST_PMU_ENABLE if the guest is not using PMU CSRs
	 * when exiting the guest, so that the next time trap into the guest.
	 * we don't need to deal with PMU CSRs contexts.
	 * Clear KVM_LARCH_PMU if the guest is not using PMU CSRs when
	 * exiting the guest, so that the next time trap into the guest.
	 * We don't need to deal with PMU CSRs contexts.
	 */
	val = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL0);
	val |= kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL1);
	val |= kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL2);
	val |= kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL3);
	if (!(val & KVM_PMU_EVENT_ENABLED))
		vcpu->arch.aux_inuse &= ~KVM_GUEST_PMU_ENABLE;
	kvm_restore_host_pmu(vcpu);
		vcpu->arch.aux_inuse &= ~KVM_LARCH_PMU;

	/* KVM_GUEST_PMU_ACTIVE needs to be cleared when exiting the guest */
	vcpu->arch.aux_inuse &= ~KVM_GUEST_PMU_ACTIVE;
	kvm_restore_host_pmu(vcpu);
}

static void kvm_own_pmu(struct kvm_vcpu *vcpu)
static void kvm_restore_pmu(struct kvm_vcpu *vcpu)
{
	unsigned long val;
	if ((vcpu->arch.aux_inuse & KVM_LARCH_PMU))
		kvm_make_request(KVM_REQ_PMU, vcpu);
}

	kvm_save_host_pmu(vcpu);
	/* Set PM0-PM(num) to guest */
	val = read_csr_gcfg() & ~CSR_GCFG_GPERF;
	val |= (kvm_get_pmu_num(&vcpu->arch) + 1) << CSR_GCFG_GPERF_SHIFT;
	write_csr_gcfg(val);
	kvm_restore_guest_pmu(vcpu);
static void kvm_check_pmu(struct kvm_vcpu *vcpu)
{
	if (kvm_check_request(KVM_REQ_PMU, vcpu)) {
		kvm_own_pmu(vcpu);
		vcpu->arch.aux_inuse |= KVM_LARCH_PMU;
	}
}

static void kvm_restore_pmu(struct kvm_vcpu *vcpu)
static void kvm_update_stolen_time(struct kvm_vcpu *vcpu)
{
	if (!(vcpu->arch.aux_inuse & KVM_GUEST_PMU_ENABLE))
	u32 version;
	u64 steal;
	gpa_t gpa;
	struct kvm_memslots *slots;
	struct kvm_steal_time __user *st;
	struct gfn_to_hva_cache *ghc;

	ghc = &vcpu->arch.st.cache;
	gpa = vcpu->arch.st.guest_addr;
	if (!(gpa & KVM_STEAL_PHYS_VALID))
		return;

	kvm_make_request(KVM_REQ_PMU, vcpu);
	gpa &= KVM_STEAL_PHYS_MASK;
	slots = kvm_memslots(vcpu->kvm);
	if (slots->generation != ghc->generation || gpa != ghc->gpa) {
		if (kvm_gfn_to_hva_cache_init(vcpu->kvm, ghc, gpa, sizeof(*st))) {
			ghc->gpa = INVALID_GPA;
			return;
		}
	}

static void kvm_check_pmu(struct kvm_vcpu *vcpu)
{
	if (!kvm_check_request(KVM_REQ_PMU, vcpu))
		return;
	st = (struct kvm_steal_time __user *)ghc->hva;
	unsafe_get_user(version, &st->version, out);
	if (version & 1)
		version += 1; /* first time write, random junk */

	kvm_own_pmu(vcpu);
	version += 1;
	unsafe_put_user(version, &st->version, out);
	smp_wmb();

	/*
	 * Set KVM_GUEST PMU_ENABLE and GUEST_PMU_ACTIVE
	 * when guest has KVM_REQ_PMU request.
	 */
	vcpu->arch.aux_inuse |= KVM_GUEST_PMU_ENABLE;
	vcpu->arch.aux_inuse |= KVM_GUEST_PMU_ACTIVE;
	unsafe_get_user(steal, &st->steal, out);
	steal += current->sched_info.run_delay - vcpu->arch.st.last_steal;
	vcpu->arch.st.last_steal = current->sched_info.run_delay;
	unsafe_put_user(steal, &st->steal, out);

	smp_wmb();
	version += 1;
	unsafe_put_user(version, &st->version, out);
out:
	mark_page_dirty_in_slot(vcpu->kvm, ghc->memslot, gpa_to_gfn(ghc->gpa));
}

/*
@@ -662,10 +600,11 @@ static int _kvm_setcsr(struct kvm_vcpu *vcpu, unsigned int id, u64 val)
	if (id >= LOONGARCH_CSR_PERFCTRL0 && id <= LOONGARCH_CSR_PERFCNTR3) {
		unsigned long val;

		val = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL0);
		val |= kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL1);
		val |= kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL2);
		val |= kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL3);
		val = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL0) |
		      kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL1) |
		      kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL2) |
		      kvm_read_sw_gcsr(csr, LOONGARCH_CSR_PERFCTRL3);

		if (val & KVM_PMU_EVENT_ENABLED)
			kvm_make_request(KVM_REQ_PMU, vcpu);
	}
@@ -738,7 +677,7 @@ static int _kvm_get_cpucfg_mask(int id, u64 *v)

static int kvm_check_cpucfg(int id, u64 val)
{
	int ret, host;
	int ret;
	u64 mask = 0;

	ret = _kvm_get_cpucfg_mask(id, &mask);
@@ -766,9 +705,8 @@ static int kvm_check_cpucfg(int id, u64 val)
		return 0;
	case LOONGARCH_CPUCFG6:
		if (val & CPUCFG6_PMP) {
			host = read_cpucfg(LOONGARCH_CPUCFG6);
			u32 host = read_cpucfg(LOONGARCH_CPUCFG6);
			if ((val & CPUCFG6_PMBITS) != (host & CPUCFG6_PMBITS))
				/* Guest pmbits must be the same with host */
				return -EINVAL;
			if ((val & CPUCFG6_PMNUM) > (host & CPUCFG6_PMNUM))
				return -EINVAL;
@@ -889,10 +827,9 @@ static int kvm_set_one_reg(struct kvm_vcpu *vcpu,
		if (ret)
			break;
		vcpu->arch.cpucfg[id] = (u32)v;
		if (id == LOONGARCH_CPUCFG6) {
			vcpu->arch.max_pmu_csrid = LOONGARCH_CSR_PERFCTRL0 +
							2 * kvm_get_pmu_num(&vcpu->arch) + 1;
		}
		if (id == LOONGARCH_CPUCFG6)
			vcpu->arch.max_pmu_csrid =
				LOONGARCH_CSR_PERFCTRL0 + 2 * kvm_get_pmu_num(&vcpu->arch) + 1;
		break;
	case KVM_REG_LOONGARCH_LBT:
		if (!kvm_guest_has_lbt(&vcpu->arch))
+4 −0
Original line number Diff line number Diff line
@@ -127,6 +127,10 @@ static int kvm_vm_feature_has_attr(struct kvm *kvm, struct kvm_device_attr *attr
		if (cpu_has_lbt_mips)
			return 0;
		return -ENXIO;
	case KVM_LOONGARCH_VM_FEAT_PMU:
		if (cpu_has_pmp)
			return 0;
		return -ENXIO;
	default:
		return -ENXIO;
	}