]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - arch/x86/mm/pageattr.c
x86/mm/cpa: Do the range check early
[linux.git] / arch / x86 / mm / pageattr.c
index 61ff2784845225aa606c3ee195449f7c54768987..bbc5eb5cbc9dd2028b693236da474a5b19e60381 100644 (file)
@@ -37,11 +37,20 @@ struct cpa_data {
        unsigned long   numpages;
        int             flags;
        unsigned long   pfn;
-       unsigned        force_split : 1;
+       unsigned        force_split             : 1,
+                       force_static_prot       : 1;
        int             curpage;
        struct page     **pages;
 };
 
+enum cpa_warn {
+       CPA_CONFLICT,
+       CPA_PROTECT,
+       CPA_DETECT,
+};
+
+static const int cpa_warn_level = CPA_PROTECT;
+
 /*
  * Serialize cpa() (for !DEBUG_PAGEALLOC which uses large identity mappings)
  * using cpa_lock. So that we don't allow any other cpu, with stale large tlb
@@ -94,6 +103,95 @@ void arch_report_meminfo(struct seq_file *m)
 static inline void split_page_count(int level) { }
 #endif
 
+#ifdef CONFIG_X86_CPA_STATISTICS
+
+static unsigned long cpa_1g_checked;
+static unsigned long cpa_1g_sameprot;
+static unsigned long cpa_1g_preserved;
+static unsigned long cpa_2m_checked;
+static unsigned long cpa_2m_sameprot;
+static unsigned long cpa_2m_preserved;
+static unsigned long cpa_4k_checked;
+static unsigned long cpa_4k_install;
+
+static inline void cpa_inc_1g_checked(void)
+{
+       cpa_1g_checked++;
+}
+
+static inline void cpa_inc_2m_checked(void)
+{
+       cpa_2m_checked++;
+}
+
+static inline void cpa_inc_4k_checked(void)
+{
+       cpa_4k_checked++;
+}
+
+static inline void cpa_inc_4k_install(void)
+{
+       cpa_4k_install++;
+}
+
+static inline void cpa_inc_lp_sameprot(int level)
+{
+       if (level == PG_LEVEL_1G)
+               cpa_1g_sameprot++;
+       else
+               cpa_2m_sameprot++;
+}
+
+static inline void cpa_inc_lp_preserved(int level)
+{
+       if (level == PG_LEVEL_1G)
+               cpa_1g_preserved++;
+       else
+               cpa_2m_preserved++;
+}
+
+static int cpastats_show(struct seq_file *m, void *p)
+{
+       seq_printf(m, "1G pages checked:     %16lu\n", cpa_1g_checked);
+       seq_printf(m, "1G pages sameprot:    %16lu\n", cpa_1g_sameprot);
+       seq_printf(m, "1G pages preserved:   %16lu\n", cpa_1g_preserved);
+       seq_printf(m, "2M pages checked:     %16lu\n", cpa_2m_checked);
+       seq_printf(m, "2M pages sameprot:    %16lu\n", cpa_2m_sameprot);
+       seq_printf(m, "2M pages preserved:   %16lu\n", cpa_2m_preserved);
+       seq_printf(m, "4K pages checked:     %16lu\n", cpa_4k_checked);
+       seq_printf(m, "4K pages set-checked: %16lu\n", cpa_4k_install);
+       return 0;
+}
+
+static int cpastats_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, cpastats_show, NULL);
+}
+
+static const struct file_operations cpastats_fops = {
+       .open           = cpastats_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int __init cpa_stats_init(void)
+{
+       debugfs_create_file("cpa_stats", S_IRUSR, arch_debugfs_dir, NULL,
+                           &cpastats_fops);
+       return 0;
+}
+late_initcall(cpa_stats_init);
+#else
+static inline void cpa_inc_1g_checked(void) { }
+static inline void cpa_inc_2m_checked(void) { }
+static inline void cpa_inc_4k_checked(void) { }
+static inline void cpa_inc_4k_install(void) { }
+static inline void cpa_inc_lp_sameprot(int level) { }
+static inline void cpa_inc_lp_preserved(int level) { }
+#endif
+
+
 static inline int
 within(unsigned long addr, unsigned long start, unsigned long end)
 {
@@ -395,6 +493,29 @@ static pgprotval_t protect_kernel_text_ro(unsigned long start,
 }
 #endif
 
+static inline bool conflicts(pgprot_t prot, pgprotval_t val)
+{
+       return (pgprot_val(prot) & ~val) != pgprot_val(prot);
+}
+
+static inline void check_conflict(int warnlvl, pgprot_t prot, pgprotval_t val,
+                                 unsigned long start, unsigned long end,
+                                 unsigned long pfn, const char *txt)
+{
+       static const char *lvltxt[] = {
+               [CPA_CONFLICT]  = "conflict",
+               [CPA_PROTECT]   = "protect",
+               [CPA_DETECT]    = "detect",
+       };
+
+       if (warnlvl > cpa_warn_level || !conflicts(prot, val))
+               return;
+
+       pr_warn("CPA %8s %10s: 0x%016lx - 0x%016lx PFN %lx req %016llx prevent %016llx\n",
+               lvltxt[warnlvl], txt, start, end, pfn, (unsigned long long)pgprot_val(prot),
+               (unsigned long long)val);
+}
+
 /*
  * Certain areas of memory on x86 require very specific protection flags,
  * for example the BIOS area or kernel text. Callers don't always get this
@@ -402,19 +523,38 @@ static pgprotval_t protect_kernel_text_ro(unsigned long start,
  * checks and fixes these known static required protection bits.
  */
 static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
