aboutsummaryrefslogtreecommitdiff
path: root/arch/x86/mm/pageattr_32.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/mm/pageattr_32.c')
-rw-r--r--arch/x86/mm/pageattr_32.c89
1 files changed, 49 insertions, 40 deletions
diff --git a/arch/x86/mm/pageattr_32.c b/arch/x86/mm/pageattr_32.c
index 1011b21f8db..14c923b3b07 100644
--- a/arch/x86/mm/pageattr_32.c
+++ b/arch/x86/mm/pageattr_32.c
@@ -37,33 +37,6 @@ pte_t *lookup_address(unsigned long address, int *level)
return pte_offset_kernel(pmd, address);
}
-static struct page *
-split_large_page(unsigned long address, pgprot_t ref_prot)
-{
- unsigned long addr;
- struct page *base;
- pte_t *pbase;
- int i;
-
- base = alloc_pages(GFP_KERNEL, 0);
- if (!base)
- return NULL;
-
- /*
- * page_private is used to track the number of entries in
- * the page table page that have non standard attributes.
- */
- address = __pa(address);
- addr = address & LARGE_PAGE_MASK;
- pbase = (pte_t *)page_address(base);
- paravirt_alloc_pt(&init_mm, page_to_pfn(base));
-
- for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE)
- set_pte(&pbase[i], pfn_pte(addr >> PAGE_SHIFT, ref_prot));
-
- return base;
-}
-
static void set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte)
{
unsigned long flags;
@@ -88,14 +61,58 @@ static void set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte)
spin_unlock_irqrestore(&pgd_lock, flags);
}
+static int
+split_large_page(pte_t *kpte, unsigned long address, pgprot_t ref_prot)
+{
+ int i, level;
+ unsigned long addr;
+ pte_t *pbase, *tmp;
+ struct page *base;
+
+ base = alloc_pages(GFP_KERNEL, 0);
+ if (!base)
+ return -ENOMEM;
+
+ down_write(&init_mm.mmap_sem);
+ /*
+ * Check for races, another CPU might have split this page
+ * up for us already:
+ */
+ tmp = lookup_address(address, &level);
+ if (tmp != kpte)
+ goto out_unlock;
+
+ address = __pa(address);
+ addr = address & LARGE_PAGE_MASK;
+ pbase = (pte_t *)page_address(base);
+ paravirt_alloc_pt(&init_mm, page_to_pfn(base));
+
+ for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE)
+ set_pte(&pbase[i], pfn_pte(addr >> PAGE_SHIFT, ref_prot));
+
+ /*
+ * Install the new, split up pagetable:
+ */
+ set_pmd_pte(kpte, address, mk_pte(base, ref_prot));
+ base = NULL;
+
+out_unlock:
+ up_write(&init_mm.mmap_sem);
+
+ if (base)
+ __free_pages(base, 0);
+
+ return 0;
+}
+
static int __change_page_attr(struct page *page, pgprot_t prot)
{
pgprot_t ref_prot = PAGE_KERNEL;
struct page *kpte_page;
unsigned long address;
+ int level, err = 0;
pgprot_t oldprot;
pte_t *kpte;
- int level;
BUG_ON(PageHighMem(page));
address = (unsigned long)page_address(page);
@@ -127,19 +144,11 @@ repeat:
if (level == 3) {
set_pte_atomic(kpte, mk_pte(page, prot));
} else {
- struct page *split;
-
- split = split_large_page(address, ref_prot);
- if (!split)
- return -ENOMEM;
-
- /*
- * There's a small window here to waste a bit of RAM:
- */
- set_pmd_pte(kpte, address, mk_pte(split, ref_prot));
- goto repeat;
+ err = split_large_page(kpte, address, ref_prot);
+ if (!err)
+ goto repeat;
}
- return 0;
+ return err;
}
/*