aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/patches-4.4
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/generic/patches-4.4')
-rw-r--r--target/linux/generic/patches-4.4/101-MIPS-fix-cache-flushing-for-highmem-pages.patch95
1 files changed, 95 insertions, 0 deletions
diff --git a/target/linux/generic/patches-4.4/101-MIPS-fix-cache-flushing-for-highmem-pages.patch b/target/linux/generic/patches-4.4/101-MIPS-fix-cache-flushing-for-highmem-pages.patch
new file mode 100644
index 0000000000..d7220af9af
--- /dev/null
+++ b/target/linux/generic/patches-4.4/101-MIPS-fix-cache-flushing-for-highmem-pages.patch
@@ -0,0 +1,95 @@
+From: Felix Fietkau <nbd@openwrt.org>
+Date: Sun, 24 Jan 2016 01:03:51 +0100
+Subject: [PATCH] MIPS: fix cache flushing for highmem pages
+
+Most cache flush ops were no-op for highmem pages. This led to nasty
+segfaults and (in the case of page_address(page) == NULL) kernel
+crashes.
+
+Fix this by always flushing highmem pages using kmap/kunmap_atomic
+around the actual cache flush. This might be a bit inefficient, but at
+least it's stable.
+
+Signed-off-by: Felix Fietkau <nbd@openwrt.org>
+---
+
+--- a/arch/mips/mm/cache.c
++++ b/arch/mips/mm/cache.c
+@@ -14,6 +14,7 @@
+ #include <linux/sched.h>
+ #include <linux/syscalls.h>
+ #include <linux/mm.h>
++#include <linux/highmem.h>
+
+ #include <asm/cacheflush.h>
+ #include <asm/processor.h>
+@@ -78,18 +79,29 @@ SYSCALL_DEFINE3(cacheflush, unsigned lon
+ return 0;
+ }
+
++static void
++flush_highmem_page(struct page *page)
++{
++ void *addr = kmap_atomic(page);
++ flush_data_cache_page((unsigned long)addr);
++ kunmap_atomic(addr);
++}
++
+ void __flush_dcache_page(struct page *page)
+ {
+ struct address_space *mapping = page_mapping(page);
+ unsigned long addr;
+
+- if (PageHighMem(page))
+- return;
+ if (mapping && !mapping_mapped(mapping)) {
+ SetPageDcacheDirty(page);
+ return;
+ }
+
++ if (PageHighMem(page)) {
++ flush_highmem_page(page);
++ return;
++ }
++
+ /*
+ * We could delay the flush for the !page_mapping case too. But that
+ * case is for exec env/arg pages and those are %99 certainly going to
+@@ -105,6 +117,11 @@ void __flush_anon_page(struct page *page
+ {
+ unsigned long addr = (unsigned long) page_address(page);
+
++ if (PageHighMem(page)) {
++ flush_highmem_page(page);
++ return;
++ }
++
+ if (pages_do_alias(addr, vmaddr)) {
+ if (page_mapped(page) && !Page_dcache_dirty(page)) {
+ void *kaddr;
+@@ -123,8 +140,10 @@ void __flush_icache_page(struct vm_area_
+ {
+ unsigned long addr;
+
+- if (PageHighMem(page))
++ if (PageHighMem(page)) {
++ flush_highmem_page(page);
+ return;
++ }
+
+ addr = (unsigned long) page_address(page);
+ flush_data_cache_page(addr);
+@@ -142,7 +161,12 @@ void __update_cache(struct vm_area_struc
+ if (unlikely(!pfn_valid(pfn)))
+ return;
+ page = pfn_to_page(pfn);
+- if (page_mapping(page) && Page_dcache_dirty(page)) {
++ if (!Page_dcache_dirty(page))
++ return;
++
++ if (PageHighMem(page)) {
++ flush_highmem_page(page);
++ } else if (page_mapping(page)) {
+ addr = (unsigned long) page_address(page);
+ if (exec || pages_do_alias(addr, address & PAGE_MASK))
+ flush_data_cache_page(addr);