KVM: arm64: Fix HYP idmap unmap when using 52bit PA
[linux-2.6-microblaze.git] / virt / kvm / arm / mmu.c
index f8eaf86..f7fe724 100644 (file)
@@ -479,7 +479,13 @@ static void unmap_hyp_puds(pgd_t *pgd, phys_addr_t addr, phys_addr_t end)
                clear_hyp_pgd_entry(pgd);
 }
 
-static void unmap_hyp_range(pgd_t *pgdp, phys_addr_t start, u64 size)
+static unsigned int kvm_pgd_index(unsigned long addr, unsigned int ptrs_per_pgd)
+{
+       return (addr >> PGDIR_SHIFT) & (ptrs_per_pgd - 1);
+}
+
+static void __unmap_hyp_range(pgd_t *pgdp, unsigned long ptrs_per_pgd,
+                             phys_addr_t start, u64 size)
 {
        pgd_t *pgd;
        phys_addr_t addr = start, end = start + size;
@@ -489,7 +495,7 @@ static void unmap_hyp_range(pgd_t *pgdp, phys_addr_t start, u64 size)
         * We don't unmap anything from HYP, except at the hyp tear down.
         * Hence, we don't have to invalidate the TLBs here.
         */
-       pgd = pgdp + pgd_index(addr);
+       pgd = pgdp + kvm_pgd_index(addr, ptrs_per_pgd);
        do {
                next = pgd_addr_end(addr, end);
                if (!pgd_none(*pgd))
@@ -497,6 +503,16 @@ static void unmap_hyp_range(pgd_t *pgdp, phys_addr_t start, u64 size)
        } while (pgd++, addr = next, addr != end);
 }
 
+static void unmap_hyp_range(pgd_t *pgdp, phys_addr_t start, u64 size)
+{
+       __unmap_hyp_range(pgdp, PTRS_PER_PGD, start, size);
+}
+
+static void unmap_hyp_idmap_range(pgd_t *pgdp, phys_addr_t start, u64 size)
+{
+       __unmap_hyp_range(pgdp, __kvm_idmap_ptrs_per_pgd(), start, size);
+}
+
 /**
  * free_hyp_pgds - free Hyp-mode page tables
  *
@@ -512,13 +528,13 @@ void free_hyp_pgds(void)
        mutex_lock(&kvm_hyp_pgd_mutex);
 
        if (boot_hyp_pgd) {
-               unmap_hyp_range(boot_hyp_pgd, hyp_idmap_start, PAGE_SIZE);
+               unmap_hyp_idmap_range(boot_hyp_pgd, hyp_idmap_start, PAGE_SIZE);
                free_pages((unsigned long)boot_hyp_pgd, hyp_pgd_order);
                boot_hyp_pgd = NULL;
        }
 
        if (hyp_pgd) {
-               unmap_hyp_range(hyp_pgd, hyp_idmap_start, PAGE_SIZE);
+               unmap_hyp_idmap_range(hyp_pgd, hyp_idmap_start, PAGE_SIZE);
                unmap_hyp_range(hyp_pgd, kern_hyp_va(PAGE_OFFSET),
                                (uintptr_t)high_memory - PAGE_OFFSET);
                unmap_hyp_range(hyp_pgd, kern_hyp_va(VMALLOC_START),
@@ -634,7 +650,7 @@ static int __create_hyp_mappings(pgd_t *pgdp, unsigned long ptrs_per_pgd,
        addr = start & PAGE_MASK;
        end = PAGE_ALIGN(end);
        do {
-               pgd = pgdp + ((addr >> PGDIR_SHIFT) & (ptrs_per_pgd - 1));
+               pgd = pgdp + kvm_pgd_index(addr, ptrs_per_pgd);
 
                if (pgd_none(*pgd)) {
                        pud = pud_alloc_one(NULL, addr);
@@ -709,28 +725,42 @@ int create_hyp_mappings(void *from, void *to, pgprot_t prot)
 }
 
 /**
- * create_hyp_io_mappings - duplicate a kernel IO mapping into Hyp mode
- * @from:      The kernel start VA of the range
- * @to:                The kernel end VA of the range (exclusive)
+ * create_hyp_io_mappings - Map IO into both kernel and HYP
  * @phys_addr: The physical start address which gets mapped
- *
- * The resulting HYP VA is the same as the kernel VA, modulo
- * HYP_PAGE_OFFSET.
+ * @size:      Size of the region being mapped
+ * @kaddr:     Kernel VA for this mapping
+ * @haddr:     HYP VA for this mapping
  */