-                                         unsigned long pfn, unsigned long npg)
+                                         unsigned long pfn, unsigned long npg,
+                                         int warnlvl)
 {
-       pgprotval_t forbidden;
+       pgprotval_t forbidden, res;
        unsigned long end;
 
+       /*
+        * There is no point in checking RW/NX conflicts when the requested
+        * mapping is setting the page !PRESENT.
+        */
+       if (!(pgprot_val(prot) & _PAGE_PRESENT))
+               return prot;
+
        /* Operate on the virtual address */
        end = start + npg * PAGE_SIZE - 1;
-       forbidden  = protect_kernel_text(start, end);
-       forbidden |= protect_kernel_text_ro(start, end);
+
+       res = protect_kernel_text(start, end);
+       check_conflict(warnlvl, prot, res, start, end, pfn, "Text NX");
+       forbidden = res;
+
+       res = protect_kernel_text_ro(start, end);
+       check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
+       forbidden |= res;
 
        /* Check the PFN directly */
-       forbidden |= protect_pci_bios(pfn, pfn + npg - 1);
-       forbidden |= protect_rodata(pfn, pfn + npg - 1);
+       res = protect_pci_bios(pfn, pfn + npg - 1);
+       check_conflict(warnlvl, prot, res, start, end, pfn, "PCIBIOS NX");
+       forbidden |= res;
+
+       res = protect_rodata(pfn, pfn + npg - 1);
+       check_conflict(warnlvl, prot, res, start, end, pfn, "Rodata RO");
+       forbidden |= res;
 
        return __pgprot(pgprot_val(prot) & ~forbidden);
 }
@@ -606,7 +746,7 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
                                     struct cpa_data *cpa)
 {
        unsigned long numpages, pmask, psize, lpaddr, addr, pfn, old_pfn;
-       pgprot_t old_prot, new_prot, req_prot;
+       pgprot_t old_prot, new_prot, req_prot, chk_prot;
        pte_t new_pte, old_pte, *tmp;
        enum pg_level level;
        int i;
@@ -623,10 +763,12 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
        case PG_LEVEL_2M:
                old_prot = pmd_pgprot(*(pmd_t *)kpte);
                old_pfn = pmd_pfn(*(pmd_t *)kpte);
+               cpa_inc_2m_checked();
                break;
        case PG_LEVEL_1G:
                old_prot = pud_pgprot(*(pud_t *)kpte);
                old_pfn = pud_pfn(*(pud_t *)kpte);
+               cpa_inc_1g_checked();
                break;
        default:
                return -EINVAL;
@@ -681,23 +823,75 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
        numpages = psize >> PAGE_SHIFT;
 
        /*
-        * Make sure that the requested pgprot does not violate the static
-        * protections. Check the full large page whether one of the pages
-        * in it results in a different pgprot than the first one of the
-        * requested range. If yes, then the page needs to be split.
+        * Sanity check that the existing mapping is correct versus the static
+        * protections. static_protections() guards against !PRESENT, so no
+        * extra conditional required here.
+        */
+       chk_prot = static_protections(old_prot, lpaddr, old_pfn, numpages,
+                                     CPA_CONFLICT);
+
+       if (WARN_ON_ONCE(pgprot_val(chk_prot) != pgprot_val(old_prot))) {
+               /*
+                * Split the large page and tell the split code to
+                * enforce static protections.
+                */
+               cpa->force_static_prot = 1;
+               return 1;
+       }
+
+       /*
+        * Optimization: If the requested pgprot is the same as the current
+        * pgprot, then the large page can be preserved and no updates are
+        * required independent of alignment and length of the requested
+        * range. The above already established that the current pgprot is
+        * correct, which in consequence makes the requested pgprot correct
+        * as well if it is the same. The static protection scan below will
+        * not come to a different conclusion.
         */
-       new_prot = static_protections(req_prot, address, pfn, 1);
+       if (pgprot_val(req_prot) == pgprot_val(old_prot)) {
+               cpa_inc_lp_sameprot(level);
+               return 0;
+       }
+
+       /*
+        * Optimization: Check whether the requested pgprot is conflicting
+        * with a static protection requirement in the large page. If not,
+        * then checking whether the requested range is fully covering the
+        * large page can be done right here.
+        */
+       new_prot = static_protections(req_prot, lpaddr, old_pfn, numpages,
+                                     CPA_DETECT);
+
+       if (pgprot_val(req_prot) == pgprot_val(new_prot)) {
+               if (address != lpaddr || cpa->numpages != numpages)
+                       return 1;
+               goto setlp;
+       }
+
+       /*
+        * Slow path. The full large page check above established that the
+        * requested pgprot cannot be applied to the full large page due to
+        * conflicting requirements of static protection regions. It might
+        * turn out that the whole requested range is covered by the
+        * modified protection of the first 4k segment at @address. This
+        * might result in the ability to preserve the large page
+        * nevertheless.
+        */
+       new_prot = static_protections(req_prot, address, pfn, 1, CPA_DETECT);
        pfn = old_pfn;
        for (i = 0, addr = lpaddr; i < numpages; i++, addr += PAGE_SIZE, pfn++) {
-               pgprot_t chk_prot = static_protections(req_prot, addr, pfn, 1);
-
+               chk_prot = static_protections(req_prot, addr, pfn, 1,
+                                             CPA_DETECT);
+               cpa_inc_4k_checked();
                if (pgprot_val(chk_prot) != pgprot_val(new_prot))
                        return 1;
        }
 
        /* If there are no changes, return. */
-       if (pgprot_val(new_prot) == pgprot_val(old_prot))
+       if (pgprot_val(new_prot) == pgprot_val(old_prot)) {
+               cpa_inc_lp_sameprot(level);
                return 0;
+       }
 
        /*
         * Verify that the address is aligned and the number of pages
@@ -706,10 +900,12 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
        if (address != lpaddr || cpa->numpages != numpages)
                return 1;
 
+setlp:
        /* All checks passed. Update the large page mapping. */
        new_pte = pfn_pte(old_pfn, new_prot);
        __set_pmd_pte(kpte, address, new_pte);
        cpa->flags |= CPA_FLUSHTLB;
+       cpa_inc_lp_preserved(level);
        return 0;
 }
 
