Merge branch 'work.file' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-microblaze.git] / arch / arm64 / kvm / vgic / vgic-mmio-v3.c
index 2f1b156..a09cdc0 100644 (file)
@@ -251,30 +251,35 @@ static void vgic_mmio_write_v3r_ctlr(struct kvm_vcpu *vcpu,
                vgic_enable_lpis(vcpu);
 }
 
-static unsigned long vgic_mmio_read_v3r_typer(struct kvm_vcpu *vcpu,
-                                             gpa_t addr, unsigned int len)
+static bool vgic_mmio_vcpu_rdist_is_last(struct kvm_vcpu *vcpu)
 {
-       unsigned long mpidr = kvm_vcpu_get_mpidr_aff(vcpu);
+       struct vgic_dist *vgic = &vcpu->kvm->arch.vgic;
        struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
-       struct vgic_redist_region *rdreg = vgic_cpu->rdreg;
-       int target_vcpu_id = vcpu->vcpu_id;
-       gpa_t last_rdist_typer = rdreg->base + GICR_TYPER +
-                       (rdreg->free_index - 1) * KVM_VGIC_V3_REDIST_SIZE;
-       u64 value;
+       struct vgic_redist_region *iter, *rdreg = vgic_cpu->rdreg;
 
-       value = (u64)(mpidr & GENMASK(23, 0)) << 32;
-       value |= ((target_vcpu_id & 0xffff) << 8);
+       if (!rdreg)
+               return false;
 
-       if (addr == last_rdist_typer)
-               value |= GICR_TYPER_LAST;
-       if (vgic_has_its(vcpu->kvm))
-               value |= GICR_TYPER_PLPIS;
+       if (vgic_cpu->rdreg_index < rdreg->free_index - 1) {
+               return false;
+       } else if (rdreg->count && vgic_cpu->rdreg_index == (rdreg->count - 1)) {
+               struct list_head *rd_regions = &vgic->rd_regions;
+               gpa_t end = rdreg->base + rdreg->count * KVM_VGIC_V3_REDIST_SIZE;
 
-       return extract_bytes(value, addr & 7, len);
+               /*
+                * the rdist is the last one of the redist region,
+                * check whether there is no other contiguous rdist region
+                */
+               list_for_each_entry(iter, rd_regions, list) {
+                       if (iter->base == end && iter->free_index > 0)
+                               return false;
+               }
+       }
+       return true;
 }
 
-static unsigned long vgic_uaccess_read_v3r_typer(struct kvm_vcpu *vcpu,
-                                                gpa_t addr, unsigned int len)
+static unsigned long vgic_mmio_read_v3r_typer(struct kvm_vcpu *vcpu,
+                                             gpa_t addr, unsigned int len)
 {
        unsigned long mpidr = kvm_vcpu_get_mpidr_aff(vcpu);
        int target_vcpu_id = vcpu->vcpu_id;
@@ -286,7 +291,9 @@ static unsigned long vgic_uaccess_read_v3r_typer(struct kvm_vcpu *vcpu,
        if (vgic_has_its(vcpu->kvm))
                value |= GICR_TYPER_PLPIS;
 
-       /* reporting of the Last bit is not supported for userspace */
+       if (vgic_mmio_vcpu_rdist_is_last(vcpu))
+               value |= GICR_TYPER_LAST;
+
        return extract_bytes(value, addr & 7, len);
 }
 
@@ -612,7 +619,7 @@ static const struct vgic_register_region vgic_v3_rd_registers[] = {
                VGIC_ACCESS_32bit),
        REGISTER_DESC_WITH_LENGTH_UACCESS(GICR_TYPER,
                vgic_mmio_read_v3r_typer, vgic_mmio_write_wi,
-               vgic_uaccess_read_v3r_typer, vgic_mmio_uaccess_write_wi, 8,
+               NULL, vgic_mmio_uaccess_write_wi, 8,
                VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
        REGISTER_DESC_WITH_LENGTH(GICR_WAKER,
                vgic_mmio_read_raz, vgic_mmio_write_wi, 4,
@@ -714,6 +721,7 @@ int vgic_register_redist_iodev(struct kvm_vcpu *vcpu)
                return -EINVAL;
 
        vgic_cpu->rdreg = rdreg;
+       vgic_cpu->rdreg_index = rdreg->free_index;
 
        rd_base = rdreg->base + rdreg->free_index * KVM_VGIC_V3_REDIST_SIZE;
 
@@ -768,7 +776,7 @@ static int vgic_register_all_redist_iodevs(struct kvm *kvm)
 }
 
 /**
- * vgic_v3_insert_redist_region - Insert a new redistributor region
+ * vgic_v3_alloc_redist_region - Allocate a new redistributor region
  *
  * Performs various checks before inserting the rdist region in the list.
  * Those tests depend on whether the size of the rdist region is known
@@ -782,8 +790,8 @@ static int vgic_register_all_redist_iodevs(struct kvm *kvm)
  *
  * Return 0 on success, < 0 otherwise
  */
-static int vgic_v3_insert_redist_region(struct kvm *kvm, uint32_t index,
-                                       gpa_t base, uint32_t count)
+static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,
+                                      gpa_t base, uint32_t count)
 {
        struct vgic_dist *d = &kvm->arch.vgic;
        struct vgic_redist_region *rdreg;
@@ -791,10 +799,6 @@ static int vgic_v3_insert_redist_region(struct kvm *kvm, uint32_t index,
        size_t size = count * KVM_VGIC_V3_REDIST_SIZE;
        int ret;
 
-       /* single rdist region already set ?*/
-       if (!count && !list_empty(rd_regions))
-               return -EINVAL;
-
        /* cross the end of memory ? */
        if (base + size < base)
                return -EINVAL;
@@ -805,11 +809,15 @@ static int vgic_v3_insert_redist_region(struct kvm *kvm, uint32_t index,
        } else {
                rdreg = list_last_entry(rd_regions,
                                        struct vgic_redist_region, list);
-               if (index != rdreg->index + 1)
+
+               /* Don't mix single region and discrete redist regions */
+               if (!count && rdreg->count)
                        return -EINVAL;
 
-               /* Cannot add an explicitly sized regions after legacy region */
-               if (!rdreg->count)
+               if (!count)
+                       return -EEXIST;
+
+               if (index != rdreg->index + 1)
                        return -EINVAL;
        }
 
@@ -848,11 +856,17 @@ free:
        return ret;
 }
 
+void vgic_v3_free_redist_region(struct vgic_redist_region *rdreg)
+{
+       list_del(&rdreg->list);
+       kfree(rdreg);
+}
+
 int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
 {
        int ret;
 
-       ret = vgic_v3_insert_redist_region(kvm, index, addr, count);
+       ret = vgic_v3_alloc_redist_region(kvm, index, addr, count);
        if (ret)
                return ret;
 
@@ -861,8 +875,13 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
         * afterwards will register the iodevs when needed.
         */
        ret = vgic_register_all_redist_iodevs(kvm);
-       if (ret)
+       if (ret) {
+               struct vgic_redist_region *rdreg;
+
+               rdreg = vgic_v3_rdist_region_from_index(kvm, index);
+               vgic_v3_free_redist_region(rdreg);
                return ret;
+       }
 
        return 0;
 }