diff mbox

[1/3] KVM: arm64: Implement vGICv3 distributor and redistributor access from userspace

Message ID cb311e13e56311c535362f32177313850c51af66.1440766141.git.p.fedin@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Pavel Fedin Aug. 28, 2015, 12:56 p.m. UTC
The access is done similar to GICv2, using KVM_DEV_ARM_VGIC_GRP_DIST_REGS
and KVM_DEV_ARM_VGIC_GRP_REDIST_REGS with KVM_SET_DEVICE_ATTR and
KVM_GET_DEVICE_ATTR ioctls.

Registers are always assumed to be of their native size, 4 or 8 bytes.

Signed-off-by: Pavel Fedin <p.fedin@samsung.com>
---
 arch/arm64/include/uapi/asm/kvm.h |   1 +
 virt/kvm/arm/vgic-v3-emul.c       | 186 +++++++++++++++++++++++++++++++++++---
 2 files changed, 172 insertions(+), 15 deletions(-)

Comments

Christoffer Dall Aug. 30, 2015, 4:42 p.m. UTC | #1
On Fri, Aug 28, 2015 at 03:56:10PM +0300, Pavel Fedin wrote:
> The access is done similar to GICv2, using KVM_DEV_ARM_VGIC_GRP_DIST_REGS
> and KVM_DEV_ARM_VGIC_GRP_REDIST_REGS with KVM_SET_DEVICE_ATTR and
> KVM_GET_DEVICE_ATTR ioctls.
> 
> Registers are always assumed to be of their native size, 4 or 8 bytes.
> 
> Signed-off-by: Pavel Fedin <p.fedin@samsung.com>
> ---
>  arch/arm64/include/uapi/asm/kvm.h |   1 +
>  virt/kvm/arm/vgic-v3-emul.c       | 186 +++++++++++++++++++++++++++++++++++---
>  2 files changed, 172 insertions(+), 15 deletions(-)
> 
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 0cd7b59..2936651 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -203,6 +203,7 @@ struct kvm_arch_memory_slot {
>  #define KVM_DEV_ARM_VGIC_GRP_NR_IRQS	3
>  #define KVM_DEV_ARM_VGIC_GRP_CTRL	4
>  #define   KVM_DEV_ARM_VGIC_CTRL_INIT	0
> +#define KVM_DEV_ARM_VGIC_GRP_REDIST_REGS 5
>  
>  /* KVM_IRQ_LINE irq field index values */
>  #define KVM_ARM_IRQ_TYPE_SHIFT		24
> diff --git a/virt/kvm/arm/vgic-v3-emul.c b/virt/kvm/arm/vgic-v3-emul.c
> index e661e7f..b3847e1 100644
> --- a/virt/kvm/arm/vgic-v3-emul.c
> +++ b/virt/kvm/arm/vgic-v3-emul.c
> @@ -39,6 +39,7 @@
>  #include <linux/kvm.h>
>  #include <linux/kvm_host.h>
>  #include <linux/interrupt.h>
> +#include <linux/uaccess.h>
>  
>  #include <linux/irqchip/arm-gic-v3.h>
>  #include <kvm/arm_vgic.h>
> @@ -990,6 +991,107 @@ void vgic_v3_dispatch_sgi(struct kvm_vcpu *vcpu, u64 reg)
>  		vgic_kick_vcpus(vcpu->kvm);
>  }
>  
> +static int vgic_v3_attr_regs_access(struct kvm_device *dev,
> +				 struct kvm_device_attr *attr,
> +				 void *reg, u32 len, bool is_write)

using a void pointer for the register with variable length here is
likely to cause endianness headaches.  Can we use a typed pointer here?