@@ -728,15 +924,50 @@ static int should_split_large_page(pte_t *kpte, unsigned long address,
        return do_split;
 }
 
+static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn,
+                         pgprot_t ref_prot, unsigned long address,
+                         unsigned long size)
+{
+       unsigned int npg = PFN_DOWN(size);
+       pgprot_t prot;
+
+       /*
+        * If should_split_large_page() discovered an inconsistent mapping,
+        * remove the invalid protection in the split mapping.
+        */
+       if (!cpa->force_static_prot)
+               goto set;
+
+       prot = static_protections(ref_prot, address, pfn, npg, CPA_PROTECT);
+
+       if (pgprot_val(prot) == pgprot_val(ref_prot))
+               goto set;
+
+       /*
+        * If this is splitting a PMD, fix it up. PUD splits cannot be
+        * fixed trivially as that would require to rescan the newly
+        * installed PMD mappings after returning from split_large_page()
+        * so an eventual further split can allocate the necessary PTE
+        * pages. Warn for now and revisit it in case this actually
+        * happens.
+        */
+       if (size == PAGE_SIZE)
+               ref_prot = prot;
+       else
+               pr_warn_once("CPA: Cannot fixup static protections for PUD split\n");
+set:
+       set_pte(pte, pfn_pte(pfn, ref_prot));
+}
+
 static int
 __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
                   struct page *base)
 {
+       unsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1;
        pte_t *pbase = (pte_t *)page_address(base);
-       unsigned long ref_pfn, pfn, pfninc = 1;
        unsigned int i, level;
-       pte_t *tmp;
        pgprot_t ref_prot;
+       pte_t *tmp;
 
        spin_lock(&pgd_lock);
        /*
@@ -759,15 +990,17 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
                 * PAT bit to correct position.
                 */
                ref_prot = pgprot_large_2_4k(ref_prot);
-
                ref_pfn = pmd_pfn(*(pmd_t *)kpte);
+               lpaddr = address & PMD_MASK;
+               lpinc = PAGE_SIZE;
                break;
 
        case PG_LEVEL_1G:
                ref_prot = pud_pgprot(*(pud_t *)kpte);
                ref_pfn = pud_pfn(*(pud_t *)kpte);
                pfninc = PMD_PAGE_SIZE >> PAGE_SHIFT;
-
+               lpaddr = address & PUD_MASK;
+               lpinc = PMD_SIZE;
                /*
                 * Clear the PSE flags if the PRESENT flag is not set
                 * otherwise pmd_present/pmd_huge will return true
@@ -788,8 +1021,8 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
         * Get the target pfn from the original entry:
         */
        pfn = ref_pfn;
-       for (i = 0; i < PTRS_PER_PTE; i++, pfn += pfninc)
-               set_pte(&pbase[i], pfn_pte(pfn, ref_prot));
+       for (i = 0; i < PTRS_PER_PTE; i++, pfn += pfninc, lpaddr += lpinc)
+               split_set_pte(cpa, pbase + i, pfn, ref_prot, lpaddr, lpinc);
 
        if (virt_addr_valid(address)) {
                unsigned long pfn = PFN_DOWN(__pa(address));
@@ -1299,7 +1532,9 @@ static int __change_page_attr(struct cpa_data *cpa, int primary)
                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, pfn, 1);
+               cpa_inc_4k_install();
+               new_prot = static_protections(new_prot, address, pfn, 1,
+                                             CPA_PROTECT);
 
                new_prot = pgprot_clear_protnone_bits(new_prot);