-int create_hyp_io_mappings(void *from, void *to, phys_addr_t phys_addr)
+int create_hyp_io_mappings(phys_addr_t phys_addr, size_t size,
+                          void __iomem **kaddr,
+                          void __iomem **haddr)
 {
-       unsigned long start = kern_hyp_va((unsigned long)from);
-       unsigned long end = kern_hyp_va((unsigned long)to);
+       unsigned long start, end;
+       int ret;
 
-       if (is_kernel_in_hyp_mode())
+       *kaddr = ioremap(phys_addr, size);
+       if (!*kaddr)
+               return -ENOMEM;
+
+       if (is_kernel_in_hyp_mode()) {
+               *haddr = *kaddr;
                return 0;
+       }
 
-       /* Check for a valid kernel IO mapping */
-       if (!is_vmalloc_addr(from) || !is_vmalloc_addr(to - 1))
-               return -EINVAL;
 
-       return __create_hyp_mappings(hyp_pgd, PTRS_PER_PGD, start, end,
+       start = kern_hyp_va((unsigned long)*kaddr);
+       end = kern_hyp_va((unsigned long)*kaddr + size);
+       ret = __create_hyp_mappings(hyp_pgd, PTRS_PER_PGD, start, end,
                                     __phys_to_pfn(phys_addr), PAGE_HYP_DEVICE);
+
+       if (ret) {
+               iounmap(*kaddr);
+               *kaddr = NULL;
+               return ret;
+       }
+
+       *haddr = (void __iomem *)start;
+       return 0;
 }
 
 /**
@@ -924,6 +954,25 @@ static int stage2_set_pmd_huge(struct kvm *kvm, struct kvm_mmu_memory_cache
        return 0;
 }
 
+static bool stage2_is_exec(struct kvm *kvm, phys_addr_t addr)
+{
+       pmd_t *pmdp;
+       pte_t *ptep;
+
+       pmdp = stage2_get_pmd(kvm, NULL, addr);
+       if (!pmdp || pmd_none(*pmdp) || !pmd_present(*pmdp))
+               return false;
+
+       if (pmd_thp_or_huge(*pmdp))
+               return kvm_s2pmd_exec(pmdp);
+
+       ptep = pte_offset_kernel(pmdp, addr);
+       if (!ptep || pte_none(*ptep) || !pte_present(*ptep))
+               return false;
+
+       return kvm_s2pte_exec(ptep);
+}
+
 static int stage2_set_pte(struct kvm *kvm, struct kvm_mmu_memory_cache *cache,
                          phys_addr_t addr, const pte_t *new_pte,
                          unsigned long flags)
@@ -1255,10 +1304,14 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
        kvm_mmu_write_protect_pt_masked(kvm, slot, gfn_offset, mask);
 }
 
-static void coherent_cache_guest_page(struct kvm_vcpu *vcpu, kvm_pfn_t pfn,
-                                     unsigned long size)
+static void clean_dcache_guest_page(kvm_pfn_t pfn, unsigned long size)
 {
-       __coherent_cache_guest_page(vcpu, pfn, size);
+       __clean_dcache_guest_page(pfn, size);
+}
+
+static void invalidate_icache_guest_page(kvm_pfn_t pfn, unsigned long size)
+{
+       __invalidate_icache_guest_page(pfn, size);
 }
 
 static void kvm_send_hwpoison_signal(unsigned long address,
@@ -1284,7 +1337,7 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
                          unsigned long fault_status)
 {
        int ret;
-       bool write_fault, writable, hugetlb = false, force_pte = false;
+       bool write_fault, exec_fault, writable, hugetlb = false, force_pte = false;
        unsigned long mmu_seq;
        gfn_t gfn = fault_ipa >> PAGE_SHIFT;
        struct kvm *kvm = vcpu->kvm;
@@ -1296,7 +1349,10 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
        unsigned long flags = 0;
 
        write_fault = kvm_is_write_fault(vcpu);
-       if (fault_status == FSC_PERM && !write_fault) {
+       exec_fault = kvm_vcpu_trap_is_iabt(vcpu);
+       VM_BUG_ON(write_fault && exec_fault);
+
+       if (fault_status == FSC_PERM && !write_fault && !exec_fault) {
                kvm_err("Unexpected L2 read permission error\n");
                return -EFAULT;
        }
@@ -1389,7 +1445,19 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
                        new_pmd = kvm_s2pmd_mkwrite(new_pmd);
                        kvm_set_pfn_dirty(pfn);
                }
-               coherent_cache_guest_page(vcpu, pfn, PMD_SIZE);
+
+               if (fault_status != FSC_PERM)
+                       clean_dcache_guest_page(pfn, PMD_SIZE);
+
+               if (exec_fault) {
+                       new_pmd = kvm_s2pmd_mkexec(new_pmd);
+                       invalidate_icache_guest_page(pfn, PMD_SIZE);
+               } else if (fault_status == FSC_PERM) {
+                       /* Preserve execute if XN was already cleared */
+                       if (stage2_is_exec(kvm, fault_ipa))
+                               new_pmd = kvm_s2pmd_mkexec(new_pmd);
+               }
+
                ret = stage2_set_pmd_huge(kvm, memcache, fault_ipa, &new_pmd);
        } else {
                pte_t new_pte = pfn_pte(pfn, mem_type);
@@ -1399,7 +1467,19 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
                        kvm_set_pfn_dirty(pfn);
                        mark_page_dirty(kvm, gfn);
                }
