return addr >= start && addr < end;
}
+/*
+ * Flushing functions
+ */
+
+/**
+ * clflush_cache_range - flush a cache range with clflush
+ * @addr: virtual start address
+ * @size: number of bytes to flush
+ *
+ * clflush is an unordered instruction which needs fencing with mfence
+ * to avoid ordering issues.
+ */
+void clflush_cache_range(void *vaddr, unsigned int size)
+{
+ void *vend = vaddr + size - 1;
+
+ mb();
+
+ for (; vaddr < vend; vaddr += boot_cpu_data.x86_clflush_size)
+ clflush(vaddr);
+ /*
+ * Flush any possible final partial cacheline:
+ */
+ clflush(vend);
+
+ mb();
+}
+
+static void __cpa_flush_all(void *arg)
+{
+ /*
+ * Flush all to work around Errata in early athlons regarding
+ * large page flushing.
+ */
+ __flush_tlb_all();
+
+ if (boot_cpu_data.x86_model >= 4)
+ wbinvd();
+}
+
+static void cpa_flush_all(void)
+{
+ BUG_ON(irqs_disabled());
+
+ on_each_cpu(__cpa_flush_all, NULL, 1, 1);
+}
+
+static void __cpa_flush_range(void *arg)
+{
+ /*
+ * We could optimize that further and do individual per page
+ * tlb invalidates for a low number of pages. Caveat: we must
+ * flush the high aliases on 64bit as well.
+ */
+ __flush_tlb_all();
+}
+
+static void cpa_flush_range(unsigned long start, int numpages)
+{
+ unsigned int i, level;
+ unsigned long addr;
+
+ BUG_ON(irqs_disabled());
+ WARN_ON(PAGE_ALIGN(start) != start);
+
+ on_each_cpu(__cpa_flush_range, NULL, 1, 1);
+
+ /*
+ * We only need to flush on one CPU,
+ * clflush is a MESI-coherent instruction that
+ * will cause all other CPUs to flush the same
+ * cachelines:
+ */
+ for (i = 0, addr = start; i < numpages; i++, addr += PAGE_SIZE) {
+ pte_t *pte = lookup_address(addr, &level);
+
+ /*
+ * Only flush present addresses:
+ */
+ if (pte && pte_present(*pte))
+ clflush_cache_range((void *) addr, PAGE_SIZE);
+ }
+}
+
/*
* 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
prot = static_protections(prot, address);
if (level == PG_LEVEL_4K) {
+ WARN_ON_ONCE(pgprot_val(prot) & _PAGE_PSE);
set_pte_atomic(kpte, pfn_pte(pfn, canon_pgprot(prot)));
} else {
+ /* Clear the PSE bit for the 4k level pages ! */
+ pgprot_val(prot) = pgprot_val(prot) & ~_PAGE_PSE;
+
err = split_large_page(kpte, address);
if (!err)
goto repeat;
/**
* change_page_attr_addr - Change page table attributes in linear mapping
* @address: Virtual address in linear mapping.
- * @numpages: Number of pages to change
* @prot: New page table attribute (PAGE_*)
*
* Change page attributes of a page in the direct mapping. This is a variant
* Modules and drivers should use the set_memory_* APIs instead.
*/
-int change_page_attr_addr(unsigned long address, int numpages, pgprot_t prot)
+static int change_page_attr_addr(unsigned long address, pgprot_t prot)
{
- int err = 0, kernel_map = 0, i;
+ int err = 0, kernel_map = 0;
+ unsigned long pfn = __pa(address) >> PAGE_SHIFT;
#ifdef CONFIG_X86_64
if (address >= __START_KERNEL_map &&
}
#endif
- for (i = 0; i < numpages; i++, address += PAGE_SIZE) {
- unsigned long pfn = __pa(address) >> PAGE_SHIFT;
+ if (!kernel_map || pte_present(pfn_pte(0, prot))) {
+ err = __change_page_attr(address, pfn, prot);
+ if (err)
+ return err;
+ }
- if (!kernel_map || pte_present(pfn_pte(0, prot))) {
- err = __change_page_attr(address, pfn, prot);
- if (err)
- break;
- }
#ifdef CONFIG_X86_64
- /*
- * Handle kernel mapping too which aliases part of
- * lowmem:
- */
- if (__pa(address) < KERNEL_TEXT_SIZE) {
- unsigned long addr2;
- pgprot_t prot2;
-
- addr2 = __START_KERNEL_map + __pa(address);
- /* Make sure the kernel mappings stay executable */
- prot2 = pte_pgprot(pte_mkexec(pfn_pte(0, prot)));
- err = __change_page_attr(addr2, pfn, prot2);
- }
-#endif
+ /*
+ * Handle kernel mapping too which aliases part of
+ * lowmem:
+ */
+ if (__pa(address) < KERNEL_TEXT_SIZE) {
+ unsigned long addr2;
+ pgprot_t prot2;
+
+ addr2 = __START_KERNEL_map + __pa(address);
+ /* Make sure the kernel mappings stay executable */
+ prot2 = pte_pgprot(pte_mkexec(pfn_pte(0, prot)));
+ err = __change_page_attr(addr2, pfn, prot2);
}
+#endif
return err;
}
-/**
- * change_page_attr - Change page table attributes in the linear mapping.
- * @page: First page to change
- * @numpages: Number of pages to change
- * @prot: New protection/caching type (PAGE_*)
- *
- * Returns 0 on success, otherwise a negated errno.
- *
- * This should be used when a page is mapped with a different caching policy
- * than write-back somewhere - some CPUs do not like it when mappings with
- * different caching policies exist. This changes the page attributes of the
- * in kernel linear mapping too.
- *
- * Caller must call global_flush_tlb() later to make the changes active.
- *
- * The caller needs to ensure that there are no conflicting mappings elsewhere
- * (e.g. in user space) * This function only deals with the kernel linear map.
- *
- * For MMIO areas without mem_map use change_page_attr_addr() instead.
- *
- * Modules and drivers should use the set_pages_* APIs instead.
- */
-int change_page_attr(struct page *page, int numpages, pgprot_t prot)
-{
- unsigned long addr = (unsigned long)page_address(page);
-
- return change_page_attr_addr(addr, numpages, prot);
-}
-EXPORT_UNUSED_SYMBOL(change_page_attr); /* to be removed in 2.6.27 */
-
-/**
- * change_page_attr_set - Change page table attributes in the linear mapping.
- * @addr: Virtual address in linear mapping.
- * @numpages: Number of pages to change
- * @prot: Protection/caching type bits to set (PAGE_*)
- *
- * Returns 0 on success, otherwise a negated errno.
- *
- * This should be used when a page is mapped with a different caching policy
- * than write-back somewhere - some CPUs do not like it when mappings with
- * different caching policies exist. This changes the page attributes of the
- * in kernel linear mapping too.
- *
- * Caller must call global_flush_tlb() later to make the changes active.
- *
- * The caller needs to ensure that there are no conflicting mappings elsewhere
- * (e.g. in user space) * This function only deals with the kernel linear map.
- *
- * This function is different from change_page_attr() in that only selected bits
- * are impacted, all other bits remain as is.
- */
-int change_page_attr_set(unsigned long addr, int numpages, pgprot_t prot)
+static int __change_page_attr_set_clr(unsigned long addr, int numpages,
+ pgprot_t mask_set, pgprot_t mask_clr)
{
- pgprot_t current_prot;
+ pgprot_t new_prot;
int level;
pte_t *pte;
+ int i, ret;
- pte = lookup_address(addr, &level);
- if (pte)
- current_prot = pte_pgprot(*pte);
- else
- pgprot_val(current_prot) = 0;
+ for (i = 0; i < numpages ; i++) {
- pgprot_val(prot) = pgprot_val(current_prot) | pgprot_val(prot);
+ pte = lookup_address(addr, &level);
+ if (!pte)
+ return -EINVAL;
- return change_page_attr_addr(addr, numpages, prot);
+ new_prot = pte_pgprot(*pte);
+
+ pgprot_val(new_prot) &= ~pgprot_val(mask_clr);
+ pgprot_val(new_prot) |= pgprot_val(mask_set);
+
+ ret = change_page_attr_addr(addr, new_prot);
+ if (ret)
+ return ret;
+ addr += PAGE_SIZE;
+ }
+
+ return 0;
}
-/**
- * change_page_attr_clear - Change page table attributes in the linear mapping.
- * @addr: Virtual address in linear mapping.
- * @numpages: Number of pages to change
- * @prot: Protection/caching type bits to clear (PAGE_*)
- *
- * Returns 0 on success, otherwise a negated errno.
- *
- * This should be used when a page is mapped with a different caching policy
- * than write-back somewhere - some CPUs do not like it when mappings with
- * different caching policies exist. This changes the page attributes of the
- * in kernel linear mapping too.
- *
- * Caller must call global_flush_tlb() later to make the changes active.
- *
- * The caller needs to ensure that there are no conflicting mappings elsewhere
- * (e.g. in user space) * This function only deals with the kernel linear map.
- *
- * This function is different from change_page_attr() in that only selected bits
- * are impacted, all other bits remain as is.
- */
-int change_page_attr_clear(unsigned long addr, int numpages, pgprot_t prot)
+static int change_page_attr_set_clr(unsigned long addr, int numpages,
+ pgprot_t mask_set, pgprot_t mask_clr)
{
- pgprot_t current_prot;
- int level;
- pte_t *pte;
+ int ret = __change_page_attr_set_clr(addr, numpages, mask_set,
+ mask_clr);
- pte = lookup_address(addr, &level);
- if (pte)
- current_prot = pte_pgprot(*pte);
+ /*
+ * On success we use clflush, when the CPU supports it to
+ * avoid the wbindv. If the CPU does not support it and in the
+ * error case we fall back to cpa_flush_all (which uses
+ * wbindv):
+ */
+ if (!ret && cpu_has_clflush)
+ cpa_flush_range(addr, numpages);
else
- pgprot_val(current_prot) = 0;
+ cpa_flush_all();
- pgprot_val(prot) = pgprot_val(current_prot) & ~pgprot_val(prot);
+ return ret;
+}
- return change_page_attr_addr(addr, numpages, prot);
+static inline int change_page_attr_set(unsigned long addr, int numpages,
+ pgprot_t mask)
+{
+ return change_page_attr_set_clr(addr, numpages, mask, __pgprot(0));
}
+static inline int change_page_attr_clear(unsigned long addr, int numpages,
+ pgprot_t mask)
+{
+ return __change_page_attr_set_clr(addr, numpages, __pgprot(0), mask);
+}
int set_memory_uc(unsigned long addr, int numpages)
{
- pgprot_t uncached;
-
- pgprot_val(uncached) = _PAGE_PCD | _PAGE_PWT;
- return change_page_attr_set(addr, numpages, uncached);
+ return change_page_attr_set(addr, numpages,
+ __pgprot(_PAGE_PCD | _PAGE_PWT));
}
EXPORT_SYMBOL(set_memory_uc);
int set_memory_wb(unsigned long addr, int numpages)
{
- pgprot_t uncached;
-
- pgprot_val(uncached) = _PAGE_PCD | _PAGE_PWT;
- return change_page_attr_clear(addr, numpages, uncached);
+ return change_page_attr_clear(addr, numpages,
+ __pgprot(_PAGE_PCD | _PAGE_PWT));
}
EXPORT_SYMBOL(set_memory_wb);
int set_memory_x(unsigned long addr, int numpages)
{
- pgprot_t nx;
-
- pgprot_val(nx) = _PAGE_NX;
- return change_page_attr_clear(addr, numpages, nx);
+ return change_page_attr_clear(addr, numpages, __pgprot(_PAGE_NX));
}
EXPORT_SYMBOL(set_memory_x);
int set_memory_nx(unsigned long addr, int numpages)
{
- pgprot_t nx;
-
- pgprot_val(nx) = _PAGE_NX;
- return change_page_attr_set(addr, numpages, nx);
+ return change_page_attr_set(addr, numpages, __pgprot(_PAGE_NX));
}
EXPORT_SYMBOL(set_memory_nx);
int set_memory_ro(unsigned long addr, int numpages)
{
- pgprot_t rw;
-
- pgprot_val(rw) = _PAGE_RW;
- return change_page_attr_clear(addr, numpages, rw);
+ return change_page_attr_clear(addr, numpages, __pgprot(_PAGE_RW));
}
-EXPORT_SYMBOL(set_memory_ro);
int set_memory_rw(unsigned long addr, int numpages)
{
- pgprot_t rw;
+ return change_page_attr_set(addr, numpages, __pgprot(_PAGE_RW));
+}
- pgprot_val(rw) = _PAGE_RW;
- return change_page_attr_set(addr, numpages, rw);
+int set_memory_np(unsigned long addr, int numpages)
+{
+ return change_page_attr_clear(addr, numpages, __pgprot(_PAGE_PRESENT));
}
-EXPORT_SYMBOL(set_memory_rw);
int set_pages_uc(struct page *page, int numpages)
{
unsigned long addr = (unsigned long)page_address(page);
- pgprot_t uncached;
- pgprot_val(uncached) = _PAGE_PCD | _PAGE_PWT;
- return change_page_attr_set(addr, numpages, uncached);
+ return set_memory_uc(addr, numpages);
}
EXPORT_SYMBOL(set_pages_uc);
int set_pages_wb(struct page *page, int numpages)
{
unsigned long addr = (unsigned long)page_address(page);
- pgprot_t uncached;
- pgprot_val(uncached) = _PAGE_PCD | _PAGE_PWT;
- return change_page_attr_clear(addr, numpages, uncached);
+ return set_memory_wb(addr, numpages);
}
EXPORT_SYMBOL(set_pages_wb);
int set_pages_x(struct page *page, int numpages)
{
unsigned long addr = (unsigned long)page_address(page);
- pgprot_t nx;
- pgprot_val(nx) = _PAGE_NX;
- return change_page_attr_clear(addr, numpages, nx);
+ return set_memory_x(addr, numpages);
}
EXPORT_SYMBOL(set_pages_x);
int set_pages_nx(struct page *page, int numpages)
{
unsigned long addr = (unsigned long)page_address(page);
- pgprot_t nx;
- pgprot_val(nx) = _PAGE_NX;
- return change_page_attr_set(addr, numpages, nx);
+ return set_memory_nx(addr, numpages);
}
EXPORT_SYMBOL(set_pages_nx);
int set_pages_ro(struct page *page, int numpages)
{
unsigned long addr = (unsigned long)page_address(page);
- pgprot_t rw;
- pgprot_val(rw) = _PAGE_RW;
- return change_page_attr_clear(addr, numpages, rw);
+ return set_memory_ro(addr, numpages);
}
-EXPORT_SYMBOL(set_pages_ro);
int set_pages_rw(struct page *page, int numpages)
{
unsigned long addr = (unsigned long)page_address(page);
- pgprot_t rw;
- pgprot_val(rw) = _PAGE_RW;
- return change_page_attr_set(addr, numpages, rw);
+ return set_memory_rw(addr, numpages);
}
-EXPORT_SYMBOL(set_pages_rw);
-void clflush_cache_range(void *addr, int size)
+#if defined(CONFIG_DEBUG_PAGEALLOC) || defined(CONFIG_CPA_DEBUG)
+static inline int __change_page_attr_set(unsigned long addr, int numpages,
+ pgprot_t mask)
{
- int i;
+ return __change_page_attr_set_clr(addr, numpages, mask, __pgprot(0));
+}
- for (i = 0; i < size; i += boot_cpu_data.x86_clflush_size)
- clflush(addr+i);
+static inline int __change_page_attr_clear(unsigned long addr, int numpages,
+ pgprot_t mask)
+{
+ return __change_page_attr_set_clr(addr, numpages, __pgprot(0), mask);
}
+#endif
+
+#ifdef CONFIG_DEBUG_PAGEALLOC
-static void flush_kernel_map(void *arg)
+static int __set_pages_p(struct page *page, int numpages)
{
- /*
- * Flush all to work around Errata in early athlons regarding
- * large page flushing.
- */
- __flush_tlb_all();
+ unsigned long addr = (unsigned long)page_address(page);
- if (boot_cpu_data.x86_model >= 4)
- wbinvd();
+ return __change_page_attr_set(addr, numpages,
+ __pgprot(_PAGE_PRESENT | _PAGE_RW));
}
-void global_flush_tlb(void)
+static int __set_pages_np(struct page *page, int numpages)
{
- BUG_ON(irqs_disabled());
+ unsigned long addr = (unsigned long)page_address(page);
- on_each_cpu(flush_kernel_map, NULL, 1, 1);
+ return __change_page_attr_clear(addr, numpages,
+ __pgprot(_PAGE_PRESENT));
}
-EXPORT_SYMBOL(global_flush_tlb);
-#ifdef CONFIG_DEBUG_PAGEALLOC
void kernel_map_pages(struct page *page, int numpages, int enable)
{
if (PageHighMem(page))
* The return value is ignored - the calls cannot fail,
* large pages are disabled at boot time:
*/
- change_page_attr(page, numpages, enable ? PAGE_KERNEL : __pgprot(0));
+ if (enable)
+ __set_pages_p(page, numpages);
+ else
+ __set_pages_np(page, numpages);
/*
* We should perform an IPI and flush all tlbs,
__flush_tlb_all();
}
#endif
+
+/*
+ * The testcases use internal knowledge of the implementation that shouldn't
+ * be exposed to the rest of the kernel. Include these directly here.
+ */
+#ifdef CONFIG_CPA_DEBUG
+#include "pageattr-test.c"
+#endif