diff options
Diffstat (limited to 'arch/arm/mm/ioremap.c')
| -rw-r--r-- | arch/arm/mm/ioremap.c | 498 |
1 files changed, 365 insertions, 133 deletions
diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c index de3ce1eec2e..d1e5ad7ab3b 100644 --- a/arch/arm/mm/ioremap.c +++ b/arch/arm/mm/ioremap.c @@ -24,135 +24,317 @@ #include <linux/errno.h> #include <linux/mm.h> #include <linux/vmalloc.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include <asm/cp15.h> +#include <asm/cputype.h> #include <asm/cacheflush.h> -#include <asm/hardware.h> -#include <asm/io.h> +#include <asm/mmu_context.h> +#include <asm/pgalloc.h> #include <asm/tlbflush.h> +#include <asm/system_info.h> -static inline void -remap_area_pte(pte_t * pte, unsigned long address, unsigned long size, - unsigned long phys_addr, pgprot_t pgprot) +#include <asm/mach/map.h> +#include <asm/mach/pci.h> +#include "mm.h" + + +LIST_HEAD(static_vmlist); + +static struct static_vm *find_static_vm_paddr(phys_addr_t paddr, + size_t size, unsigned int mtype) { - unsigned long end; + struct static_vm *svm; + struct vm_struct *vm; - address &= ~PMD_MASK; - end = address + size; - if (end > PMD_SIZE) - end = PMD_SIZE; - BUG_ON(address >= end); - do { - if (!pte_none(*pte)) - goto bad; + list_for_each_entry(svm, &static_vmlist, list) { + vm = &svm->vm; + if (!(vm->flags & VM_ARM_STATIC_MAPPING)) + continue; + if ((vm->flags & VM_ARM_MTYPE_MASK) != VM_ARM_MTYPE(mtype)) + continue; + + if (vm->phys_addr > paddr || + paddr + size - 1 > vm->phys_addr + vm->size - 1) + continue; + + return svm; + } + + return NULL; +} + +struct static_vm *find_static_vm_vaddr(void *vaddr) +{ + struct static_vm *svm; + struct vm_struct *vm; - set_pte(pte, pfn_pte(phys_addr >> PAGE_SHIFT, pgprot)); - address += PAGE_SIZE; - phys_addr += PAGE_SIZE; - pte++; - } while (address && (address < end)); - return; + list_for_each_entry(svm, &static_vmlist, list) { + vm = &svm->vm; + + /* static_vmlist is ascending order */ + if (vm->addr > vaddr) + break; - bad: - printk("remap_area_pte: page already exists\n"); - BUG(); + if (vm->addr <= vaddr && vm->addr + vm->size > vaddr) + return svm; + } + + return NULL; } -static inline int -remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size, - unsigned long phys_addr, unsigned long flags) +void __init add_static_vm_early(struct static_vm *svm) +{ + struct static_vm *curr_svm; + struct vm_struct *vm; + void *vaddr; + + vm = &svm->vm; + vm_area_add_early(vm); + vaddr = vm->addr; + + list_for_each_entry(curr_svm, &static_vmlist, list) { + vm = &curr_svm->vm; + + if (vm->addr > vaddr) + break; + } + list_add_tail(&svm->list, &curr_svm->list); +} + +int ioremap_page(unsigned long virt, unsigned long phys, + const struct mem_type *mtype) +{ + return ioremap_page_range(virt, virt + PAGE_SIZE, phys, + __pgprot(mtype->prot_pte)); +} +EXPORT_SYMBOL(ioremap_page); + +void __check_vmalloc_seq(struct mm_struct *mm) +{ + unsigned int seq; + + do { + seq = init_mm.context.vmalloc_seq; + memcpy(pgd_offset(mm, VMALLOC_START), + pgd_offset_k(VMALLOC_START), + sizeof(pgd_t) * (pgd_index(VMALLOC_END) - + pgd_index(VMALLOC_START))); + mm->context.vmalloc_seq = seq; + } while (seq != init_mm.context.vmalloc_seq); +} + +#if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) +/* + * Section support is unsafe on SMP - If you iounmap and ioremap a region, + * the other CPUs will not see this change until their next context switch. + * Meanwhile, (eg) if an interrupt comes in on one of those other CPUs + * which requires the new ioremap'd region to be referenced, the CPU will + * reference the _old_ region. + * + * Note that get_vm_area_caller() allocates a guard 4K page, so we need to + * mask the size back to 1MB aligned or we will overflow in the loop below. + */ +static void unmap_area_sections(unsigned long virt, unsigned long size) { - unsigned long end; - pgprot_t pgprot; + unsigned long addr = virt, end = virt + (size & ~(SZ_1M - 1)); + pgd_t *pgd; + pud_t *pud; + pmd_t *pmdp; + + flush_cache_vunmap(addr, end); + pgd = pgd_offset_k(addr); + pud = pud_offset(pgd, addr); + pmdp = pmd_offset(pud, addr); + do { + pmd_t pmd = *pmdp; + + if (!pmd_none(pmd)) { + /* + * Clear the PMD from the page table, and + * increment the vmalloc sequence so others + * notice this change. + * + * Note: this is still racy on SMP machines. + */ + pmd_clear(pmdp); + init_mm.context.vmalloc_seq++; + + /* + * Free the page table, if there was one. + */ + if ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_TABLE) + pte_free_kernel(&init_mm, pmd_page_vaddr(pmd)); + } + + addr += PMD_SIZE; + pmdp += 2; + } while (addr < end); + + /* + * Ensure that the active_mm is up to date - we want to + * catch any use-after-iounmap cases. + */ + if (current->active_mm->context.vmalloc_seq != init_mm.context.vmalloc_seq) + __check_vmalloc_seq(current->active_mm); - address &= ~PGDIR_MASK; - end = address + size; + flush_tlb_kernel_range(virt, end); +} - if (end > PGDIR_SIZE) - end = PGDIR_SIZE; +static int +remap_area_sections(unsigned long virt, unsigned long pfn, + size_t size, const struct mem_type *type) +{ + unsigned long addr = virt, end = virt + size; + pgd_t *pgd; + pud_t *pud; + pmd_t *pmd; - phys_addr -= address; - BUG_ON(address >= end); + /* + * Remove and free any PTE-based mapping, and + * sync the current kernel mapping. + */ + unmap_area_sections(virt, size); - pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | flags); + pgd = pgd_offset_k(addr); + pud = pud_offset(pgd, addr); + pmd = pmd_offset(pud, addr); do { - pte_t * pte = pte_alloc_kernel(pmd, address); - if (!pte) - return -ENOMEM; - remap_area_pte(pte, address, end - address, address + phys_addr, pgprot); - address = (address + PMD_SIZE) & PMD_MASK; - pmd++; - } while (address && (address < end)); + pmd[0] = __pmd(__pfn_to_phys(pfn) | type->prot_sect); + pfn += SZ_1M >> PAGE_SHIFT; + pmd[1] = __pmd(__pfn_to_phys(pfn) | type->prot_sect); + pfn += SZ_1M >> PAGE_SHIFT; + flush_pmd_entry(pmd); + + addr += PMD_SIZE; + pmd += 2; + } while (addr < end); + return 0; } static int -remap_area_pages(unsigned long start, unsigned long pfn, - unsigned long size, unsigned long flags) -{ - unsigned long address = start; - unsigned long end = start + size; - unsigned long phys_addr = __pfn_to_phys(pfn); - int err = 0; - pgd_t * dir; - - phys_addr -= address; - dir = pgd_offset(&init_mm, address); - BUG_ON(address >= end); +remap_area_supersections(unsigned long virt, unsigned long pfn, + size_t size, const struct mem_type *type) +{ + unsigned long addr = virt, end = virt + size; + pgd_t *pgd; + pud_t *pud; + pmd_t *pmd; + + /* + * Remove and free any PTE-based mapping, and + * sync the current kernel mapping. + */ + unmap_area_sections(virt, size); + + pgd = pgd_offset_k(virt); + pud = pud_offset(pgd, addr); + pmd = pmd_offset(pud, addr); do { - pmd_t *pmd = pmd_alloc(&init_mm, dir, address); - if (!pmd) { - err = -ENOMEM; - break; - } - if (remap_area_pmd(pmd, address, end - address, - phys_addr + address, flags)) { - err = -ENOMEM; - break; + unsigned long super_pmd_val, i; + + super_pmd_val = __pfn_to_phys(pfn) | type->prot_sect | + PMD_SECT_SUPER; + super_pmd_val |= ((pfn >> (32 - PAGE_SHIFT)) & 0xf) << 20; + + for (i = 0; i < 8; i++) { + pmd[0] = __pmd(super_pmd_val); + pmd[1] = __pmd(super_pmd_val); + flush_pmd_entry(pmd); + + addr += PMD_SIZE; + pmd += 2; } - address = (address + PGDIR_SIZE) & PGDIR_MASK; - dir++; - } while (address && (address < end)); + pfn += SUPERSECTION_SIZE >> PAGE_SHIFT; + } while (addr < end); - flush_cache_vmap(start, end); - return err; + return 0; } +#endif -/* - * Remap an arbitrary physical address space into the kernel virtual - * address space. Needed when the kernel wants to access high addresses - * directly. - * - * NOTE! We need to allow non-page-aligned mappings too: we will obviously - * have to convert them into an offset in a page-aligned mapping, but the - * caller shouldn't need to know that small detail. - * - * 'flags' are the extra L_PTE_ flags that you want to specify for this - * mapping. See include/asm-arm/proc-armv/pgtable.h for more information. - */ -void __iomem * -__ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size, - unsigned long flags) +void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, + unsigned long offset, size_t size, unsigned int mtype, void *caller) { + const struct mem_type *type; + int err; unsigned long addr; - struct vm_struct * area; + struct vm_struct *area; + phys_addr_t paddr = __pfn_to_phys(pfn); + +#ifndef CONFIG_ARM_LPAE + /* + * High mappings must be supersection aligned + */ + if (pfn >= 0x100000 && (paddr & ~SUPERSECTION_MASK)) + return NULL; +#endif - area = get_vm_area(size, VM_IOREMAP); + type = get_mem_type(mtype); + if (!type) + return NULL; + + /* + * Page align the mapping size, taking account of any offset. + */ + size = PAGE_ALIGN(offset + size); + + /* + * Try to reuse one of the static mapping whenever possible. + */ + if (size && !(sizeof(phys_addr_t) == 4 && pfn >= 0x100000)) { + struct static_vm *svm; + + svm = find_static_vm_paddr(paddr, size, mtype); + if (svm) { + addr = (unsigned long)svm->vm.addr; + addr += paddr - svm->vm.phys_addr; + return (void __iomem *) (offset + addr); + } + } + + /* + * Don't allow RAM to be mapped - this causes problems with ARMv6+ + */ + if (WARN_ON(pfn_valid(pfn))) + return NULL; + + area = get_vm_area_caller(size, VM_IOREMAP, caller); if (!area) return NULL; addr = (unsigned long)area->addr; - if (remap_area_pages(addr, pfn, size, flags)) { - vfree(addr); + area->phys_addr = paddr; + +#if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) + if (DOMAIN_IO == 0 && + (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)) || + cpu_is_xsc3()) && pfn >= 0x100000 && + !((paddr | size | addr) & ~SUPERSECTION_MASK)) { + area->flags |= VM_ARM_SECTION_MAPPING; + err = remap_area_supersections(addr, pfn, size, type); + } else if (!((paddr | size | addr) & ~PMD_MASK)) { + area->flags |= VM_ARM_SECTION_MAPPING; + err = remap_area_sections(addr, pfn, size, type); + } else +#endif + err = ioremap_page_range(addr, addr + size, paddr, + __pgprot(type->prot_pte)); + + if (err) { + vunmap((void *)addr); return NULL; } - return (void __iomem *) (offset + (char *)addr); + + flush_cache_vmap(addr, addr + size); + return (void __iomem *) (offset + addr); } -EXPORT_SYMBOL(__ioremap_pfn); -void __iomem * -__ioremap(unsigned long phys_addr, size_t size, unsigned long flags) +void __iomem *__arm_ioremap_caller(phys_addr_t phys_addr, size_t size, + unsigned int mtype, void *caller) { - unsigned long last_addr; + phys_addr_t last_addr; unsigned long offset = phys_addr & ~PAGE_MASK; unsigned long pfn = __phys_to_pfn(phys_addr); @@ -163,64 +345,114 @@ __ioremap(unsigned long phys_addr, size_t size, unsigned long flags) if (!size || last_addr < phys_addr) return NULL; - /* - * Page align the mapping size - */ - size = PAGE_ALIGN(last_addr + 1) - phys_addr; - - return __ioremap_pfn(pfn, offset, size, flags); + return __arm_ioremap_pfn_caller(pfn, offset, size, mtype, + caller); } -EXPORT_SYMBOL(__ioremap); -void __iounmap(void __iomem *addr) +/* + * Remap an arbitrary physical address space into the kernel virtual + * address space. Needed when the kernel wants to access high addresses + * directly. + * + * NOTE! We need to allow non-page-aligned mappings too: we will obviously + * have to convert them into an offset in a page-aligned mapping, but the + * caller shouldn't need to know that small detail. + */ +void __iomem * +__arm_ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size, + unsigned int mtype) { - vfree((void *) (PAGE_MASK & (unsigned long) addr)); + return __arm_ioremap_pfn_caller(pfn, offset, size, mtype, + __builtin_return_address(0)); } -EXPORT_SYMBOL(__iounmap); +EXPORT_SYMBOL(__arm_ioremap_pfn); -#ifdef __io -void __iomem *ioport_map(unsigned long port, unsigned int nr) +void __iomem * (*arch_ioremap_caller)(phys_addr_t, size_t, + unsigned int, void *) = + __arm_ioremap_caller; + +void __iomem * +__arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype) { - return __io(port); + return arch_ioremap_caller(phys_addr, size, mtype, + __builtin_return_address(0)); } -EXPORT_SYMBOL(ioport_map); +EXPORT_SYMBOL(__arm_ioremap); -void ioport_unmap(void __iomem *addr) +/* + * Remap an arbitrary physical address space into the kernel virtual + * address space as memory. Needed when the kernel wants to execute + * code in external memory. This is needed for reprogramming source + * clocks that would affect normal memory for example. Please see + * CONFIG_GENERIC_ALLOCATOR for allocating external memory. + */ +void __iomem * +__arm_ioremap_exec(phys_addr_t phys_addr, size_t size, bool cached) { + unsigned int mtype; + + if (cached) + mtype = MT_MEMORY_RWX; + else + mtype = MT_MEMORY_RWX_NONCACHED; + + return __arm_ioremap_caller(phys_addr, size, mtype, + __builtin_return_address(0)); } -EXPORT_SYMBOL(ioport_unmap); + +void __iounmap(volatile void __iomem *io_addr) +{ + void *addr = (void *)(PAGE_MASK & (unsigned long)io_addr); + struct static_vm *svm; + + /* If this is a static mapping, we must leave it alone */ + svm = find_static_vm_vaddr(addr); + if (svm) + return; + +#if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) + { + struct vm_struct *vm; + + vm = find_vm_area(addr); + + /* + * If this is a section based mapping we need to handle it + * specially as the VM subsystem does not know how to handle + * such a beast. + */ + if (vm && (vm->flags & VM_ARM_SECTION_MAPPING)) + unmap_area_sections((unsigned long)vm->addr, vm->size); + } #endif -#ifdef CONFIG_PCI -#include <linux/pci.h> -#include <linux/ioport.h> + vunmap(addr); +} + +void (*arch_iounmap)(volatile void __iomem *) = __iounmap; -void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen) +void __arm_iounmap(volatile void __iomem *io_addr) { - unsigned long start = pci_resource_start(dev, bar); - unsigned long len = pci_resource_len(dev, bar); - unsigned long flags = pci_resource_flags(dev, bar); + arch_iounmap(io_addr); +} +EXPORT_SYMBOL(__arm_iounmap); - if (!len || !start) - return NULL; - if (maxlen && len > maxlen) - len = maxlen; - if (flags & IORESOURCE_IO) - return ioport_map(start, len); - if (flags & IORESOURCE_MEM) { - if (flags & IORESOURCE_CACHEABLE) - return ioremap(start, len); - return ioremap_nocache(start, len); - } - return NULL; +#ifdef CONFIG_PCI +static int pci_ioremap_mem_type = MT_DEVICE; + +void pci_ioremap_set_mem_type(int mem_type) +{ + pci_ioremap_mem_type = mem_type; } -EXPORT_SYMBOL(pci_iomap); -void pci_iounmap(struct pci_dev *dev, void __iomem *addr) +int pci_ioremap_io(unsigned int offset, phys_addr_t phys_addr) { - if ((unsigned long)addr >= VMALLOC_START && - (unsigned long)addr < VMALLOC_END) - iounmap(addr); + BUG_ON(offset + SZ_64K > IO_SPACE_LIMIT); + + return ioremap_page_range(PCI_IO_VIRT_BASE + offset, + PCI_IO_VIRT_BASE + offset + SZ_64K, + phys_addr, + __pgprot(get_mem_type(pci_ioremap_mem_type)->prot_pte)); } -EXPORT_SYMBOL(pci_iounmap); +EXPORT_SYMBOL_GPL(pci_ioremap_io); #endif |
