Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86, ioremap: Fix incorrect physical address handling in PAE mode

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>

authored by

Kenji Kaneshige and committed by
H. Peter Anvin
ffa71f33 d7a0380d

+14 -16
+5 -7
arch/x86/mm/ioremap.c
··· 62 62 static void __iomem *__ioremap_caller(resource_size_t phys_addr, 63 63 unsigned long size, unsigned long prot_val, void *caller) 64 64 { 65 - unsigned long pfn, offset, vaddr; 66 - resource_size_t last_addr; 65 + unsigned long offset, vaddr; 66 + resource_size_t pfn, last_pfn, last_addr; 67 67 const resource_size_t unaligned_phys_addr = phys_addr; 68 68 const unsigned long unaligned_size = size; 69 69 struct vm_struct *area; ··· 100 100 /* 101 101 * Don't allow anybody to remap normal RAM that we're using.. 102 102 */ 103 - for (pfn = phys_addr >> PAGE_SHIFT; 104 - (pfn << PAGE_SHIFT) < (last_addr & PAGE_MASK); 105 - pfn++) { 106 - 103 + last_pfn = last_addr >> PAGE_SHIFT; 104 + for (pfn = phys_addr >> PAGE_SHIFT; pfn < last_pfn; pfn++) { 107 105 int is_ram = page_is_ram(pfn); 108 106 109 107 if (is_ram && pfn_valid(pfn) && !PageReserved(pfn_to_page(pfn))) ··· 113 115 * Mappings have to be page-aligned 114 116 */ 115 117 offset = phys_addr & ~PAGE_MASK; 116 - phys_addr &= PAGE_MASK; 118 + phys_addr &= PHYSICAL_PAGE_MASK; 117 119 size = PAGE_ALIGN(last_addr+1) - phys_addr; 118 120 119 121 retval = reserve_memtype(phys_addr, (u64)phys_addr + size,
+2 -2
include/linux/io.h
··· 29 29 30 30 #ifdef CONFIG_MMU 31 31 int ioremap_page_range(unsigned long addr, unsigned long end, 32 - unsigned long phys_addr, pgprot_t prot); 32 + phys_addr_t phys_addr, pgprot_t prot); 33 33 #else 34 34 static inline int ioremap_page_range(unsigned long addr, unsigned long end, 35 - unsigned long phys_addr, pgprot_t prot) 35 + phys_addr_t phys_addr, pgprot_t prot) 36 36 { 37 37 return 0; 38 38 }
+1 -1
include/linux/vmalloc.h
··· 30 30 unsigned long flags; 31 31 struct page **pages; 32 32 unsigned int nr_pages; 33 - unsigned long phys_addr; 33 + phys_addr_t phys_addr; 34 34 void *caller; 35 35 }; 36 36
+5 -5
lib/ioremap.c
··· 13 13 #include <asm/pgtable.h> 14 14 15 15 static int ioremap_pte_range(pmd_t *pmd, unsigned long addr, 16 - unsigned long end, unsigned long phys_addr, pgprot_t prot) 16 + unsigned long end, phys_addr_t phys_addr, pgprot_t prot) 17 17 { 18 18 pte_t *pte; 19 - unsigned long pfn; 19 + u64 pfn; 20 20 21 21 pfn = phys_addr >> PAGE_SHIFT; 22 22 pte = pte_alloc_kernel(pmd, addr); ··· 31 31 } 32 32 33 33 static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr, 34 - unsigned long end, unsigned long phys_addr, pgprot_t prot) 34 + unsigned long end, phys_addr_t phys_addr, pgprot_t prot) 35 35 { 36 36 pmd_t *pmd; 37 37 unsigned long next; ··· 49 49 } 50 50 51 51 static inline int ioremap_pud_range(pgd_t *pgd, unsigned long addr, 52 - unsigned long end, unsigned long phys_addr, pgprot_t prot) 52 + unsigned long end, phys_addr_t phys_addr, pgprot_t prot) 53 53 { 54 54 pud_t *pud; 55 55 unsigned long next; ··· 67 67 } 68 68 69 69 int ioremap_page_range(unsigned long addr, 70 - unsigned long end, unsigned long phys_addr, pgprot_t prot) 70 + unsigned long end, phys_addr_t phys_addr, pgprot_t prot) 71 71 { 72 72 pgd_t *pgd; 73 73 unsigned long start;
+1 -1
mm/vmalloc.c
··· 2403 2403 seq_printf(m, " pages=%d", v->nr_pages); 2404 2404 2405 2405 if (v->phys_addr) 2406 - seq_printf(m, " phys=%lx", v->phys_addr); 2406 + seq_printf(m, " phys=%llx", (unsigned long long)v->phys_addr); 2407 2407 2408 2408 if (v->flags & VM_IOREMAP) 2409 2409 seq_printf(m, " ioremap");