ioremap.c 4.24 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10
/*
 *  linux/arch/arm/mm/ioremap.c
 *
 * Re-map IO memory to kernel address space so that we can access it.
 *
 * (C) Copyright 1995 1996 Linus Torvalds
 *
 * Hacked for ARM by Phil Blundell <philb@gnu.org>
 * Hacked to allow all architectures to build, and various cleanups
 * by Russell King
11
 *
Linus Torvalds's avatar
Linus Torvalds committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 * This allows a driver to remap an arbitrary region of bus memory into
 * virtual space.  One should *only* use readl, writel, memcpy_toio and
 * so on with such remapped areas.
 *
 * Because the ARM only has a 32-bit address space we can't address the
 * whole of the (physical) PCI space at once.  PCI huge-mode addressing
 * allows us to circumvent this restriction by splitting PCI space into
 * two 2GB chunks and mapping only one at a time into processor memory.
 * We use MMU protection domains to trap any attempt to access the bank
 * that is not currently mapped.  (This isn't fully implemented yet.)
 */
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>

#include <asm/page.h>
#include <asm/pgalloc.h>
#include <asm/io.h>
30
#include <asm/tlbflush.h>
Linus Torvalds's avatar
Linus Torvalds committed
31 32 33 34 35 36 37 38 39 40

static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
	unsigned long phys_addr, pgprot_t pgprot)
{
	unsigned long end;

	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
Linus Torvalds's avatar
Linus Torvalds committed
41 42
	if (address >= end)
		BUG();
Linus Torvalds's avatar
Linus Torvalds committed
43
	do {
Linus Torvalds's avatar
Linus Torvalds committed
44
		if (!pte_none(*pte)) {
Linus Torvalds's avatar
Linus Torvalds committed
45
			printk("remap_area_pte: page already exists\n");
Linus Torvalds's avatar
Linus Torvalds committed
46 47
			BUG();
		}
48
		set_pte(pte, pfn_pte(phys_addr >> PAGE_SHIFT, pgprot));
Linus Torvalds's avatar
Linus Torvalds committed
49 50 51
		address += PAGE_SIZE;
		phys_addr += PAGE_SIZE;
		pte++;
Linus Torvalds's avatar
Linus Torvalds committed
52
	} while (address && (address < end));
Linus Torvalds's avatar
Linus Torvalds committed
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
}

static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
	unsigned long phys_addr, unsigned long flags)
{
	unsigned long end;
	pgprot_t pgprot;

	address &= ~PGDIR_MASK;
	end = address + size;

	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;

	phys_addr -= address;
Linus Torvalds's avatar
Linus Torvalds committed
68 69 70
	if (address >= end)
		BUG();

Linus Torvalds's avatar
Linus Torvalds committed
71 72
	pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | flags);
	do {
73
		pte_t * pte = pte_alloc_kernel(&init_mm, pmd, address);
Linus Torvalds's avatar
Linus Torvalds committed
74 75 76 77 78
		if (!pte)
			return -ENOMEM;
		remap_area_pte(pte, address, end - address, address + phys_addr, pgprot);
		address = (address + PMD_SIZE) & PMD_MASK;
		pmd++;
Linus Torvalds's avatar
Linus Torvalds committed
79
	} while (address && (address < end));
Linus Torvalds's avatar
Linus Torvalds committed
80 81 82 83 84 85
	return 0;
}

static int remap_area_pages(unsigned long address, unsigned long phys_addr,
				 unsigned long size, unsigned long flags)
{
Linus Torvalds's avatar
Linus Torvalds committed
86
	int error;
Linus Torvalds's avatar
Linus Torvalds committed
87 88 89 90 91 92
	pgd_t * dir;
	unsigned long end = address + size;

	phys_addr -= address;
	dir = pgd_offset(&init_mm, address);
	flush_cache_all();
Linus Torvalds's avatar
Linus Torvalds committed
93 94 95 96 97 98 99
	if (address >= end)
		BUG();
	spin_lock(&init_mm.page_table_lock);
	do {
		pmd_t *pmd;
		pmd = pmd_alloc(&init_mm, dir, address);
		error = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
100
		if (!pmd)
Linus Torvalds's avatar
Linus Torvalds committed
101
			break;
Linus Torvalds's avatar
Linus Torvalds committed
102 103
		if (remap_area_pmd(pmd, address, end - address,
					 phys_addr + address, flags))
Linus Torvalds's avatar
Linus Torvalds committed
104 105
			break;
		error = 0;
Linus Torvalds's avatar
Linus Torvalds committed
106 107
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
Linus Torvalds's avatar
Linus Torvalds committed
108 109
	} while (address && (address < end));
	spin_unlock(&init_mm.page_table_lock);
Linus Torvalds's avatar
Linus Torvalds committed
110
	flush_tlb_all();
Linus Torvalds's avatar
Linus Torvalds committed
111
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125
}

/*
 * 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.
 */
126 127 128
void *
__ioremap(unsigned long phys_addr, size_t size, unsigned long flags,
	  unsigned long align)
Linus Torvalds's avatar
Linus Torvalds committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
{
	void * addr;
	struct vm_struct * area;
	unsigned long offset, last_addr;

	/* Don't allow wraparound or zero size */
	last_addr = phys_addr + size - 1;
	if (!size || last_addr < phys_addr)
		return NULL;

	/*
	 * Mappings have to be page-aligned
	 */
	offset = phys_addr & ~PAGE_MASK;
	phys_addr &= PAGE_MASK;
	size = PAGE_ALIGN(last_addr) - phys_addr;

	/*
	 * Ok, go for it..
	 */
	area = get_vm_area(size, VM_IOREMAP);
	if (!area)
		return NULL;
	addr = area->addr;
153
	if (remap_area_pages((unsigned long) addr, phys_addr, size, flags)) {
Linus Torvalds's avatar
Linus Torvalds committed
154 155 156 157 158 159 160 161 162 163
		vfree(addr);
		return NULL;
	}
	return (void *) (offset + (char *)addr);
}

void __iounmap(void *addr)
{
	vfree((void *) (PAGE_MASK & (unsigned long) addr));
}