-               coherent_cache_guest_page(vcpu, pfn, PAGE_SIZE);
+
+               if (fault_status != FSC_PERM)
+                       clean_dcache_guest_page(pfn, PAGE_SIZE);
+
+               if (exec_fault) {
+                       new_pte = kvm_s2pte_mkexec(new_pte);
+                       invalidate_icache_guest_page(pfn, PAGE_SIZE);
+               } else if (fault_status == FSC_PERM) {
+                       /* Preserve execute if XN was already cleared */
+                       if (stage2_is_exec(kvm, fault_ipa))
+                               new_pte = kvm_s2pte_mkexec(new_pte);
+               }
+
                ret = stage2_set_pte(kvm, memcache, fault_ipa, &new_pte, flags);
        }
 
@@ -1751,7 +1831,9 @@ int kvm_mmu_init(void)
        int err;
 
        hyp_idmap_start = kvm_virt_to_phys(__hyp_idmap_text_start);
+       hyp_idmap_start = ALIGN_DOWN(hyp_idmap_start, PAGE_SIZE);
        hyp_idmap_end = kvm_virt_to_phys(__hyp_idmap_text_end);
+       hyp_idmap_end = ALIGN(hyp_idmap_end, PAGE_SIZE);
        hyp_idmap_vector = kvm_virt_to_phys(__kvm_hyp_init);
 
        /*
@@ -1760,9 +1842,10 @@ int kvm_mmu_init(void)
         */
        BUG_ON((hyp_idmap_start ^ (hyp_idmap_end - 1)) & PAGE_MASK);
 
-       kvm_info("IDMAP page: %lx\n", hyp_idmap_start);
-       kvm_info("HYP VA range: %lx:%lx\n",
-                kern_hyp_va(PAGE_OFFSET), kern_hyp_va(~0UL));
+       kvm_debug("IDMAP page: %lx\n", hyp_idmap_start);
+       kvm_debug("HYP VA range: %lx:%lx\n",
+                 kern_hyp_va(PAGE_OFFSET),
+                 kern_hyp_va((unsigned long)high_memory - 1));
 
        if (hyp_idmap_start >= kern_hyp_va(PAGE_OFFSET) &&
            hyp_idmap_start <  kern_hyp_va(~0UL) &&
@@ -1985,7 +2068,7 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
  */
 void kvm_set_way_flush(struct kvm_vcpu *vcpu)
 {
-       unsigned long hcr = vcpu_get_hcr(vcpu);
+       unsigned long hcr = *vcpu_hcr(vcpu);
 
        /*
         * If this is the first time we do a S/W operation
@@ -2000,7 +2083,7 @@ void kvm_set_way_flush(struct kvm_vcpu *vcpu)
                trace_kvm_set_way_flush(*vcpu_pc(vcpu),
                                        vcpu_has_cache_enabled(vcpu));
                stage2_flush_vm(vcpu->kvm);
-               vcpu_set_hcr(vcpu, hcr | HCR_TVM);
+               *vcpu_hcr(vcpu) = hcr | HCR_TVM;
        }
 }
 
@@ -2018,7 +2101,7 @@ void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled)
 
        /* Caches are now on, stop trapping VM ops (until a S/W op) */
        if (now_enabled)
-               vcpu_set_hcr(vcpu, vcpu_get_hcr(vcpu) & ~HCR_TVM);
+               *vcpu_hcr(vcpu) &= ~HCR_TVM;
 
        trace_kvm_toggle_cache(*vcpu_pc(vcpu), was_enabled, now_enabled);
 }