11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * linux/arch/arm/mm/ioremap.c 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * Re-map IO memory to kernel address space so that we can access it. 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * (C) Copyright 1995 1996 Linus Torvalds 71da177e4SLinus Torvalds * 81da177e4SLinus Torvalds * Hacked for ARM by Phil Blundell <philb@gnu.org> 91da177e4SLinus Torvalds * Hacked to allow all architectures to build, and various cleanups 101da177e4SLinus Torvalds * by Russell King 111da177e4SLinus Torvalds * 121da177e4SLinus Torvalds * This allows a driver to remap an arbitrary region of bus memory into 131da177e4SLinus Torvalds * virtual space. One should *only* use readl, writel, memcpy_toio and 141da177e4SLinus Torvalds * so on with such remapped areas. 151da177e4SLinus Torvalds * 161da177e4SLinus Torvalds * Because the ARM only has a 32-bit address space we can't address the 171da177e4SLinus Torvalds * whole of the (physical) PCI space at once. PCI huge-mode addressing 181da177e4SLinus Torvalds * allows us to circumvent this restriction by splitting PCI space into 191da177e4SLinus Torvalds * two 2GB chunks and mapping only one at a time into processor memory. 201da177e4SLinus Torvalds * We use MMU protection domains to trap any attempt to access the bank 211da177e4SLinus Torvalds * that is not currently mapped. (This isn't fully implemented yet.) 221da177e4SLinus Torvalds */ 231da177e4SLinus Torvalds #include <linux/module.h> 241da177e4SLinus Torvalds #include <linux/errno.h> 251da177e4SLinus Torvalds #include <linux/mm.h> 261da177e4SLinus Torvalds #include <linux/vmalloc.h> 271da177e4SLinus Torvalds 281da177e4SLinus Torvalds #include <asm/cacheflush.h> 291da177e4SLinus Torvalds #include <asm/io.h> 301da177e4SLinus Torvalds #include <asm/tlbflush.h> 311da177e4SLinus Torvalds 321da177e4SLinus Torvalds static inline void 331da177e4SLinus Torvalds remap_area_pte(pte_t * pte, unsigned long address, unsigned long size, 341da177e4SLinus Torvalds unsigned long phys_addr, pgprot_t pgprot) 351da177e4SLinus Torvalds { 361da177e4SLinus Torvalds unsigned long end; 371da177e4SLinus Torvalds 381da177e4SLinus Torvalds address &= ~PMD_MASK; 391da177e4SLinus Torvalds end = address + size; 401da177e4SLinus Torvalds if (end > PMD_SIZE) 411da177e4SLinus Torvalds end = PMD_SIZE; 421da177e4SLinus Torvalds BUG_ON(address >= end); 431da177e4SLinus Torvalds do { 441da177e4SLinus Torvalds if (!pte_none(*pte)) 451da177e4SLinus Torvalds goto bad; 461da177e4SLinus Torvalds 471da177e4SLinus Torvalds set_pte(pte, pfn_pte(phys_addr >> PAGE_SHIFT, pgprot)); 481da177e4SLinus Torvalds address += PAGE_SIZE; 491da177e4SLinus Torvalds phys_addr += PAGE_SIZE; 501da177e4SLinus Torvalds pte++; 511da177e4SLinus Torvalds } while (address && (address < end)); 521da177e4SLinus Torvalds return; 531da177e4SLinus Torvalds 541da177e4SLinus Torvalds bad: 551da177e4SLinus Torvalds printk("remap_area_pte: page already exists\n"); 561da177e4SLinus Torvalds BUG(); 571da177e4SLinus Torvalds } 581da177e4SLinus Torvalds 591da177e4SLinus Torvalds static inline int 601da177e4SLinus Torvalds remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size, 611da177e4SLinus Torvalds unsigned long phys_addr, unsigned long flags) 621da177e4SLinus Torvalds { 631da177e4SLinus Torvalds unsigned long end; 641da177e4SLinus Torvalds pgprot_t pgprot; 651da177e4SLinus Torvalds 661da177e4SLinus Torvalds address &= ~PGDIR_MASK; 671da177e4SLinus Torvalds end = address + size; 681da177e4SLinus Torvalds 691da177e4SLinus Torvalds if (end > PGDIR_SIZE) 701da177e4SLinus Torvalds end = PGDIR_SIZE; 711da177e4SLinus Torvalds 721da177e4SLinus Torvalds phys_addr -= address; 731da177e4SLinus Torvalds BUG_ON(address >= end); 741da177e4SLinus Torvalds 751da177e4SLinus Torvalds pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | flags); 761da177e4SLinus Torvalds do { 771da177e4SLinus Torvalds pte_t * pte = pte_alloc_kernel(&init_mm, pmd, address); 781da177e4SLinus Torvalds if (!pte) 791da177e4SLinus Torvalds return -ENOMEM; 801da177e4SLinus Torvalds remap_area_pte(pte, address, end - address, address + phys_addr, pgprot); 811da177e4SLinus Torvalds address = (address + PMD_SIZE) & PMD_MASK; 821da177e4SLinus Torvalds pmd++; 831da177e4SLinus Torvalds } while (address && (address < end)); 841da177e4SLinus Torvalds return 0; 851da177e4SLinus Torvalds } 861da177e4SLinus Torvalds 871da177e4SLinus Torvalds static int 881da177e4SLinus Torvalds remap_area_pages(unsigned long start, unsigned long phys_addr, 891da177e4SLinus Torvalds unsigned long size, unsigned long flags) 901da177e4SLinus Torvalds { 911da177e4SLinus Torvalds unsigned long address = start; 921da177e4SLinus Torvalds unsigned long end = start + size; 931da177e4SLinus Torvalds int err = 0; 941da177e4SLinus Torvalds pgd_t * dir; 951da177e4SLinus Torvalds 961da177e4SLinus Torvalds phys_addr -= address; 971da177e4SLinus Torvalds dir = pgd_offset(&init_mm, address); 981da177e4SLinus Torvalds BUG_ON(address >= end); 991da177e4SLinus Torvalds spin_lock(&init_mm.page_table_lock); 1001da177e4SLinus Torvalds do { 1011da177e4SLinus Torvalds pmd_t *pmd = pmd_alloc(&init_mm, dir, address); 1021da177e4SLinus Torvalds if (!pmd) { 1031da177e4SLinus Torvalds err = -ENOMEM; 1041da177e4SLinus Torvalds break; 1051da177e4SLinus Torvalds } 1061da177e4SLinus Torvalds if (remap_area_pmd(pmd, address, end - address, 1071da177e4SLinus Torvalds phys_addr + address, flags)) { 1081da177e4SLinus Torvalds err = -ENOMEM; 1091da177e4SLinus Torvalds break; 1101da177e4SLinus Torvalds } 1111da177e4SLinus Torvalds 1121da177e4SLinus Torvalds address = (address + PGDIR_SIZE) & PGDIR_MASK; 1131da177e4SLinus Torvalds dir++; 1141da177e4SLinus Torvalds } while (address && (address < end)); 1151da177e4SLinus Torvalds 1161da177e4SLinus Torvalds spin_unlock(&init_mm.page_table_lock); 1171da177e4SLinus Torvalds flush_cache_vmap(start, end); 1181da177e4SLinus Torvalds return err; 1191da177e4SLinus Torvalds } 1201da177e4SLinus Torvalds 1211da177e4SLinus Torvalds /* 1221da177e4SLinus Torvalds * Remap an arbitrary physical address space into the kernel virtual 1231da177e4SLinus Torvalds * address space. Needed when the kernel wants to access high addresses 1241da177e4SLinus Torvalds * directly. 1251da177e4SLinus Torvalds * 1261da177e4SLinus Torvalds * NOTE! We need to allow non-page-aligned mappings too: we will obviously 1271da177e4SLinus Torvalds * have to convert them into an offset in a page-aligned mapping, but the 1281da177e4SLinus Torvalds * caller shouldn't need to know that small detail. 1291da177e4SLinus Torvalds * 1301da177e4SLinus Torvalds * 'flags' are the extra L_PTE_ flags that you want to specify for this 1311da177e4SLinus Torvalds * mapping. See include/asm-arm/proc-armv/pgtable.h for more information. 1321da177e4SLinus Torvalds */ 1331da177e4SLinus Torvalds void __iomem * 1341da177e4SLinus Torvalds __ioremap(unsigned long phys_addr, size_t size, unsigned long flags, 1351da177e4SLinus Torvalds unsigned long align) 1361da177e4SLinus Torvalds { 1371da177e4SLinus Torvalds void * addr; 1381da177e4SLinus Torvalds struct vm_struct * area; 1391da177e4SLinus Torvalds unsigned long offset, last_addr; 1401da177e4SLinus Torvalds 1411da177e4SLinus Torvalds /* Don't allow wraparound or zero size */ 1421da177e4SLinus Torvalds last_addr = phys_addr + size - 1; 1431da177e4SLinus Torvalds if (!size || last_addr < phys_addr) 1441da177e4SLinus Torvalds return NULL; 1451da177e4SLinus Torvalds 1461da177e4SLinus Torvalds /* 1471da177e4SLinus Torvalds * Mappings have to be page-aligned 1481da177e4SLinus Torvalds */ 1491da177e4SLinus Torvalds offset = phys_addr & ~PAGE_MASK; 1501da177e4SLinus Torvalds phys_addr &= PAGE_MASK; 1511da177e4SLinus Torvalds size = PAGE_ALIGN(last_addr + 1) - phys_addr; 1521da177e4SLinus Torvalds 1531da177e4SLinus Torvalds /* 1541da177e4SLinus Torvalds * Ok, go for it.. 1551da177e4SLinus Torvalds */ 1561da177e4SLinus Torvalds area = get_vm_area(size, VM_IOREMAP); 1571da177e4SLinus Torvalds if (!area) 1581da177e4SLinus Torvalds return NULL; 1591da177e4SLinus Torvalds addr = area->addr; 1601da177e4SLinus Torvalds if (remap_area_pages((unsigned long) addr, phys_addr, size, flags)) { 1611da177e4SLinus Torvalds vfree(addr); 1621da177e4SLinus Torvalds return NULL; 1631da177e4SLinus Torvalds } 1641da177e4SLinus Torvalds return (void __iomem *) (offset + (char *)addr); 1651da177e4SLinus Torvalds } 1661da177e4SLinus Torvalds EXPORT_SYMBOL(__ioremap); 1671da177e4SLinus Torvalds 1681da177e4SLinus Torvalds void __iounmap(void __iomem *addr) 1691da177e4SLinus Torvalds { 1701da177e4SLinus Torvalds vfree((void *) (PAGE_MASK & (unsigned long) addr)); 1711da177e4SLinus Torvalds } 1721da177e4SLinus Torvalds EXPORT_SYMBOL(__iounmap); 173