x86: cpa, micro-optimization
[sfrench/cifs-2.6.git] / arch / x86 / mm / pageattr.c
index 79a9f1b42ddd60c5d0f24290b691eaba5021514d..bb55a78dcd621cad4a608073ed46c438f4940163 100644 (file)
 #include <asm/uaccess.h>
 #include <asm/pgalloc.h>
 
+/*
+ * The current flushing context - we pass it instead of 5 arguments:
+ */
 struct cpa_data {
        unsigned long   vaddr;
-       int             numpages;
        pgprot_t        mask_set;
        pgprot_t        mask_clr;
+       int             numpages;
        int             flushtlb;
 };
 
@@ -114,7 +117,7 @@ static void cpa_flush_range(unsigned long start, int numpages, int cache)
                /*
                 * Only flush present addresses:
                 */
-               if (pte && pte_present(*pte))
+               if (pte && (pte_val(*pte) & _PAGE_PRESENT))
                        clflush_cache_range((void *) addr, PAGE_SIZE);
        }
 }
@@ -183,6 +186,14 @@ static inline pgprot_t static_protections(pgprot_t prot, unsigned long address)
        return prot;
 }
 
+/*
+ * Lookup the page table entry for a virtual address. Return a pointer
+ * to the entry and the level of the mapping.
+ *
+ * Note: We return pud and pmd either when the entry is marked large
+ * or when the present bit is not set. Otherwise we would return a
+ * pointer to a nonexisting mapping.
+ */
 pte_t *lookup_address(unsigned long address, int *level)
 {
        pgd_t *pgd = pgd_offset_k(address);
@@ -193,21 +204,31 @@ pte_t *lookup_address(unsigned long address, int *level)
 
        if (pgd_none(*pgd))
                return NULL;
+
        pud = pud_offset(pgd, address);
        if (pud_none(*pud))
                return NULL;
+
+       *level = PG_LEVEL_1G;
+       if (pud_large(*pud) || !pud_present(*pud))
+               return (pte_t *)pud;
+
        pmd = pmd_offset(pud, address);
        if (pmd_none(*pmd))
                return NULL;
 
        *level = PG_LEVEL_2M;
-       if (pmd_large(*pmd))
+       if (pmd_large(*pmd) || !pmd_present(*pmd))
                return (pte_t *)pmd;
 
        *level = PG_LEVEL_4K;
+
        return pte_offset_kernel(pmd, address);
 }
 
+/*
+ * Set the new pmd in all the pgds we know about:
+ */
 static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte)
 {
        /* change init_mm */
@@ -216,6 +237,7 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte)
        if (!SHARED_KERNEL_PMD) {
                struct page *page;
 
+               address = __pa(address);
                list_for_each_entry(page, &pgd_list, lru) {
                        pgd_t *pgd;
                        pud_t *pud;
@@ -230,17 +252,114 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte)
 #endif
 }
 
