#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;
pgprot_t mask_set;
int flushtlb;
};
-enum {
- CPA_NO_SPLIT = 0,
- CPA_SPLIT,
-};
-
static inline int
within(unsigned long addr, unsigned long start, unsigned long end)
{
/*
* Only flush present addresses:
*/
- if (pte && pte_present(*pte))
+ if (pte && (pte_val(*pte) & _PAGE_PRESENT))
clflush_cache_range((void *) addr, PAGE_SIZE);
}
}
if (within(address, virt_to_highmap(_text), virt_to_highmap(_etext)))
pgprot_val(forbidden) |= _PAGE_NX;
-
-#ifdef CONFIG_DEBUG_RODATA
/* The .rodata section needs to be read-only */
if (within(address, (unsigned long)__start_rodata,
(unsigned long)__end_rodata))
if (within(address, virt_to_highmap(__start_rodata),
virt_to_highmap(__end_rodata)))
pgprot_val(forbidden) |= _PAGE_RW;
-#endif
prot = __pgprot(pgprot_val(prot) & ~pgprot_val(forbidden));
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;
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 */
#endif
}
-static int try_preserve_large_page(pte_t *kpte, unsigned long address,
- struct cpa_data *cpa)
+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, res = CPA_SPLIT;
-
- /*
- * 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 res;
+ int level, do_split = 1;
spin_lock_irqsave(&pgd_lock, flags);
/*
switch (level) {
case PG_LEVEL_2M:
- psize = LARGE_PAGE_SIZE;
- pmask = LARGE_PAGE_MASK;
+ 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:
- res = -EINVAL;
+ do_split = -EINVAL;
goto out_unlock;
}
* above:
*/
if (pgprot_val(new_prot) == pgprot_val(old_prot)) {
- res = CPA_NO_SPLIT;
+ do_split = 0;
goto out_unlock;
}
new_pte = pfn_pte(pte_pfn(old_pte), canon_pgprot(new_prot));
__set_pmd_pte(kpte, address, new_pte);
cpa->flushtlb = 1;
- res = CPA_NO_SPLIT;
+ do_split = 0;
}
out_unlock:
spin_unlock_irqrestore(&pgd_lock, flags);
- return res;
+
+ 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);
* 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;
static int __change_page_attr(unsigned long address, struct cpa_data *cpa)
{
+ int level, do_split, err;
struct page *kpte_page;
- int level, res;
pte_t *kpte;
repeat:
* Check, whether we can keep the large page intact
* and just change the pte:
*/
- res = try_preserve_large_page(kpte, address, cpa);
- if (res < 0)
- return res;
-
+ 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 (res == CPA_NO_SPLIT)
- return 0;
+ if (do_split <= 0)
+ return do_split;
/*
* We have to split the large page:
*/
- res = split_large_page(kpte, address);
- if (res)
- return res;
- cpa->flushtlb = 1;
- goto repeat;
+ err = split_large_page(kpte, address);
+ if (!err) {
+ cpa->flushtlb = 1;
+ goto repeat;
+ }
+
+ return err;
}
/**
*
* Modules and drivers should use the set_memory_* APIs instead.
*/
-
static int change_page_attr_addr(struct cpa_data *cpa)
{
int err;