diff mbox

[v2,13/25] KVM: arm64: vgic-v3: Add ICV_BPR0_EL1 handler

Message ID 20170601102117.17750-14-marc.zyngier@arm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Marc Zyngier June 1, 2017, 10:21 a.m. UTC
Add a handler for reading/writing the guest's view of the ICC_BPR0_EL1
register, which is located in the ICH_VMCR_EL2.BPR0 field.

Reviewed-by: Eric Auger <eric.auger@redhat.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
---
 arch/arm64/include/asm/sysreg.h |  1 +
 virt/kvm/arm/hyp/vgic-v3-sr.c   | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

Comments

Christoffer Dall June 6, 2017, 12:11 p.m. UTC | #1
On Thu, Jun 01, 2017 at 11:21:05AM +0100, Marc Zyngier wrote:
> Add a handler for reading/writing the guest's view of the ICC_BPR0_EL1
> register, which is located in the ICH_VMCR_EL2.BPR0 field.
> 
> Reviewed-by: Eric Auger <eric.auger@redhat.com>
> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
> ---
>  arch/arm64/include/asm/sysreg.h |  1 +
>  virt/kvm/arm/hyp/vgic-v3-sr.c   | 36 ++++++++++++++++++++++++++++++++++++
>  2 files changed, 37 insertions(+)
> 
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index bd000686194a..d20be0b28ca4 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h
> @@ -180,6 +180,7 @@
>  
>  #define SYS_VBAR_EL1			sys_reg(3, 0, 12, 0, 0)
>  
> +#define SYS_ICC_BPR0_EL1		sys_reg(3, 0, 12, 8, 3)
>  #define SYS_ICC_AP1Rn_EL1(n)		sys_reg(3, 0, 12, 9, n)
>  #define SYS_ICC_DIR_EL1			sys_reg(3, 0, 12, 11, 1)
>  #define SYS_ICC_SGI1R_EL1		sys_reg(3, 0, 12, 11, 5)
> diff --git a/virt/kvm/arm/hyp/vgic-v3-sr.c b/virt/kvm/arm/hyp/vgic-v3-sr.c
> index 42ac9ee7650a..54a8e828c85b 100644
> --- a/virt/kvm/arm/hyp/vgic-v3-sr.c
> +++ b/virt/kvm/arm/hyp/vgic-v3-sr.c
> @@ -688,11 +688,41 @@ static void __hyp_text __vgic_v3_write_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr,
>  	__vgic_v3_write_vmcr(vmcr);
>  }
>  
> +static void __hyp_text __vgic_v3_read_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> +{
> +	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr0(vmcr));
> +}
> +
>  static void __hyp_text __vgic_v3_read_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  {
>  	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr1(vmcr));
>  }
>  
> +static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> +{
> +	u64 val = vcpu_get_reg(vcpu, rt);
> +	u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
> +
> +	/* Enforce BPR limiting */
> +	if (val < bpr_min)
> +		val = bpr_min;
> +
> +	val <<= ICH_VMCR_BPR0_SHIFT;
> +	val &= ICH_VMCR_BPR0_MASK;
> +	vmcr &= ~ICH_VMCR_BPR0_MASK;
> +	vmcr |= val;
> +
> +	if (vmcr & ICH_VMCR_CBPR_MASK) {
> +		val = __vgic_v3_get_bpr1(vmcr);
> +		val <<= ICH_VMCR_BPR1_SHIFT;
> +		val &= ICH_VMCR_BPR1_MASK;
> +		vmcr &= ~ICH_VMCR_BPR1_MASK;
> +		vmcr |= val;
> +	}

I don't understand why this block is needed?

Thanks,
-Christoffer

> +
> +	__vgic_v3_write_vmcr(vmcr);
> +}
> +
>  static void __hyp_text __vgic_v3_write_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  {
>  	u64 val = vcpu_get_reg(vcpu, rt);
> @@ -870,6 +900,12 @@ int __hyp_text __vgic_v3_perform_cpuif_access(struct kvm_vcpu *vcpu)
>  	case SYS_ICC_HPPIR1_EL1:
>  		fn = __vgic_v3_read_hppir;
>  		break;
> +	case SYS_ICC_BPR0_EL1:
> +		if (is_read)
> +			fn = __vgic_v3_read_bpr0;
> +		else
> +			fn = __vgic_v3_write_bpr0;
> +		break;
>  	default:
>  		return 0;
>  	}
> -- 
> 2.11.0
>
Marc Zyngier June 6, 2017, 3:15 p.m. UTC | #2
On 06/06/17 13:11, Christoffer Dall wrote:
> On Thu, Jun 01, 2017 at 11:21:05AM +0100, Marc Zyngier wrote:
>> Add a handler for reading/writing the guest's view of the ICC_BPR0_EL1
>> register, which is located in the ICH_VMCR_EL2.BPR0 field.
>>
>> Reviewed-by: Eric Auger <eric.auger@redhat.com>
>> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
>> ---
>>  arch/arm64/include/asm/sysreg.h |  1 +
>>  virt/kvm/arm/hyp/vgic-v3-sr.c   | 36 ++++++++++++++++++++++++++++++++++++
>>  2 files changed, 37 insertions(+)
>>
>> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
>> index bd000686194a..d20be0b28ca4 100644
>> --- a/arch/arm64/include/asm/sysreg.h
>> +++ b/arch/arm64/include/asm/sysreg.h
>> @@ -180,6 +180,7 @@
>>  
>>  #define SYS_VBAR_EL1			sys_reg(3, 0, 12, 0, 0)
>>  
>> +#define SYS_ICC_BPR0_EL1		sys_reg(3, 0, 12, 8, 3)
>>  #define SYS_ICC_AP1Rn_EL1(n)		sys_reg(3, 0, 12, 9, n)
>>  #define SYS_ICC_DIR_EL1			sys_reg(3, 0, 12, 11, 1)
>>  #define SYS_ICC_SGI1R_EL1		sys_reg(3, 0, 12, 11, 5)
>> diff --git a/virt/kvm/arm/hyp/vgic-v3-sr.c b/virt/kvm/arm/hyp/vgic-v3-sr.c
>> index 42ac9ee7650a..54a8e828c85b 100644
>> --- a/virt/kvm/arm/hyp/vgic-v3-sr.c
>> +++ b/virt/kvm/arm/hyp/vgic-v3-sr.c
>> @@ -688,11 +688,41 @@ static void __hyp_text __vgic_v3_write_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr,
>>  	__vgic_v3_write_vmcr(vmcr);
>>  }
>>  
>> +static void __hyp_text __vgic_v3_read_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>> +{
>> +	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr0(vmcr));
>> +}
>> +
>>  static void __hyp_text __vgic_v3_read_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>>  {
>>  	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr1(vmcr));
>>  }
>>  
>> +static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>> +{
>> +	u64 val = vcpu_get_reg(vcpu, rt);
>> +	u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
>> +
>> +	/* Enforce BPR limiting */
>> +	if (val < bpr_min)
>> +		val = bpr_min;
>> +
>> +	val <<= ICH_VMCR_BPR0_SHIFT;
>> +	val &= ICH_VMCR_BPR0_MASK;
>> +	vmcr &= ~ICH_VMCR_BPR0_MASK;
>> +	vmcr |= val;
>> +
>> +	if (vmcr & ICH_VMCR_CBPR_MASK) {
>> +		val = __vgic_v3_get_bpr1(vmcr);
>> +		val <<= ICH_VMCR_BPR1_SHIFT;
>> +		val &= ICH_VMCR_BPR1_MASK;
>> +		vmcr &= ~ICH_VMCR_BPR1_MASK;
>> +		vmcr |= val;
>> +	}
> 
> I don't understand why this block is needed?

If you have CBPR already set, and then update BPR0, you need to make
sure that BPR1 gets updated as well. You could hope that the HW would do
it for you, but since we're erratum workaround land...

Thanks,

	M.
Christoffer Dall June 6, 2017, 3:46 p.m. UTC | #3
On Tue, Jun 06, 2017 at 04:15:05PM +0100, Marc Zyngier wrote:
> On 06/06/17 13:11, Christoffer Dall wrote:
> > On Thu, Jun 01, 2017 at 11:21:05AM +0100, Marc Zyngier wrote:
> >> Add a handler for reading/writing the guest's view of the ICC_BPR0_EL1
> >> register, which is located in the ICH_VMCR_EL2.BPR0 field.
> >>
> >> Reviewed-by: Eric Auger <eric.auger@redhat.com>
> >> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
> >> ---
> >>  arch/arm64/include/asm/sysreg.h |  1 +
> >>  virt/kvm/arm/hyp/vgic-v3-sr.c   | 36 ++++++++++++++++++++++++++++++++++++
> >>  2 files changed, 37 insertions(+)
> >>
> >> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> >> index bd000686194a..d20be0b28ca4 100644
> >> --- a/arch/arm64/include/asm/sysreg.h
> >> +++ b/arch/arm64/include/asm/sysreg.h
> >> @@ -180,6 +180,7 @@
> >>  
> >>  #define SYS_VBAR_EL1			sys_reg(3, 0, 12, 0, 0)
> >>  
> >> +#define SYS_ICC_BPR0_EL1		sys_reg(3, 0, 12, 8, 3)
> >>  #define SYS_ICC_AP1Rn_EL1(n)		sys_reg(3, 0, 12, 9, n)
> >>  #define SYS_ICC_DIR_EL1			sys_reg(3, 0, 12, 11, 1)
> >>  #define SYS_ICC_SGI1R_EL1		sys_reg(3, 0, 12, 11, 5)
> >> diff --git a/virt/kvm/arm/hyp/vgic-v3-sr.c b/virt/kvm/arm/hyp/vgic-v3-sr.c
> >> index 42ac9ee7650a..54a8e828c85b 100644
> >> --- a/virt/kvm/arm/hyp/vgic-v3-sr.c
> >> +++ b/virt/kvm/arm/hyp/vgic-v3-sr.c
> >> @@ -688,11 +688,41 @@ static void __hyp_text __vgic_v3_write_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr,
> >>  	__vgic_v3_write_vmcr(vmcr);
> >>  }
> >>  
> >> +static void __hyp_text __vgic_v3_read_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> >> +{
> >> +	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr0(vmcr));
> >> +}
> >> +
> >>  static void __hyp_text __vgic_v3_read_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> >>  {
> >>  	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr1(vmcr));
> >>  }
> >>  
> >> +static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> >> +{
> >> +	u64 val = vcpu_get_reg(vcpu, rt);
> >> +	u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
> >> +
> >> +	/* Enforce BPR limiting */
> >> +	if (val < bpr_min)
> >> +		val = bpr_min;
> >> +
> >> +	val <<= ICH_VMCR_BPR0_SHIFT;
> >> +	val &= ICH_VMCR_BPR0_MASK;
> >> +	vmcr &= ~ICH_VMCR_BPR0_MASK;
> >> +	vmcr |= val;
> >> +
> >> +	if (vmcr & ICH_VMCR_CBPR_MASK) {
> >> +		val = __vgic_v3_get_bpr1(vmcr);
> >> +		val <<= ICH_VMCR_BPR1_SHIFT;
> >> +		val &= ICH_VMCR_BPR1_MASK;
> >> +		vmcr &= ~ICH_VMCR_BPR1_MASK;
> >> +		vmcr |= val;
> >> +	}
> > 
> > I don't understand why this block is needed?
> 
> If you have CBPR already set, and then update BPR0, you need to make
> sure that BPR1 gets updated as well. You could hope that the HW would do
> it for you, but since we're erratum workaround land...
> 

I just didn't read the spec that way, I gathered that the hardware would
maintain read-as-written for for bpr1 but use bpr0 to set the binary
point when cbpr is set, and just ignore writes to bpr1 for as long as
cbpr is set.

In any case, probably doesn't matter, but I was just curious if the spec
dictateted this behavior and if we should reference that part of the
spec in a comment then.

Thanks,
-Christoffer
Peter Maydell June 6, 2017, 3:56 p.m. UTC | #4
On 6 June 2017 at 16:46, Christoffer Dall <cdall@linaro.org> wrote:
> On Tue, Jun 06, 2017 at 04:15:05PM +0100, Marc Zyngier wrote:
>> >> +static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>> >> +{
>> >> +  u64 val = vcpu_get_reg(vcpu, rt);
>> >> +  u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
>> >> +
>> >> +  /* Enforce BPR limiting */
>> >> +  if (val < bpr_min)
>> >> +          val = bpr_min;
>> >> +
>> >> +  val <<= ICH_VMCR_BPR0_SHIFT;
>> >> +  val &= ICH_VMCR_BPR0_MASK;
>> >> +  vmcr &= ~ICH_VMCR_BPR0_MASK;
>> >> +  vmcr |= val;
>> >> +
>> >> +  if (vmcr & ICH_VMCR_CBPR_MASK) {
>> >> +          val = __vgic_v3_get_bpr1(vmcr);
>> >> +          val <<= ICH_VMCR_BPR1_SHIFT;
>> >> +          val &= ICH_VMCR_BPR1_MASK;
>> >> +          vmcr &= ~ICH_VMCR_BPR1_MASK;
>> >> +          vmcr |= val;
>> >> +  }
>> >
>> > I don't understand why this block is needed?
>>
>> If you have CBPR already set, and then update BPR0, you need to make
>> sure that BPR1 gets updated as well. You could hope that the HW would do
>> it for you, but since we're erratum workaround land...
>>
>
> I just didn't read the spec that way, I gathered that the hardware would
> maintain read-as-written for for bpr1 but use bpr0 to set the binary
> point when cbpr is set, and just ignore writes to bpr1 for as long as
> cbpr is set.

This depends whether you're talking about the ICC/ICV BPR0/BPR1 registers,
or the fields in the ICH_VMCR*. For the former, if CBPR is set then
BPR1 reads and writes affect BPR0. For the latter, the two fields
are both independent and read-as-written regardless of the value
of CBPR, it's just that the value in the BPR1 field has no effect.
(The reason for this is for VM state migration: if a guest does:
  - write X to BPR0
  - write Y to BPR1
  - set CBPR
  - write Z to BPR0
  - unset CBPR
  - read BPR1
it should get back Y still; so EL2 needs to have a way to see
both the underlying BPR0 and BPR1 values even if CPBR is in effect.)

So the code above is not correct, I think, because it's making
writes to BPR0 affect the BPR1 value. Instead what should happen
is that when we emulate reads and writes to ICC_BPR1 we should pay
attention to CBPR and work with either the VMCR BPR0 or BPR1 field
appropriately.

thanks
-- PMM
Marc Zyngier June 6, 2017, 4:56 p.m. UTC | #5
On 06/06/17 16:56, Peter Maydell wrote:
> On 6 June 2017 at 16:46, Christoffer Dall <cdall@linaro.org> wrote:
>> On Tue, Jun 06, 2017 at 04:15:05PM +0100, Marc Zyngier wrote:
>>>>> +static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>>>>> +{
>>>>> +  u64 val = vcpu_get_reg(vcpu, rt);
>>>>> +  u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
>>>>> +
>>>>> +  /* Enforce BPR limiting */
>>>>> +  if (val < bpr_min)
>>>>> +          val = bpr_min;
>>>>> +
>>>>> +  val <<= ICH_VMCR_BPR0_SHIFT;
>>>>> +  val &= ICH_VMCR_BPR0_MASK;
>>>>> +  vmcr &= ~ICH_VMCR_BPR0_MASK;
>>>>> +  vmcr |= val;
>>>>> +
>>>>> +  if (vmcr & ICH_VMCR_CBPR_MASK) {
>>>>> +          val = __vgic_v3_get_bpr1(vmcr);
>>>>> +          val <<= ICH_VMCR_BPR1_SHIFT;
>>>>> +          val &= ICH_VMCR_BPR1_MASK;
>>>>> +          vmcr &= ~ICH_VMCR_BPR1_MASK;
>>>>> +          vmcr |= val;
>>>>> +  }
>>>>
>>>> I don't understand why this block is needed?
>>>
>>> If you have CBPR already set, and then update BPR0, you need to make
>>> sure that BPR1 gets updated as well. You could hope that the HW would do
>>> it for you, but since we're erratum workaround land...
>>>
>>
>> I just didn't read the spec that way, I gathered that the hardware would
>> maintain read-as-written for for bpr1 but use bpr0 to set the binary
>> point when cbpr is set, and just ignore writes to bpr1 for as long as
>> cbpr is set.
> 
> This depends whether you're talking about the ICC/ICV BPR0/BPR1 registers,
> or the fields in the ICH_VMCR*. For the former, if CBPR is set then
> BPR1 reads and writes affect BPR0. For the latter, the two fields
> are both independent and read-as-written regardless of the value
> of CBPR, it's just that the value in the BPR1 field has no effect.
> (The reason for this is for VM state migration: if a guest does:
>   - write X to BPR0
>   - write Y to BPR1
>   - set CBPR
>   - write Z to BPR0
>   - unset CBPR
>   - read BPR1
> it should get back Y still; so EL2 needs to have a way to see
> both the underlying BPR0 and BPR1 values even if CPBR is in effect.)
> 
> So the code above is not correct, I think, because it's making
> writes to BPR0 affect the BPR1 value. Instead what should happen
> is that when we emulate reads and writes to ICC_BPR1 we should pay
> attention to CBPR and work with either the VMCR BPR0 or BPR1 field
> appropriately.

The BPR1 accessors are already behaving that way (write ignored and read
as BPR0+1 if CBPR is set). But I didn't realize that you could mess with
CBPR and BPR0, and yet have ICH_VMCR_EL2.VBPR1 be unaffected.

I'll drop that hunk.

Thanks,

	M.
Christoffer Dall June 6, 2017, 5:23 p.m. UTC | #6
On Tue, Jun 06, 2017 at 04:56:27PM +0100, Peter Maydell wrote:
> On 6 June 2017 at 16:46, Christoffer Dall <cdall@linaro.org> wrote:
> > On Tue, Jun 06, 2017 at 04:15:05PM +0100, Marc Zyngier wrote:
> >> >> +static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> >> >> +{
> >> >> +  u64 val = vcpu_get_reg(vcpu, rt);
> >> >> +  u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
> >> >> +
> >> >> +  /* Enforce BPR limiting */
> >> >> +  if (val < bpr_min)
> >> >> +          val = bpr_min;
> >> >> +
> >> >> +  val <<= ICH_VMCR_BPR0_SHIFT;
> >> >> +  val &= ICH_VMCR_BPR0_MASK;
> >> >> +  vmcr &= ~ICH_VMCR_BPR0_MASK;
> >> >> +  vmcr |= val;
> >> >> +
> >> >> +  if (vmcr & ICH_VMCR_CBPR_MASK) {
> >> >> +          val = __vgic_v3_get_bpr1(vmcr);
> >> >> +          val <<= ICH_VMCR_BPR1_SHIFT;
> >> >> +          val &= ICH_VMCR_BPR1_MASK;
> >> >> +          vmcr &= ~ICH_VMCR_BPR1_MASK;
> >> >> +          vmcr |= val;
> >> >> +  }
> >> >
> >> > I don't understand why this block is needed?
> >>
> >> If you have CBPR already set, and then update BPR0, you need to make
> >> sure that BPR1 gets updated as well. You could hope that the HW would do
> >> it for you, but since we're erratum workaround land...
> >>
> >
> > I just didn't read the spec that way, I gathered that the hardware would
> > maintain read-as-written for for bpr1 but use bpr0 to set the binary
> > point when cbpr is set, and just ignore writes to bpr1 for as long as
> > cbpr is set.
> 
> This depends whether you're talking about the ICC/ICV BPR0/BPR1 registers,
> or the fields in the ICH_VMCR*. For the former, if CBPR is set then
> BPR1 reads and writes affect BPR0. 

From the spec on ICV_BPR1_EL1: "Non-secure EL1 writes are ignored".
Doesn't that mean that reads of BPR1 reflect BPR0 but writes are
ignored?


> For the latter, the two fields
> are both independent and read-as-written regardless of the value
> of CBPR, it's just that the value in the BPR1 field has no effect.
> (The reason for this is for VM state migration: if a guest does:
>   - write X to BPR0
>   - write Y to BPR1
>   - set CBPR
>   - write Z to BPR0
>   - unset CBPR
>   - read BPR1
> it should get back Y still; so EL2 needs to have a way to see
> both the underlying BPR0 and BPR1 values even if CPBR is in effect.)
> 
> So the code above is not correct, I think, because it's making
> writes to BPR0 affect the BPR1 value. Instead what should happen
> is that when we emulate reads and writes to ICC_BPR1 we should pay
> attention to CBPR and work with either the VMCR BPR0 or BPR1 field
> appropriately.

I agree that we should drop the block above, but also think we should do
what we do in patch #5 and ignore writes to BPR1 if CBPR is set.

Thanks,
-Christoffer
Peter Maydell June 6, 2017, 5:36 p.m. UTC | #7
On 6 June 2017 at 18:23, Christoffer Dall <cdall@linaro.org> wrote:
> On Tue, Jun 06, 2017 at 04:56:27PM +0100, Peter Maydell wrote:
>> This depends whether you're talking about the ICC/ICV BPR0/BPR1 registers,
>> or the fields in the ICH_VMCR*. For the former, if CBPR is set then
>> BPR1 reads and writes affect BPR0.
>
> From the spec on ICV_BPR1_EL1: "Non-secure EL1 writes are ignored".
> Doesn't that mean that reads of BPR1 reflect BPR0 but writes are
> ignored?

Yes, you're right.

thanks
-- PMM
diff mbox

Patch

diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index bd000686194a..d20be0b28ca4 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -180,6 +180,7 @@ 
 
 #define SYS_VBAR_EL1			sys_reg(3, 0, 12, 0, 0)
 
+#define SYS_ICC_BPR0_EL1		sys_reg(3, 0, 12, 8, 3)
 #define SYS_ICC_AP1Rn_EL1(n)		sys_reg(3, 0, 12, 9, n)
 #define SYS_ICC_DIR_EL1			sys_reg(3, 0, 12, 11, 1)
 #define SYS_ICC_SGI1R_EL1		sys_reg(3, 0, 12, 11, 5)
diff --git a/virt/kvm/arm/hyp/vgic-v3-sr.c b/virt/kvm/arm/hyp/vgic-v3-sr.c
index 42ac9ee7650a..54a8e828c85b 100644
--- a/virt/kvm/arm/hyp/vgic-v3-sr.c
+++ b/virt/kvm/arm/hyp/vgic-v3-sr.c
@@ -688,11 +688,41 @@  static void __hyp_text __vgic_v3_write_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr,
 	__vgic_v3_write_vmcr(vmcr);
 }
 
+static void __hyp_text __vgic_v3_read_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
+{
+	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr0(vmcr));
+}
+
 static void __hyp_text __vgic_v3_read_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 {
 	vcpu_set_reg(vcpu, rt, __vgic_v3_get_bpr1(vmcr));
 }
 
+static void __hyp_text __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
+{
+	u64 val = vcpu_get_reg(vcpu, rt);
+	u8 bpr_min = 7 - vtr_to_nr_pre_bits(read_gicreg(ICH_VTR_EL2));
+
+	/* Enforce BPR limiting */
+	if (val < bpr_min)
+		val = bpr_min;
+
+	val <<= ICH_VMCR_BPR0_SHIFT;
+	val &= ICH_VMCR_BPR0_MASK;
+	vmcr &= ~ICH_VMCR_BPR0_MASK;
+	vmcr |= val;
+
+	if (vmcr & ICH_VMCR_CBPR_MASK) {
+		val = __vgic_v3_get_bpr1(vmcr);
+		val <<= ICH_VMCR_BPR1_SHIFT;
+		val &= ICH_VMCR_BPR1_MASK;
+		vmcr &= ~ICH_VMCR_BPR1_MASK;
+		vmcr |= val;
+	}
+
+	__vgic_v3_write_vmcr(vmcr);
+}
+
 static void __hyp_text __vgic_v3_write_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 {
 	u64 val = vcpu_get_reg(vcpu, rt);
@@ -870,6 +900,12 @@  int __hyp_text __vgic_v3_perform_cpuif_access(struct kvm_vcpu *vcpu)
 	case SYS_ICC_HPPIR1_EL1:
 		fn = __vgic_v3_read_hppir;
 		break;
+	case SYS_ICC_BPR0_EL1:
+		if (is_read)
+			fn = __vgic_v3_read_bpr0;
+		else
+			fn = __vgic_v3_write_bpr0;
+		break;
 	default:
 		return 0;
 	}