+static int
+try_preserve_large_page(pte_t *kpte, unsigned long address,
+                       struct cpa_data *cpa)
+{
+       unsigned long nextpage_addr, numpages, pmask, psize, flags;
+       pte_t new_pte, old_pte, *tmp;
+       pgprot_t old_prot, new_prot;
+       int level, do_split = 1;
+
+       /*
+        * An Athlon 64 X2 showed hard hangs if we tried to preserve
+        * largepages and changed the PSE entry from RW to RO.
+        *
+        * As AMD CPUs have a long series of erratas in this area,
+        * (and none of the known ones seem to explain this hang),
+        * disable this code until the hang can be debugged:
+        */
+       if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD)
+               return 1;
+
+       spin_lock_irqsave(&pgd_lock, flags);
+       /*
+        * Check for races, another CPU might have split this page
+        * up already:
+        */
+       tmp = lookup_address(address, &level);
+       if (tmp != kpte)
+               goto out_unlock;
+
+       switch (level) {
+       case PG_LEVEL_2M:
+               psize = PMD_PAGE_SIZE;
+               pmask = PMD_PAGE_MASK;
+               break;
+#ifdef CONFIG_X86_64
+       case PG_LEVEL_1G:
+               psize = PMD_PAGE_SIZE;
+               pmask = PMD_PAGE_MASK;
+               break;
+#endif
+       default:
+               do_split = -EINVAL;
+               goto out_unlock;
+       }
+
+       /*
+        * Calculate the number of pages, which fit into this large
+        * page starting at address:
+        */
+       nextpage_addr = (address + psize) & pmask;
+       numpages = (nextpage_addr - address) >> PAGE_SHIFT;
+       if (numpages < cpa->numpages)
+               cpa->numpages = numpages;
+
+       /*
+        * We are safe now. Check whether the new pgprot is the same:
+        */
+       old_pte = *kpte;
+       old_prot = new_prot = pte_pgprot(old_pte);
+
+       pgprot_val(new_prot) &= ~pgprot_val(cpa->mask_clr);
+       pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
+       new_prot = static_protections(new_prot, address);
+
+       /*
+        * If there are no changes, return. maxpages has been updated
+        * above:
+        */
+       if (pgprot_val(new_prot) == pgprot_val(old_prot)) {
+               do_split = 0;
+               goto out_unlock;
+       }
+
+       /*
+        * We need to change the attributes. Check, whether we can
+        * change the large page in one go. We request a split, when
+        * the address is not aligned and the number of pages is
+        * smaller than the number of pages in the large page. Note
+        * that we limited the number of possible pages already to
+        * the number of pages in the large page.
+        */
+       if (address == (nextpage_addr - psize) && cpa->numpages == numpages) {
+               /*
+                * The address is aligned and the number of pages
+                * covers the full page.
+                */
+               new_pte = pfn_pte(pte_pfn(old_pte), canon_pgprot(new_prot));
+               __set_pmd_pte(kpte, address, new_pte);
+               cpa->flushtlb = 1;
+               do_split = 0;
+       }
+
+out_unlock:
+       spin_unlock_irqrestore(&pgd_lock, flags);
+
+       return do_split;
+}
+
 static int split_large_page(pte_t *kpte, unsigned long address)
 {
-       pgprot_t ref_prot = pte_pgprot(pte_clrhuge(*kpte));
+       unsigned long flags, pfn, pfninc = 1;
        gfp_t gfp_flags = GFP_KERNEL;
-       unsigned long flags, addr, pfn;
+       unsigned int i, level;
        pte_t *pbase, *tmp;
+       pgprot_t ref_prot;
        struct page *base;
-       unsigned int i, level;
 
 #ifdef CONFIG_DEBUG_PAGEALLOC
-       gfp_flags = __GFP_HIGH | __GFP_NOFAIL | __GFP_NOWARN;
        gfp_flags = GFP_ATOMIC | __GFP_NOWARN;
 #endif
        base = alloc_pages(gfp_flags, 0);
@@ -253,33 +372,41 @@ static int split_large_page(pte_t *kpte, unsigned long address)
         * up for us already:
         */
        tmp = lookup_address(address, &level);
-       if (tmp != kpte) {
-               WARN_ON_ONCE(1);
+       if (tmp != kpte)
                goto out_unlock;
-       }
 
-       address = __pa(address);
-       addr = address & LARGE_PAGE_MASK;
        pbase = (pte_t *)page_address(base);
 #ifdef CONFIG_X86_32
        paravirt_alloc_pt(&init_mm, page_to_pfn(base));
 #endif
+       ref_prot = pte_pgprot(pte_clrhuge(*kpte));
+
+#ifdef CONFIG_X86_64
+       if (level == PG_LEVEL_1G) {
+               pfninc = PMD_PAGE_SIZE >> PAGE_SHIFT;
+               pgprot_val(ref_prot) |= _PAGE_PSE;
+       }
+#endif
 
        /*
         * Get the target pfn from the original entry:
         */
        pfn = pte_pfn(*kpte);
-       for (i = 0; i < PTRS_PER_PTE; i++, pfn++)
+       for (i = 0; i < PTRS_PER_PTE; i++, pfn += pfninc)
                set_pte(&pbase[i], pfn_pte(pfn, ref_prot));
 
        /*
-        * Install the new, split up pagetable. Important detail here:
+        * Install the new, split up pagetable. Important details here:
         *
         * On Intel the NX bit of all levels must be cleared to make a
         * page executable. See section 4.13.2 of Intel 64 and IA-32
         * Architectures Software Developer's Manual).
+        *
+        * Mark the entry present. The current mapping might be
+        * set to not present, which we preserved above.
         */
        ref_prot = pte_pgprot(pte_mkexec(pte_clrhuge(*kpte)));
+       pgprot_val(ref_prot) |= _PAGE_PRESENT;
        __set_pmd_pte(kpte, address, mk_pte(base, ref_prot));
        base = NULL;
 
@@ -294,8 +421,8 @@ out_unlock:
 
 static int __change_page_attr(unsigned long address, struct cpa_data *cpa)
 {
+       int level, do_split, err;
        struct page *kpte_page;
-       int level, err = 0;
        pte_t *kpte;
 
 repeat:
@@ -338,12 +465,32 @@ repeat:
                        set_pte_atomic(kpte, new_pte);
                        cpa->flushtlb = 1;
                }
-       } else {
-               err = split_large_page(kpte, address);
-               if (!err)
-                       goto repeat;
+               cpa->numpages = 1;
+               return 0;
+       }
+
+       /*
+        * Check, whether we can keep the large page intact
+        * and just change the pte:
+        */
+       do_split = try_preserve_large_page(kpte, address, cpa);
+       /*
+        * When the range fits into the existing large page,
+        * return. cp->numpages and cpa->tlbflush have been updated in
+        * try_large_page:
+        */
+       if (do_split <= 0)
+               return do_split;
+
+       /*
+        * We have to split the large page:
+        */
+       err = split_large_page(kpte, address);
+       if (!err) {
                cpa->flushtlb = 1;
+               goto repeat;
        }
+
        return err;
 }
 
@@ -360,7 +507,6 @@ repeat:
  *
  * Modules and drivers should use the set_memory_* APIs instead.
  */
-
 static int change_page_attr_addr(struct cpa_data *cpa)
 {
        int err;
@@ -410,15 +556,27 @@ static int change_page_attr_addr(struct cpa_data *cpa)
 
 static int __change_page_attr_set_clr(struct cpa_data *cpa)
 {
-       unsigned int i;
-       int ret;
+       int ret, numpages = cpa->numpages;
 
-       for (i = 0; i < cpa->numpages ; i++, cpa->vaddr += PAGE_SIZE) {
+       while (numpages) {
+               /*
+                * Store the remaining nr of pages for the large page
+                * preservation check.
+                */
+               cpa->numpages = numpages;
                ret = change_page_attr_addr(cpa);
                if (ret)
                        return ret;
-       }
 
+               /*
+                * Adjust the number of pages with the result of the
+                * CPA operation. Either a large page has been
+                * preserved or a single page update happened.
+                */
+               BUG_ON(cpa->numpages > numpages);
+               numpages -= cpa->numpages;
+               cpa->vaddr += cpa->numpages * PAGE_SIZE;
+       }
        return 0;
 }