]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ARM: 5691/1: fix cache aliasing issues between kmap() and kmap_atomic() with highmem
authorNicolas Pitre <nico@cam.org>
Thu, 3 Sep 2009 20:45:59 +0000 (21:45 +0100)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 24 Sep 2009 15:27:14 +0000 (08:27 -0700)
commit 7929eb9cf643ae416e5081b2a6fa558d37b9854c upstream.

Let's suppose a highmem page is kmap'd with kmap().  A pkmap entry is
used, the page mapped to it, and the virtual cache is dirtied.  Then
kunmap() is used which does virtually nothing except for decrementing a
usage count.

Then, let's suppose the _same_ page gets mapped using kmap_atomic().
It is therefore mapped onto a fixmap entry instead, which has a
different virtual address unaware of the dirty cache data for that page
sitting in the pkmap mapping.

Fortunately it is easy to know if a pkmap mapping still exists for that
page and use it directly with kmap_atomic(), thanks to kmap_high_get().

And actual testing with a printk in the added code path shows that this
condition is actually met *extremely* frequently.  Seems that we've been
quite lucky that things have worked so well with highmem so far.

Signed-off-by: Nicolas Pitre <nico@marvell.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
arch/arm/mm/highmem.c

index a34954d9df7daa3763b589d36eced9271c513b55..73cae57fa707db6ce286e3945420c88601dd8a67 100644 (file)
@@ -40,11 +40,16 @@ void *kmap_atomic(struct page *page, enum km_type type)
 {
        unsigned int idx;
        unsigned long vaddr;
+       void *kmap;
 
        pagefault_disable();
        if (!PageHighMem(page))
                return page_address(page);
 
+       kmap = kmap_high_get(page);
+       if (kmap)
+               return kmap;
+
        idx = type + KM_TYPE_NR * smp_processor_id();
        vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
 #ifdef CONFIG_DEBUG_HIGHMEM
@@ -80,6 +85,9 @@ void kunmap_atomic(void *kvaddr, enum km_type type)
 #else
                (void) idx;  /* to kill a warning */
 #endif
+       } else if (vaddr >= PKMAP_ADDR(0) && vaddr < PKMAP_ADDR(LAST_PKMAP)) {
+               /* this address was obtained through kmap_high_get() */
+               kunmap_high(pte_page(pkmap_page_table[PKMAP_NR(vaddr)]));
        }
        pagefault_enable();
 }