ioremap.c 4.5 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5
/*
 * arch/parisc/mm/ioremap.c
 *
 * (C) Copyright 1995 1996 Linus Torvalds
 * (C) Copyright 2001 Helge Deller <deller@gmx.de>
6
 * (C) Copyright 2005 Kyle McMartin <kyle@parisc-linux.org>
L
Linus Torvalds 已提交
7 8 9 10 11 12 13
 */

#include <linux/vmalloc.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/pgalloc.h>
14 15
#include <asm/tlbflush.h>
#include <asm/cacheflush.h>
L
Linus Torvalds 已提交
16

17 18 19
static inline void 
remap_area_pte(pte_t *pte, unsigned long address, unsigned long size,
	       unsigned long phys_addr, unsigned long flags)
L
Linus Torvalds 已提交
20
{
21 22 23
	unsigned long end, pfn;
	pgprot_t pgprot = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY |
				   _PAGE_ACCESSED | flags);
L
Linus Torvalds 已提交
24 25

	address &= ~PMD_MASK;
26

L
Linus Torvalds 已提交
27 28 29
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
30 31 32 33

	BUG_ON(address >= end);

	pfn = phys_addr >> PAGE_SHIFT;
L
Linus Torvalds 已提交
34
	do {
35 36 37 38
		BUG_ON(!pte_none(*pte));

		set_pte(pte, pfn_pte(pfn, pgprot));

L
Linus Torvalds 已提交
39
		address += PAGE_SIZE;
40
		pfn++;
L
Linus Torvalds 已提交
41 42 43 44
		pte++;
	} while (address && (address < end));
}

45 46 47
static inline int 
remap_area_pmd(pmd_t *pmd, unsigned long address, unsigned long size,
	       unsigned long phys_addr, unsigned long flags)
L
Linus Torvalds 已提交
48 49 50 51
{
	unsigned long end;

	address &= ~PGDIR_MASK;
52

L
Linus Torvalds 已提交
53 54 55
	end = address + size;
	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;
56 57 58

	BUG_ON(address >= end);

L
Linus Torvalds 已提交
59 60
	phys_addr -= address;
	do {
61
		pte_t *pte = pte_alloc_kernel(pmd, address);
L
Linus Torvalds 已提交
62 63
		if (!pte)
			return -ENOMEM;
64 65 66 67

		remap_area_pte(pte, address, end - address, 
			       address + phys_addr, flags);

L
Linus Torvalds 已提交
68 69 70
		address = (address + PMD_SIZE) & PMD_MASK;
		pmd++;
	} while (address && (address < end));
71

L
Linus Torvalds 已提交
72 73 74
	return 0;
}

75 76 77
static int 
remap_area_pages(unsigned long address, unsigned long phys_addr,
		 unsigned long size, unsigned long flags)
L
Linus Torvalds 已提交
78
{
79 80
	pgd_t *dir;
	int error = 0;
L
Linus Torvalds 已提交
81 82
	unsigned long end = address + size;

83 84
	BUG_ON(address >= end);

L
Linus Torvalds 已提交
85
	phys_addr -= address;
86 87
	dir = pgd_offset_k(address);

L
Linus Torvalds 已提交
88
	flush_cache_all();
89

L
Linus Torvalds 已提交
90
	do {
91
		pud_t *pud;
L
Linus Torvalds 已提交
92
		pmd_t *pmd;
93

L
Linus Torvalds 已提交
94
		error = -ENOMEM;
95 96 97 98 99
		pud = pud_alloc(&init_mm, dir, address);
		if (!pud)
			break;

		pmd = pmd_alloc(&init_mm, pud, address);
L
Linus Torvalds 已提交
100 101
		if (!pmd)
			break;
102

L
Linus Torvalds 已提交
103
		if (remap_area_pmd(pmd, address, end - address,
104
				   phys_addr + address, flags))
L
Linus Torvalds 已提交
105
			break;
106

L
Linus Torvalds 已提交
107 108 109 110
		error = 0;
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (address && (address < end));
111

L
Linus Torvalds 已提交
112
	flush_tlb_all();
113

L
Linus Torvalds 已提交
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
	return error;
}

#ifdef CONFIG_DEBUG_IOREMAP
static unsigned long last = 0;

void gsc_bad_addr(unsigned long addr)
{
	if (time_after(jiffies, last + HZ*10)) {
		printk("gsc_foo() called with bad address 0x%lx\n", addr);
		dump_stack();
		last = jiffies;
	}
}
EXPORT_SYMBOL(gsc_bad_addr);

void __raw_bad_addr(const volatile void __iomem *addr)
{
	if (time_after(jiffies, last + HZ*10)) {
		printk("__raw_foo() called with bad address 0x%p\n", addr);
		dump_stack();
		last = jiffies;
	}
}
EXPORT_SYMBOL(__raw_bad_addr);
#endif

/*
 * Generic mapping function (not visible outside):
 */

/*
 * Remap an arbitrary physical address space into the kernel virtual
147
 * address space.
L
Linus Torvalds 已提交
148 149 150 151 152 153 154
 *
 * 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 * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
155 156
#ifdef CONFIG_EISA
	#error FIXME.
L
Linus Torvalds 已提交
157 158 159 160 161 162 163 164
	unsigned long end = phys_addr + size - 1;
	/* Support EISA addresses */
	if ((phys_addr >= 0x00080000 && end < 0x000fffff)
			|| (phys_addr >= 0x00500000 && end < 0x03bfffff)) {
		phys_addr |= 0xfc000000;
	}
#endif

165 166
	void *addr;
	struct vm_struct *area;
L
Linus Torvalds 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
	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;

	/*
	 * Don't allow anybody to remap normal RAM that we're using..
	 */
	if (phys_addr < virt_to_phys(high_memory)) {
		char *t_addr, *t_end;
		struct page *page;

		t_addr = __va(phys_addr);
		t_end = t_addr + (size - 1);
	   
184 185
		for (page = virt_to_page(t_addr); 
		     page <= virt_to_page(t_end); page++) {
L
Linus Torvalds 已提交
186 187
			if(!PageReserved(page))
				return NULL;
188
		}
L
Linus Torvalds 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
	}

	/*
	 * 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;
204

L
Linus Torvalds 已提交
205 206 207 208 209
	addr = area->addr;
	if (remap_area_pages((unsigned long) addr, phys_addr, size, flags)) {
		vfree(addr);
		return NULL;
	}
210

L
Linus Torvalds 已提交
211 212 213 214 215 216 217 218
	return (void __iomem *) (offset + (char *)addr);
}

void iounmap(void __iomem *addr)
{
	if (addr > high_memory)
		return vfree((void *) (PAGE_MASK & (unsigned long __force) addr));
}