|
@@ -16,6 +16,7 @@
|
|
#include <asm/sections.h>
|
|
#include <asm/sections.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/pgalloc.h>
|
|
#include <asm/pgalloc.h>
|
|
|
|
+#include <asm/proto.h>
|
|
|
|
|
|
/*
|
|
/*
|
|
* The current flushing context - we pass it instead of 5 arguments:
|
|
* The current flushing context - we pass it instead of 5 arguments:
|
|
@@ -26,8 +27,23 @@ struct cpa_data {
|
|
pgprot_t mask_clr;
|
|
pgprot_t mask_clr;
|
|
int numpages;
|
|
int numpages;
|
|
int flushtlb;
|
|
int flushtlb;
|
|
|
|
+ unsigned long pfn;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+#ifdef CONFIG_X86_64
|
|
|
|
+
|
|
|
|
+static inline unsigned long highmap_start_pfn(void)
|
|
|
|
+{
|
|
|
|
+ return __pa(_text) >> PAGE_SHIFT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline unsigned long highmap_end_pfn(void)
|
|
|
|
+{
|
|
|
|
+ return __pa(round_up((unsigned long)_end, PMD_SIZE)) >> PAGE_SHIFT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#endif
|
|
|
|
+
|
|
static inline int
|
|
static inline int
|
|
within(unsigned long addr, unsigned long start, unsigned long end)
|
|
within(unsigned long addr, unsigned long start, unsigned long end)
|
|
{
|
|
{
|
|
@@ -123,29 +139,14 @@ static void cpa_flush_range(unsigned long start, int numpages, int cache)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-#define HIGH_MAP_START __START_KERNEL_map
|
|
|
|
-#define HIGH_MAP_END (__START_KERNEL_map + KERNEL_TEXT_SIZE)
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Converts a virtual address to a X86-64 highmap address
|
|
|
|
- */
|
|
|
|
-static unsigned long virt_to_highmap(void *address)
|
|
|
|
-{
|
|
|
|
-#ifdef CONFIG_X86_64
|
|
|
|
- return __pa((unsigned long)address) + HIGH_MAP_START - phys_base;
|
|
|
|
-#else
|
|
|
|
- return (unsigned long)address;
|
|
|
|
-#endif
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
/*
|
|
/*
|
|
* Certain areas of memory on x86 require very specific protection flags,
|
|
* 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
|
|
* for example the BIOS area or kernel text. Callers don't always get this
|
|
* right (again, ioremap() on BIOS memory is not uncommon) so this function
|
|
* right (again, ioremap() on BIOS memory is not uncommon) so this function
|
|
* checks and fixes these known static required protection bits.
|
|
* checks and fixes these known static required protection bits.
|
|
*/
|
|
*/
|
|
-static inline pgprot_t static_protections(pgprot_t prot, unsigned long address)
|
|
|
|
|
|
+static inline pgprot_t static_protections(pgprot_t prot, unsigned long address,
|
|
|
|
+ unsigned long pfn)
|
|
{
|
|
{
|
|
pgprot_t forbidden = __pgprot(0);
|
|
pgprot_t forbidden = __pgprot(0);
|
|
|
|
|
|
@@ -153,30 +154,23 @@ static inline pgprot_t static_protections(pgprot_t prot, unsigned long address)
|
|
* The BIOS area between 640k and 1Mb needs to be executable for
|
|
* The BIOS area between 640k and 1Mb needs to be executable for
|
|
* PCI BIOS based config access (CONFIG_PCI_GOBIOS) support.
|
|
* PCI BIOS based config access (CONFIG_PCI_GOBIOS) support.
|
|
*/
|
|
*/
|
|
- if (within(__pa(address), BIOS_BEGIN, BIOS_END))
|
|
|
|
|
|
+ if (within(pfn, BIOS_BEGIN >> PAGE_SHIFT, BIOS_END >> PAGE_SHIFT))
|
|
pgprot_val(forbidden) |= _PAGE_NX;
|
|
pgprot_val(forbidden) |= _PAGE_NX;
|
|
|
|
|
|
/*
|
|
/*
|
|
* The kernel text needs to be executable for obvious reasons
|
|
* The kernel text needs to be executable for obvious reasons
|
|
- * Does not cover __inittext since that is gone later on
|
|
|
|
|
|
+ * Does not cover __inittext since that is gone later on. On
|
|
|
|
+ * 64bit we do not enforce !NX on the low mapping
|
|
*/
|
|
*/
|
|
if (within(address, (unsigned long)_text, (unsigned long)_etext))
|
|
if (within(address, (unsigned long)_text, (unsigned long)_etext))
|
|
pgprot_val(forbidden) |= _PAGE_NX;
|
|
pgprot_val(forbidden) |= _PAGE_NX;
|
|
- /*
|
|
|
|
- * Do the same for the x86-64 high kernel mapping
|
|
|
|
- */
|
|
|
|
- if (within(address, virt_to_highmap(_text), virt_to_highmap(_etext)))
|
|
|
|
- pgprot_val(forbidden) |= _PAGE_NX;
|
|
|
|
|
|
|
|
- /* The .rodata section needs to be read-only */
|
|
|
|
- if (within(address, (unsigned long)__start_rodata,
|
|
|
|
- (unsigned long)__end_rodata))
|
|
|
|
- pgprot_val(forbidden) |= _PAGE_RW;
|
|
|
|
/*
|
|
/*
|
|
- * Do the same for the x86-64 high kernel mapping
|
|
|
|
|
|
+ * The .rodata section needs to be read-only. Using the pfn
|
|
|
|
+ * catches all aliases.
|
|
*/
|
|
*/
|
|
- if (within(address, virt_to_highmap(__start_rodata),
|
|
|
|
- virt_to_highmap(__end_rodata)))
|
|
|
|
|
|
+ if (within(pfn, __pa((unsigned long)__start_rodata) >> PAGE_SHIFT,
|
|
|
|
+ __pa((unsigned long)__end_rodata) >> PAGE_SHIFT))
|
|
pgprot_val(forbidden) |= _PAGE_RW;
|
|
pgprot_val(forbidden) |= _PAGE_RW;
|
|
|
|
|
|
prot = __pgprot(pgprot_val(prot) & ~pgprot_val(forbidden));
|
|
prot = __pgprot(pgprot_val(prot) & ~pgprot_val(forbidden));
|
|
@@ -253,7 +247,7 @@ static int
|
|
try_preserve_large_page(pte_t *kpte, unsigned long address,
|
|
try_preserve_large_page(pte_t *kpte, unsigned long address,
|
|
struct cpa_data *cpa)
|
|
struct cpa_data *cpa)
|
|
{
|
|
{
|
|
- unsigned long nextpage_addr, numpages, pmask, psize, flags, addr;
|
|
|
|
|
|
+ unsigned long nextpage_addr, numpages, pmask, psize, flags, addr, pfn;
|
|
pte_t new_pte, old_pte, *tmp;
|
|
pte_t new_pte, old_pte, *tmp;
|
|
pgprot_t old_prot, new_prot;
|
|
pgprot_t old_prot, new_prot;
|
|
int i, do_split = 1;
|
|
int i, do_split = 1;
|
|
@@ -301,7 +295,15 @@ try_preserve_large_page(pte_t *kpte, unsigned long address,
|
|
|
|
|
|
pgprot_val(new_prot) &= ~pgprot_val(cpa->mask_clr);
|
|
pgprot_val(new_prot) &= ~pgprot_val(cpa->mask_clr);
|
|
pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
|
|
pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
|
|
- new_prot = static_protections(new_prot, address);
|
|
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * old_pte points to the large page base address. So we need
|
|
|
|
+ * to add the offset of the virtual address:
|
|
|
|
+ */
|
|
|
|
+ pfn = pte_pfn(old_pte) + ((address & (psize - 1)) >> PAGE_SHIFT);
|
|
|
|
+ cpa->pfn = pfn;
|
|
|
|
+
|
|
|
|
+ new_prot = static_protections(new_prot, address, pfn);
|
|
|
|
|
|
/*
|
|
/*
|
|
* We need to check the full range, whether
|
|
* We need to check the full range, whether
|
|
@@ -309,8 +311,9 @@ try_preserve_large_page(pte_t *kpte, unsigned long address,
|
|
* the pages in the range we try to preserve:
|
|
* the pages in the range we try to preserve:
|
|
*/
|
|
*/
|
|
addr = address + PAGE_SIZE;
|
|
addr = address + PAGE_SIZE;
|
|
- for (i = 1; i < cpa->numpages; i++, addr += PAGE_SIZE) {
|
|
|
|
- pgprot_t chk_prot = static_protections(new_prot, addr);
|
|
|
|
|
|
+ pfn++;
|
|
|
|
+ for (i = 1; i < cpa->numpages; i++, addr += PAGE_SIZE, pfn++) {
|
|
|
|
+ pgprot_t chk_prot = static_protections(new_prot, addr, pfn);
|
|
|
|
|
|
if (pgprot_val(chk_prot) != pgprot_val(new_prot))
|
|
if (pgprot_val(chk_prot) != pgprot_val(new_prot))
|
|
goto out_unlock;
|
|
goto out_unlock;
|
|
@@ -505,46 +508,51 @@ out_unlock:
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-static int __change_page_attr(unsigned long address, struct cpa_data *cpa)
|
|
|
|
|
|
+static int __change_page_attr(struct cpa_data *cpa, int primary)
|
|
{
|
|
{
|
|
|
|
+ unsigned long address = cpa->vaddr;
|
|
int do_split, err;
|
|
int do_split, err;
|
|
unsigned int level;
|
|
unsigned int level;
|
|
struct page *kpte_page;
|
|
struct page *kpte_page;
|
|
- pte_t *kpte;
|
|
|
|
|
|
+ pte_t *kpte, old_pte;
|
|
|
|
|
|
repeat:
|
|
repeat:
|
|
kpte = lookup_address(address, &level);
|
|
kpte = lookup_address(address, &level);
|
|
if (!kpte)
|
|
if (!kpte)
|
|
|
|
+ return primary ? -EINVAL : 0;
|
|
|
|
+
|
|
|
|
+ old_pte = *kpte;
|
|
|
|
+ if (!pte_val(old_pte)) {
|
|
|
|
+ if (!primary)
|
|
|
|
+ return 0;
|
|
|
|
+ printk(KERN_WARNING "CPA: called for zero pte. "
|
|
|
|
+ "vaddr = %lx cpa->vaddr = %lx\n", address,
|
|
|
|
+ cpa->vaddr);
|
|
|
|
+ WARN_ON(1);
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
+ }
|
|
|
|
|
|
kpte_page = virt_to_page(kpte);
|
|
kpte_page = virt_to_page(kpte);
|
|
BUG_ON(PageLRU(kpte_page));
|
|
BUG_ON(PageLRU(kpte_page));
|
|
BUG_ON(PageCompound(kpte_page));
|
|
BUG_ON(PageCompound(kpte_page));
|
|
|
|
|
|
if (level == PG_LEVEL_4K) {
|
|
if (level == PG_LEVEL_4K) {
|
|
- pte_t new_pte, old_pte = *kpte;
|
|
|
|
|
|
+ pte_t new_pte;
|
|
pgprot_t new_prot = pte_pgprot(old_pte);
|
|
pgprot_t new_prot = pte_pgprot(old_pte);
|
|
-
|
|
|
|
- if(!pte_val(old_pte)) {
|
|
|
|
- printk(KERN_WARNING "CPA: called for zero pte. "
|
|
|
|
- "vaddr = %lx cpa->vaddr = %lx\n", address,
|
|
|
|
- cpa->vaddr);
|
|
|
|
- WARN_ON(1);
|
|
|
|
- return -EINVAL;
|
|
|
|
- }
|
|
|
|
|
|
+ unsigned long pfn = pte_pfn(old_pte);
|
|
|
|
|
|
pgprot_val(new_prot) &= ~pgprot_val(cpa->mask_clr);
|
|
pgprot_val(new_prot) &= ~pgprot_val(cpa->mask_clr);
|
|
pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
|
|
pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
|
|
|
|
|
|
- new_prot = static_protections(new_prot, address);
|
|
|
|
|
|
+ new_prot = static_protections(new_prot, address, pfn);
|
|
|
|
|
|
/*
|
|
/*
|
|
* We need to keep the pfn from the existing PTE,
|
|
* We need to keep the pfn from the existing PTE,
|
|
* after all we're only going to change it's attributes
|
|
* after all we're only going to change it's attributes
|
|
* not the memory it points to
|
|
* not the memory it points to
|
|
*/
|
|
*/
|
|
- new_pte = pfn_pte(pte_pfn(old_pte), canon_pgprot(new_prot));
|
|
|
|
-
|
|
|
|
|
|
+ new_pte = pfn_pte(pfn, canon_pgprot(new_prot));
|
|
|
|
+ cpa->pfn = pfn;
|
|
/*
|
|
/*
|
|
* Do we really change anything ?
|
|
* Do we really change anything ?
|
|
*/
|
|
*/
|
|
@@ -581,67 +589,44 @@ repeat:
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * change_page_attr_addr - Change page table attributes in linear mapping
|
|
|
|
- * @address: Virtual address in linear mapping.
|
|
|
|
- * @prot: New page table attribute (PAGE_*)
|
|
|
|
- *
|
|
|
|
- * Change page attributes of a page in the direct mapping. This is a variant
|
|
|
|
- * of change_page_attr() that also works on memory holes that do not have
|
|
|
|
- * mem_map entry (pfn_valid() is false).
|
|
|
|
- *
|
|
|
|
- * See change_page_attr() documentation for more details.
|
|
|
|
- *
|
|
|
|
- * Modules and drivers should use the set_memory_* APIs instead.
|
|
|
|
- */
|
|
|
|
-static int change_page_attr_addr(struct cpa_data *cpa)
|
|
|
|
|
|
+static int __change_page_attr_set_clr(struct cpa_data *cpa, int checkalias);
|
|
|
|
+
|
|
|
|
+static int cpa_process_alias(struct cpa_data *cpa)
|
|
{
|
|
{
|
|
- int err;
|
|
|
|
- unsigned long address = cpa->vaddr;
|
|
|
|
|
|
+ struct cpa_data alias_cpa;
|
|
|
|
+ int ret;
|
|
|
|
|
|
-#ifdef CONFIG_X86_64
|
|
|
|
- unsigned long phys_addr = __pa(address);
|
|
|
|
|
|
+ if (cpa->pfn > max_pfn_mapped)
|
|
|
|
+ return 0;
|
|
|
|
|
|
- /*
|
|
|
|
- * If we are inside the high mapped kernel range, then we
|
|
|
|
- * fixup the low mapping first. __va() returns the virtual
|
|
|
|
- * address in the linear mapping:
|
|
|
|
- */
|
|
|
|
- if (within(address, HIGH_MAP_START, HIGH_MAP_END))
|
|
|
|
- address = (unsigned long) __va(phys_addr);
|
|
|
|
-#endif
|
|
|
|
|
|
+ alias_cpa = *cpa;
|
|
|
|
+ alias_cpa.vaddr = (unsigned long) __va(cpa->pfn << PAGE_SHIFT);
|
|
|
|
|
|
- err = __change_page_attr(address, cpa);
|
|
|
|
- if (err)
|
|
|
|
- return err;
|
|
|
|
|
|
+ ret = __change_page_attr_set_clr(&alias_cpa, 0);
|
|
|
|
|
|
#ifdef CONFIG_X86_64
|
|
#ifdef CONFIG_X86_64
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
/*
|
|
/*
|
|
* If the physical address is inside the kernel map, we need
|
|
* If the physical address is inside the kernel map, we need
|
|
* to touch the high mapped kernel as well:
|
|
* to touch the high mapped kernel as well:
|
|
*/
|
|
*/
|
|
- if (within(phys_addr, 0, KERNEL_TEXT_SIZE)) {
|
|
|
|
- /*
|
|
|
|
- * Calc the high mapping address. See __phys_addr()
|
|
|
|
- * for the non obvious details.
|
|
|
|
- *
|
|
|
|
- * Note that NX and other required permissions are
|
|
|
|
- * checked in static_protections().
|
|
|
|
- */
|
|
|
|
- address = phys_addr + HIGH_MAP_START - phys_base;
|
|
|
|
|
|
+ if (!within(cpa->pfn, highmap_start_pfn(), highmap_end_pfn()))
|
|
|
|
+ return 0;
|
|
|
|
|
|
- /*
|
|
|
|
- * Our high aliases are imprecise, because we check
|
|
|
|
- * everything between 0 and KERNEL_TEXT_SIZE, so do
|
|
|
|
- * not propagate lookup failures back to users:
|
|
|
|
- */
|
|
|
|
- __change_page_attr(address, cpa);
|
|
|
|
- }
|
|
|
|
|
|
+ alias_cpa = *cpa;
|
|
|
|
+ alias_cpa.vaddr =
|
|
|
|
+ (cpa->pfn << PAGE_SHIFT) + __START_KERNEL_map - phys_base;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * The high mapping range is imprecise, so ignore the return value.
|
|
|
|
+ */
|
|
|
|
+ __change_page_attr_set_clr(&alias_cpa, 0);
|
|
#endif
|
|
#endif
|
|
- return err;
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
-static int __change_page_attr_set_clr(struct cpa_data *cpa)
|
|
|
|
|
|
+static int __change_page_attr_set_clr(struct cpa_data *cpa, int checkalias)
|
|
{
|
|
{
|
|
int ret, numpages = cpa->numpages;
|
|
int ret, numpages = cpa->numpages;
|
|
|
|
|
|
@@ -651,10 +636,17 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa)
|
|
* preservation check.
|
|
* preservation check.
|
|
*/
|
|
*/
|
|
cpa->numpages = numpages;
|
|
cpa->numpages = numpages;
|
|
- ret = change_page_attr_addr(cpa);
|
|
|
|
|
|
+
|
|
|
|
+ ret = __change_page_attr(cpa, checkalias);
|
|
if (ret)
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
|
|
+ if (checkalias) {
|
|
|
|
+ ret = cpa_process_alias(cpa);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Adjust the number of pages with the result of the
|
|
* Adjust the number of pages with the result of the
|
|
* CPA operation. Either a large page has been
|
|
* CPA operation. Either a large page has been
|
|
@@ -703,7 +695,7 @@ static int change_page_attr_set_clr(unsigned long addr, int numpages,
|
|
cpa.mask_clr = mask_clr;
|
|
cpa.mask_clr = mask_clr;
|
|
cpa.flushtlb = 0;
|
|
cpa.flushtlb = 0;
|
|
|
|
|
|
- ret = __change_page_attr_set_clr(&cpa);
|
|
|
|
|
|
+ ret = __change_page_attr_set_clr(&cpa, 1);
|
|
|
|
|
|
/*
|
|
/*
|
|
* Check whether we really changed something:
|
|
* Check whether we really changed something:
|
|
@@ -841,7 +833,7 @@ static int __set_pages_p(struct page *page, int numpages)
|
|
.mask_set = __pgprot(_PAGE_PRESENT | _PAGE_RW),
|
|
.mask_set = __pgprot(_PAGE_PRESENT | _PAGE_RW),
|
|
.mask_clr = __pgprot(0)};
|
|
.mask_clr = __pgprot(0)};
|
|
|
|
|
|
- return __change_page_attr_set_clr(&cpa);
|
|
|
|
|
|
+ return __change_page_attr_set_clr(&cpa, 1);
|
|
}
|
|
}
|
|
|
|
|
|
static int __set_pages_np(struct page *page, int numpages)
|
|
static int __set_pages_np(struct page *page, int numpages)
|
|
@@ -851,7 +843,7 @@ static int __set_pages_np(struct page *page, int numpages)
|
|
.mask_set = __pgprot(0),
|
|
.mask_set = __pgprot(0),
|
|
.mask_clr = __pgprot(_PAGE_PRESENT | _PAGE_RW)};
|
|
.mask_clr = __pgprot(_PAGE_PRESENT | _PAGE_RW)};
|
|
|
|
|
|
- return __change_page_attr_set_clr(&cpa);
|
|
|
|
|
|
+ return __change_page_attr_set_clr(&cpa, 1);
|
|
}
|
|
}
|
|
|
|
|
|
void kernel_map_pages(struct page *page, int numpages, int enable)
|
|
void kernel_map_pages(struct page *page, int numpages, int enable)
|