x86, ioremap: Fix incorrect physical address handling in PAE mode
authorKenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Fri, 18 Jun 2010 03:22:40 +0000 (12:22 +0900)
committerH. Peter Anvin <hpa@linux.intel.com>
Fri, 9 Jul 2010 18:42:03 +0000 (11:42 -0700)
Current x86 ioremap() doesn't handle physical address higher than
32-bit properly in X86_32 PAE mode. When physical address higher than
32-bit is passed to ioremap(), higher 32-bits in physical address is
cleared wrongly. Due to this bug, ioremap() can map wrong address to
linear address space.

In my case, 64-bit MMIO region was assigned to a PCI device (ioat
device) on my system. Because of the ioremap()'s bug, wrong physical
address (instead of MMIO region) was mapped to linear address space.
Because of this, loading ioatdma driver caused unexpected behavior
(kernel panic, kernel hangup, ...).

Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
LKML-Reference: <4C1AE680.7090408@jp.fujitsu.com>
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
arch/x86/mm/ioremap.c
include/linux/io.h
include/linux/vmalloc.h
lib/ioremap.c
mm/vmalloc.c

index 12e4d2d3c1105e24990808cd898897bdefebb403..754cb4cbce66206196c8bacdf09caf9eb13623d4 100644 (file)
@@ -62,8 +62,8 @@ int ioremap_change_attr(unsigned long vaddr, unsigned long size,
 static void __iomem *__ioremap_caller(resource_size_t phys_addr,
                unsigned long size, unsigned long prot_val, void *caller)
 {
-       unsigned long pfn, offset, vaddr;
-       resource_size_t last_addr;
+       unsigned long offset, vaddr;
+       resource_size_t pfn, last_pfn, last_addr;
        const resource_size_t unaligned_phys_addr = phys_addr;
        const unsigned long unaligned_size = size;
        struct vm_struct *area;
@@ -100,10 +100,8 @@ static void __iomem *__ioremap_caller(resource_size_t phys_addr,
        /*
         * Don't allow anybody to remap normal RAM that we're using..
         */
-       for (pfn = phys_addr >> PAGE_SHIFT;
-                               (pfn << PAGE_SHIFT) < (last_addr & PAGE_MASK);
-                               pfn++) {
-
+       last_pfn = last_addr >> PAGE_SHIFT;
+       for (pfn = phys_addr >> PAGE_SHIFT; pfn < last_pfn; pfn++) {
                int is_ram = page_is_ram(pfn);
 
                if (is_ram && pfn_valid(pfn) && !PageReserved(pfn_to_page(pfn)))
@@ -115,7 +113,7 @@ static void __iomem *__ioremap_caller(resource_size_t phys_addr,
         * Mappings have to be page-aligned
         */
        offset = phys_addr & ~PAGE_MASK;
-       phys_addr &= PAGE_MASK;
+       phys_addr &= PHYSICAL_PAGE_MASK;
        size = PAGE_ALIGN(last_addr+1) - phys_addr;
 
        retval = reserve_memtype(phys_addr, (u64)phys_addr + size,
index 6c7f0ba0d5faf473b6c2ffb60d1a0c957c007059..7fd2d2138bf3de8130e5660922a32fe81a47f22b 100644 (file)
@@ -29,10 +29,10 @@ void __iowrite64_copy(void __iomem *to, const void *from, size_t count);
 
 #ifdef CONFIG_MMU
 int ioremap_page_range(unsigned long addr, unsigned long end,
-                      unsigned long phys_addr, pgprot_t prot);
+                      phys_addr_t phys_addr, pgprot_t prot);
 #else
 static inline int ioremap_page_range(unsigned long addr, unsigned long end,
-                                    unsigned long phys_addr, pgprot_t prot)
+                                    phys_addr_t phys_addr, pgprot_t prot)
 {
        return 0;
 }
index 227c2a585e4f326a4e3355fc01f34a1eb4901b03..de05e96e0a70587efd6a3043852c610796a1a140 100644 (file)
@@ -30,7 +30,7 @@ struct vm_struct {
        unsigned long           flags;
        struct page             **pages;
        unsigned int            nr_pages;
-       unsigned long           phys_addr;
+       phys_addr_t             phys_addr;
        void                    *caller;
 };
 
index 14c6078f17a2a2aa26da4ac638da4a7e966649ed..5730ecd3eb6678aca50355955955f0cf5ea5ee38 100644 (file)
 #include <asm/pgtable.h>
 
 static int ioremap_pte_range(pmd_t *pmd, unsigned long addr,
-               unsigned long end, unsigned long phys_addr, pgprot_t prot)
+               unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
 {
        pte_t *pte;
-       unsigned long pfn;
+       u64 pfn;
 
        pfn = phys_addr >> PAGE_SHIFT;
        pte = pte_alloc_kernel(pmd, addr);
@@ -31,7 +31,7 @@ static int ioremap_pte_range(pmd_t *pmd, unsigned long addr,
 }
 
 static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr,
-               unsigned long end, unsigned long phys_addr, pgprot_t prot)
+               unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
 {
        pmd_t *pmd;
        unsigned long next;
@@ -49,7 +49,7 @@ static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr,
 }
 
 static inline int ioremap_pud_range(pgd_t *pgd, unsigned long addr,
-               unsigned long end, unsigned long phys_addr, pgprot_t prot)
+               unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
 {
        pud_t *pud;
        unsigned long next;
@@ -67,7 +67,7 @@ static inline int ioremap_pud_range(pgd_t *pgd, unsigned long addr,
 }
 
 int ioremap_page_range(unsigned long addr,
-                      unsigned long end, unsigned long phys_addr, pgprot_t prot)
+                      unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
 {
        pgd_t *pgd;
        unsigned long start;
index ae007462b7f6e8c2463d83edc2d202107f996a32..b7e314b1009f62e8c723064fc28b4e7a1528e275 100644 (file)
@@ -2403,7 +2403,7 @@ static int s_show(struct seq_file *m, void *p)
                seq_printf(m, " pages=%d", v->nr_pages);
 
        if (v->phys_addr)
-               seq_printf(m, " phys=%lx", v->phys_addr);
+               seq_printf(m, " phys=%llx", (unsigned long long)v->phys_addr);
 
        if (v->flags & VM_IOREMAP)
                seq_printf(m, " ioremap");