diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27f581750c6e..bcecc6224c59 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -194,6 +194,17 @@ enum vcpu_sysreg {
 	CNTP_CVAL_EL0,
 	CNTP_CTL_EL0,
 
+	/* Statistical Profiling Extension Registers. */
+	PMSCR_EL1,	/* Statistical Profiling Control Register */
+	PMSICR_EL1,	/* Sampling Interval Counter Register */
+	PMSIRR_EL1,	/* Sampling Interval Reload Register */
+	PMSFCR_EL1,	/* Sampling Filter Control Register */
+	PMSEVFR_EL1,	/* Sampling Event Filter Register */
+	PMSLATFR_EL1,	/* Sampling Latency Filter Register */
+	PMBLIMITR_EL1,	/* Profiling Buffer Limit Address Register */
+	PMBPTR_EL1,	/* Profiling Buffer Write Pointer Register */
+	PMBSR_EL1,	/* Profiling Buffer Status/syndrome Register */
+
 	/* 32bit specific registers. Keep them at the end of the range */
 	DACR32_EL2,	/* Domain Access Control Register */
 	IFSR32_EL2,	/* Instruction Fault Status Register */
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 316ff8dfed5b..0e365a51cac7 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -12,6 +12,16 @@
 
 #include <asm/kvm_mmu.h>
 
+void kvm_arm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
+{
+	__vcpu_sys_reg(vcpu, reg) = val;
+}
+
+u64 kvm_arm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
+{
+	return __vcpu_sys_reg(vcpu, reg);
+}
+
 void kvm_arm_spe_notify_vcpu_init(struct kvm_vcpu *vcpu)
 {
 	vcpu->kvm->arch.spe.finalized = false;
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index aa776c006a2a..2871484993ec 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -244,9 +244,37 @@ static bool access_vm_reg(struct kvm_vcpu *vcpu,
 	return true;
 }
 
+static bool access_spe_reg(struct kvm_vcpu *vcpu,
+			   struct sys_reg_params *p,
+			   const struct sys_reg_desc *r)
+{
+	u64 val = p->regval;
+	int reg = r->reg;
+	u32 sr = sys_reg((u32)r->Op0, (u32)r->Op1,
+			 (u32)r->CRn, (u32)r->CRm, (u32)r->Op2);
+
+	if (sr == SYS_PMSIDR_EL1) {
+		/* Ignore writes. */
+		if (!p->is_write)
+			p->regval = read_sysreg_s(SYS_PMSIDR_EL1);
+		goto out;
+	}
+
+	if (p->is_write)
+		kvm_arm_spe_write_sysreg(vcpu, reg, val);
+	else
+		p->regval = kvm_arm_spe_read_sysreg(vcpu, reg);
+
+out:
+	return true;
+}
+
 static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
 				   const struct sys_reg_desc *r)
 {
+	if (vcpu_has_spe(vcpu))
+		return 0;
+
 	return REG_HIDDEN_GUEST | REG_HIDDEN_USER;
 }
 
@@ -1598,16 +1626,16 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_FAR_EL1), access_vm_reg, reset_unknown, FAR_EL1 },
 	{ SYS_DESC(SYS_PAR_EL1), NULL, reset_unknown, PAR_EL1 },
 
-	{ SYS_DESC(SYS_PMSCR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMSICR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMSIRR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMSFCR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMSEVFR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMSLATFR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMSIDR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMBLIMITR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMBPTR_EL1), .visibility = spe_visibility },
-	{ SYS_DESC(SYS_PMBSR_EL1), .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSCR_EL1), access_spe_reg, reset_val, PMSCR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSICR_EL1), access_spe_reg, reset_val, PMSICR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSIRR_EL1), access_spe_reg, reset_val, PMSIRR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSFCR_EL1), access_spe_reg, reset_val, PMSFCR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSEVFR_EL1), access_spe_reg, reset_val, PMSEVFR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSLATFR_EL1), access_spe_reg, reset_val, PMSEVFR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMSIDR_EL1), access_spe_reg, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMBLIMITR_EL1), access_spe_reg, reset_val, PMBLIMITR_EL1, 0, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMBPTR_EL1), access_spe_reg, reset_unknown, PMBPTR_EL1, .visibility = spe_visibility },
+	{ SYS_DESC(SYS_PMBSR_EL1), access_spe_reg, reset_val, PMBSR_EL1, 0, .visibility = spe_visibility },
 
 	{ SYS_DESC(SYS_PMINTENSET_EL1), access_pminten, reset_unknown, PMINTENSET_EL1 },
 	{ SYS_DESC(SYS_PMINTENCLR_EL1), access_pminten, reset_unknown, PMINTENSET_EL1 },
diff --git a/include/kvm/arm_spe.h b/include/kvm/arm_spe.h
index 7f9f3a03aadb..a2429edc4483 100644
--- a/include/kvm/arm_spe.h
+++ b/include/kvm/arm_spe.h
@@ -38,6 +38,9 @@ int kvm_arm_vm_spe_has_attr(struct kvm *vcpu, struct kvm_device_attr *attr);
 
 void kvm_arm_spe_notify_vcpu_init(struct kvm_vcpu *vcpu);
 
+void kvm_arm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val);
+u64 kvm_arm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg);
+
 #else
 #define kvm_arm_supports_spe()	false
 
@@ -87,5 +90,11 @@ static inline int kvm_arm_vm_spe_has_attr(struct kvm *vcpu,
 
 static inline void kvm_arm_spe_notify_vcpu_init(struct kvm_vcpu *vcpu) {}
 
+static inline void kvm_arm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val) {}
+static inline u64 kvm_arm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
+{
+	return 0;
+}
+
 #endif /* CONFIG_KVM_ARM_SPE */
 #endif /* __ASM_ARM_KVM_SPE_H */