> +{
> +	const struct vgic_io_range *r = NULL, *ranges;
> +	phys_addr_t offset;
> +	int ret, cpuid, c;
> +	struct kvm_vcpu *vcpu, *tmp_vcpu;
> +	struct vgic_dist *vgic;
> +	struct kvm_exit_mmio mmio;
> +	u64 data;
> +
> +	offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
> +	cpuid = (attr->attr & KVM_DEV_ARM_VGIC_CPUID_MASK) >>
> +		KVM_DEV_ARM_VGIC_CPUID_SHIFT;
> +
> +	mutex_lock(&dev->kvm->lock);
> +
> +	ret = vgic_init(dev->kvm);
> +	if (ret)
> +		goto out;
> +
> +	if (cpuid >= atomic_read(&dev->kvm->online_vcpus)) {
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	vcpu = kvm_get_vcpu(dev->kvm, cpuid);
> +	vgic = &dev->kvm->arch.vgic;
> +
> +	mmio.len = len;
> +	mmio.is_write = is_write;
> +	mmio.data = &data;
> +	if (is_write) {
> +		if (len == 8)
> +			data = cpu_to_le64(*((u64 *)reg));
> +		else
> +			mmio_data_write(&mmio, ~0, *((u32 *)reg));
> +	}
> +	switch (attr->group) {
> +	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
> +		mmio.phys_addr = vgic->vgic_dist_base + offset;
> +		ranges = vgic_v3_dist_ranges;
> +		break;
> +	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
> +		mmio.phys_addr = vgic->vgic_redist_base + offset;
> +		ranges = vgic_redist_ranges;
> +		break;
> +	default:
> +		BUG();
> +	}
> +	r = vgic_find_range(ranges, 4, offset);
> +
> +	if (unlikely(!r || !r->handle_mmio)) {
> +		ret = -ENXIO;
> +		goto out;
> +	}
> +
> +
> +	spin_lock(&vgic->lock);
> +
> +	/*
> +	 * Ensure that no other VCPU is running by checking the vcpu->cpu
> +	 * field.  If no other VPCUs are running we can safely access the VGIC
> +	 * state, because even if another VPU is run after this point, that
> +	 * VCPU will not touch the vgic state, because it will block on
> +	 * getting the vgic->lock in kvm_vgic_sync_hwstate().
> +	 */
> +	kvm_for_each_vcpu(c, tmp_vcpu, dev->kvm) {
> +		if (unlikely(tmp_vcpu->cpu != -1)) {
> +			ret = -EBUSY;
> +			goto out_vgic_unlock;
> +		}
> +	}
> +
> +	/*
> +	 * Move all pending IRQs from the LRs on all VCPUs so the pending
> +	 * state can be properly represented in the register state accessible
> +	 * through this API.
> +	 */
> +	kvm_for_each_vcpu(c, tmp_vcpu, dev->kvm)
> +		vgic_unqueue_irqs(tmp_vcpu);
> +
> +	offset -= r->base;
> +	r->handle_mmio(vcpu, &mmio, offset);
> +
> +	if (!is_write) {
> +		if (len == 8)
> +			*(u64 *)reg = le64_to_cpu(data);
> +		else
> +			*(u32 *)reg = mmio_data_read(&mmio, ~0);
> +	}
> +
> +	ret = 0;
> +out_vgic_unlock:
> +	spin_unlock(&vgic->lock);
> +out:
> +	mutex_unlock(&dev->kvm->lock);
> +	return ret;

I feel like there's a lot of reused code with the v2 vgic here.  Can you
look at reusing some of the logic?

> +}
> +
>  static int vgic_v3_create(struct kvm_device *dev, u32 type)
>  {
>  	return kvm_vgic_create(dev->kvm, type);
> @@ -1000,40 +1102,95 @@ static void vgic_v3_destroy(struct kvm_device *dev)
>  	kfree(dev);
>  }
>  
> +static u32 vgic_v3_get_reg_size(struct kvm_device_attr *attr)
> +{
> +	u32 offset;
> +
> +	switch (attr->group) {
> +	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
> +		offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
> +		if (offset >= GICD_IROUTER && offset <= 0x7FD8)

eh, 0x7FD8 ?

> +			return 8;
> +		else
> +			return 4;
> +		break;
> +
> +	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
> +		offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
> +		if ((offset == GICR_TYPER) ||
> +		    (offset >= GICR_SETLPIR && offset <= GICR_INVALLR))
> +			return 8;
> +		else
> +			return 4;
> +		break;
> +
> +	default:
> +		return -ENXIO;
> +	}
> +}

this feels wrong.

How about encoding the userspace requested access size in the reserved
bits of the attr field similarly to how the register indicies for the
SET_ONE/GET_ONE ioctls work and then you can enforce specific access
length restrictions in the individual register access functions.

> +
>  static int vgic_v3_set_attr(struct kvm_device *dev,
>  			    struct kvm_device_attr *attr)
>  {
> -	int ret;
> +	int ret, len;
> +	u64 reg64;
> +	u32 reg;
> +	void *data;
>  
>  	ret = vgic_set_common_attr(dev, attr);
>  	if (ret != -ENXIO)
>  		return ret;
>  
> -	switch (attr->group) {
> -	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
> -	case KVM_DEV_ARM_VGIC_GRP_CPU_REGS:
> -		return -ENXIO;
> +	len = vgic_v3_get_reg_size(attr);
> +	if (len < 0)
> +		return len;
> +
> +	if (len == 8) {
> +		u64 __user *uaddr = (u64 __user *)(long)attr->addr;
> +
> +		ret = get_user(reg64, uaddr);
> +		data = &reg64;
> +	} else {
> +		u32 __user *uaddr = (u32 __user *)(long)attr->addr;
> +
> +		ret = get_user(reg, uaddr);
> +		data = &reg;
>  	}
> +	if (ret)
> +		return -EFAULT;
>  
> -	return -ENXIO;
> +	return vgic_v3_attr_regs_access(dev, attr, data, len, true);
>  }
>  
>  static int vgic_v3_get_attr(struct kvm_device *dev,
>  			    struct kvm_device_attr *attr)
>  {
> -	int ret;
> +	int ret, len;
> +	u64 reg64;
> +	u32 reg;
>  
>  	ret = vgic_get_common_attr(dev, attr);
>  	if (ret != -ENXIO)
>  		return ret;
>  
> -	switch (attr->group) {
> -	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
> -	case KVM_DEV_ARM_VGIC_GRP_CPU_REGS:
> -		return -ENXIO;
> -	}
> +	len = vgic_v3_get_reg_size(attr);
> +	if (len < 0)
> +		return len;
>  
> -	return -ENXIO;
> +	ret = vgic_v3_attr_regs_access(dev, attr, (len == 8) ? (void *)&reg64 :
> +					(void *)&reg, len, false);

this use of the ternary operator is terrible, but it should be solved if
you always use a u64 for the reg parameter.

> +	if (ret)
> +		return ret;
> +
> +	if (len == 8) {
> +		u64 __user *uaddr = (u64 __user *)(long)attr->addr;
> +
> +		return put_user(reg64, uaddr);
> +	} else {
> +		u32 __user *uaddr = (u32 __user *)(long)attr->addr;
> +
> +		return put_user(reg, uaddr);
> +	}
>  }
>  
>  static int vgic_v3_has_attr(struct kvm_device *dev,
> @@ -1051,8 +1208,7 @@ static int vgic_v3_has_attr(struct kvm_device *dev,
>  		}
>  		break;
>  	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
> -	case KVM_DEV_ARM_VGIC_GRP_CPU_REGS:
> -		return -ENXIO;
> +	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
>  	case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
>  		return 0;
>  	case KVM_DEV_ARM_VGIC_GRP_CTRL:
> -- 
> 2.4.4
> 
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pavel Fedin Aug. 31, 2015, 7:35 a.m. UTC | #2
Hello!

> > +	len = vgic_v3_get_reg_size(attr);
> > +	if (len < 0)
> > +		return len;
> >
> > -	return -ENXIO;
> > +	ret = vgic_v3_attr_regs_access(dev, attr, (len == 8) ? (void *)&reg64 :
> > +					(void *)&reg, len, false);
> 
> this use of the ternary operator is terrible, but it should be solved if
> you always use a u64 for the reg parameter.

 I also dislike this, but this is the best thing i could invent. This is dictated by put_user() and
get_user(), which rely on typeof() of their arguments. Well, i could do some castings, but they are
no less ugly, and would give more headache to bigendian systems.
However, what about doing the same thing as GET/SET_ONE_REG does by just assuming that everything is
64-bit wide? This would automatically resolve two other issues you have commented on. By the way,
handling it in userspace would also be simpler.

Kind regards,
Pavel Fedin
Expert Engineer
Samsung Electronics Research center Russia


--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christoffer Dall Aug. 31, 2015, 8:59 a.m. UTC | #3
On Mon, Aug 31, 2015 at 10:35:05AM +0300, Pavel Fedin wrote:
>  Hello!
> 
> > > +	len = vgic_v3_get_reg_size(attr);
> > > +	if (len < 0)
> > > +		return len;
> > >
> > > -	return -ENXIO;
> > > +	ret = vgic_v3_attr_regs_access(dev, attr, (len == 8) ? (void *)&reg64 :
> > > +					(void *)&reg, len, false);
> > 
> > this use of the ternary operator is terrible, but it should be solved if
> > you always use a u64 for the reg parameter.
> 
>  I also dislike this, but this is the best thing i could invent. This is dictated by put_user() and
> get_user(), which rely on typeof() of their arguments. Well, i could do some castings, but they are
> no less ugly, and would give more headache to bigendian systems.
> However, what about doing the same thing as GET/SET_ONE_REG does by just assuming that everything is
> 64-bit wide? This would automatically resolve two other issues you have commented on. By the way,
> handling it in userspace would also be simpler.
> 
Sounds fine to me, definitely if you must do a cast doing it in a
function between typed variables is strictly preferred to passing void *
values between functions.

-Christoffer
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andre Przywara Sept. 1, 2015, 1:52 p.m. UTC | #4
Hi Pavel,

...

>> diff --git a/virt/kvm/arm/vgic-v3-emul.c b/virt/kvm/arm/vgic-v3-emul.c
>> index e661e7f..b3847e1 100644
>> --- a/virt/kvm/arm/vgic-v3-emul.c
>> +++ b/virt/kvm/arm/vgic-v3-emul.c
...
>> @@ -1000,40 +1102,95 @@ static void vgic_v3_destroy(struct kvm_device *dev)
>>  	kfree(dev);
>>  }
>>  
>> +static u32 vgic_v3_get_reg_size(struct kvm_device_attr *attr)
>> +{
>> +	u32 offset;
>> +
>> +	switch (attr->group) {
>> +	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
>> +		offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
>> +		if (offset >= GICD_IROUTER && offset <= 0x7FD8)
> 
> eh, 0x7FD8 ?
> 
>> +			return 8;
>> +		else
>> +			return 4;
>> +		break;
>> +
>> +	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
>> +		offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
>> +		if ((offset == GICR_TYPER) ||
>> +		    (offset >= GICR_SETLPIR && offset <= GICR_INVALLR))
>> +			return 8;
>> +		else
>> +			return 4;
>> +		break;
>> +
>> +	default:
>> +		return -ENXIO;
>> +	}
>> +}
> 
> this feels wrong.

I agree on this, actually I consider this dangerous. Currently the
memory behind addr in QEMU (hw/intc/arm_gic_kvm.c:kvm_arm_gic_get() for
instance) is only uint32_t, so you have to take care to provide uint64_t
backing for those registers, which means that there must be a match
between the register size the kernel knows and the size userland thinks
of. So I'd rather see the access size controlled by userland, probably
using Christoffer's suggestion below.

Also the GIC specification says that everything must be accessible with
32-bit accesses. Correct me if I am wrong on this, but vCPUs are not
supposed to run while you are getting/setting VGIC registers, right? So
there shouldn't be any issues with non-atomic accesses to 64-bit
registers, which means you could just go ahead and do everything in
32-bit only. This would also help with supporting 32-bit userland and/or
kernel later.

Cheers,
Andre.
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pavel Fedin Sept. 1, 2015, 2:27 p.m. UTC | #5
Hello!

> I agree on this, actually I consider this dangerous. Currently the
> memory behind addr in QEMU (hw/intc/arm_gic_kvm.c:kvm_arm_gic_get() for
> instance) is only uint32_t, so you have to take care to provide uint64_t
> backing for those registers, which means that there must be a match
> between the register size the kernel knows and the size userland thinks
> of. So I'd rather see the access size controlled by userland

 Ok, i will implement it this way.

> Also the GIC specification says that everything must be accessible with
> 32-bit accesses. Correct me if I am wrong on this, but vCPUs are not
> supposed to run while you are getting/setting VGIC registers, right?

 Right.

> So there shouldn't be any issues with non-atomic accesses to 64-bit
> registers, which means you could just go ahead and do everything in
> 32-bit only.

 I thought about it too, it's inconvenient. In the userland you would have to do two accesses and
merge the result. It's just tedious. After all this API is not emulating guest behavior, it's just
for reading/writing GIC state.
 So on next respin i'll add size bit.

Kind regards,
Pavel Fedin
Expert Engineer
Samsung Electronics Research center Russia

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Peter Maydell Sept. 1, 2015, 2:46 p.m. UTC | #6
On 1 September 2015 at 14:52, Andre Przywara <andre.przywara@arm.com> wrote:
> Also the GIC specification says that everything must be accessible with
> 32-bit accesses. Correct me if I am wrong on this, but vCPUs are not
> supposed to run while you are getting/setting VGIC registers, right? So
> there shouldn't be any issues with non-atomic accesses to 64-bit
> registers, which means you could just go ahead and do everything in
> 32-bit only. This would also help with supporting 32-bit userland and/or
> kernel later.

We should design the userspace API based on the natural size
of the registers in the GICv3 spec, not on what happens to
be convenient for the kernel to implement. There's only one
kernel but there can be multiple userspace consumers of the API...

I don't see any reason why a 32-bit userland wouldn't be able
to handle 64-bit accesses via the KVM_SET/GET_DEVICE_ATTR
ioctls, or am I missing something?

thanks
-- PMM
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 0cd7b59..2936651 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -203,6 +203,7 @@  struct kvm_arch_memory_slot {
 #define KVM_DEV_ARM_VGIC_GRP_NR_IRQS	3
 #define KVM_DEV_ARM_VGIC_GRP_CTRL	4
 #define   KVM_DEV_ARM_VGIC_CTRL_INIT	0
+#define KVM_DEV_ARM_VGIC_GRP_REDIST_REGS 5
 
 /* KVM_IRQ_LINE irq field index values */
 #define KVM_ARM_IRQ_TYPE_SHIFT		24
diff --git a/virt/kvm/arm/vgic-v3-emul.c b/virt/kvm/arm/vgic-v3-emul.c
index e661e7f..b3847e1 100644
--- a/virt/kvm/arm/vgic-v3-emul.c
+++ b/virt/kvm/arm/vgic-v3-emul.c
@@ -39,6 +39,7 @@ 
 #include <linux/kvm.h>
 #include <linux/kvm_host.h>
 #include <linux/interrupt.h>
+#include <linux/uaccess.h>
 
 #include <linux/irqchip/arm-gic-v3.h>
 #include <kvm/arm_vgic.h>
@@ -990,6 +991,107 @@  void vgic_v3_dispatch_sgi(struct kvm_vcpu *vcpu, u64 reg)
 		vgic_kick_vcpus(vcpu->kvm);
 }
 
+static int vgic_v3_attr_regs_access(struct kvm_device *dev,
+				 struct kvm_device_attr *attr,
+				 void *reg, u32 len, bool is_write)
+{
+	const struct vgic_io_range *r = NULL, *ranges;
+	phys_addr_t offset;
+	int ret, cpuid, c;
+	struct kvm_vcpu *vcpu, *tmp_vcpu;
+	struct vgic_dist *vgic;
+	struct kvm_exit_mmio mmio;
+	u64 data;
+
+	offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
+	cpuid = (attr->attr & KVM_DEV_ARM_VGIC_CPUID_MASK) >>
+		KVM_DEV_ARM_VGIC_CPUID_SHIFT;
+
+	mutex_lock(&dev->kvm->lock);
+
+	ret = vgic_init(dev->kvm);
+	if (ret)
+		goto out;
+
+	if (cpuid >= atomic_read(&dev->kvm->online_vcpus)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	vcpu = kvm_get_vcpu(dev->kvm, cpuid);
+	vgic = &dev->kvm->arch.vgic;
+
+	mmio.len = len;
+	mmio.is_write = is_write;
+	mmio.data = &data;
+	if (is_write) {
+		if (len == 8)
+			data = cpu_to_le64(*((u64 *)reg));
+		else
+			mmio_data_write(&mmio, ~0, *((u32 *)reg));
+	}
+	switch (attr->group) {
+	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
+		mmio.phys_addr = vgic->vgic_dist_base + offset;
+		ranges = vgic_v3_dist_ranges;
+		break;
+	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
+		mmio.phys_addr = vgic->vgic_redist_base + offset;
+		ranges = vgic_redist_ranges;
+		break;
+	default:
+		BUG();
+	}
+	r = vgic_find_range(ranges, 4, offset);
+
+	if (unlikely(!r || !r->handle_mmio)) {
+		ret = -ENXIO;
+		goto out;
+	}
+
+
+	spin_lock(&vgic->lock);
+
+	/*
+	 * Ensure that no other VCPU is running by checking the vcpu->cpu
+	 * field.  If no other VPCUs are running we can safely access the VGIC
+	 * state, because even if another VPU is run after this point, that
+	 * VCPU will not touch the vgic state, because it will block on
+	 * getting the vgic->lock in kvm_vgic_sync_hwstate().
+	 */
+	kvm_for_each_vcpu(c, tmp_vcpu, dev->kvm) {
+		if (unlikely(tmp_vcpu->cpu != -1)) {
+			ret = -EBUSY;
+			goto out_vgic_unlock;
+		}
+	}
+
+	/*
+	 * Move all pending IRQs from the LRs on all VCPUs so the pending
+	 * state can be properly represented in the register state accessible
+	 * through this API.
+	 */
+	kvm_for_each_vcpu(c, tmp_vcpu, dev->kvm)
+		vgic_unqueue_irqs(tmp_vcpu);
+
+	offset -= r->base;
+	r->handle_mmio(vcpu, &mmio, offset);
+
+	if (!is_write) {
+		if (len == 8)
+			*(u64 *)reg = le64_to_cpu(data);
+		else
+			*(u32 *)reg = mmio_data_read(&mmio, ~0);
+	}
+
+	ret = 0;
+out_vgic_unlock:
+	spin_unlock(&vgic->lock);
+out:
+	mutex_unlock(&dev->kvm->lock);
+	return ret;
+}
+
 static int vgic_v3_create(struct kvm_device *dev, u32 type)
 {
 	return kvm_vgic_create(dev->kvm, type);
@@ -1000,40 +1102,95 @@  static void vgic_v3_destroy(struct kvm_device *dev)
 	kfree(dev);
 }
 
+static u32 vgic_v3_get_reg_size(struct kvm_device_attr *attr)
+{
+	u32 offset;
+
+	switch (attr->group) {
+	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
+		offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
+		if (offset >= GICD_IROUTER && offset <= 0x7FD8)
+			return 8;
+		else
+			return 4;
+		break;
+
+	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
+		offset = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
+		if ((offset == GICR_TYPER) ||
+		    (offset >= GICR_SETLPIR && offset <= GICR_INVALLR))
+			return 8;
+		else
+			return 4;
+		break;
+
+	default:
+		return -ENXIO;
+	}
+}
+
 static int vgic_v3_set_attr(struct kvm_device *dev,
 			    struct kvm_device_attr *attr)
 {
-	int ret;
+	int ret, len;
+	u64 reg64;
+	u32 reg;
+	void *data;
 
 	ret = vgic_set_common_attr(dev, attr);
 	if (ret != -ENXIO)
 		return ret;
 
-	switch (attr->group) {
-	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
-	case KVM_DEV_ARM_VGIC_GRP_CPU_REGS:
-		return -ENXIO;
+	len = vgic_v3_get_reg_size(attr);
+	if (len < 0)
+		return len;
+
+	if (len == 8) {
+		u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+
+		ret = get_user(reg64, uaddr);
+		data = &reg64;
+	} else {
+		u32 __user *uaddr = (u32 __user *)(long)attr->addr;
+
+		ret = get_user(reg, uaddr);
+		data = &reg;
 	}
+	if (ret)
+		return -EFAULT;
 
-	return -ENXIO;
+	return vgic_v3_attr_regs_access(dev, attr, data, len, true);
 }
 
 static int vgic_v3_get_attr(struct kvm_device *dev,
 			    struct kvm_device_attr *attr)
 {
-	int ret;
+	int ret, len;
+	u64 reg64;
+	u32 reg;
 
 	ret = vgic_get_common_attr(dev, attr);
 	if (ret != -ENXIO)
 		return ret;
 
-	switch (attr->group) {
-	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
-	case KVM_DEV_ARM_VGIC_GRP_CPU_REGS:
-		return -ENXIO;
-	}
+	len = vgic_v3_get_reg_size(attr);
+	if (len < 0)
+		return len;
 
-	return -ENXIO;
+	ret = vgic_v3_attr_regs_access(dev, attr, (len == 8) ? (void *)&reg64 :
+					(void *)&reg, len, false);
+	if (ret)
+		return ret;
+
+	if (len == 8) {
+		u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+
+		return put_user(reg64, uaddr);
+	} else {
+		u32 __user *uaddr = (u32 __user *)(long)attr->addr;
+
+		return put_user(reg, uaddr);
+	}
 }
 
 static int vgic_v3_has_attr(struct kvm_device *dev,
@@ -1051,8 +1208,7 @@  static int vgic_v3_has_attr(struct kvm_device *dev,
 		}
 		break;
 	case KVM_DEV_ARM_VGIC_GRP_DIST_REGS:
-	case KVM_DEV_ARM_VGIC_GRP_CPU_REGS:
-		return -ENXIO;
+	case KVM_DEV_ARM_VGIC_GRP_REDIST_REGS:
 	case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
 		return 0;
 	case KVM_DEV_ARM_VGIC_GRP_CTRL: