ioremap.c 4.06 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * arch/cris/mm/ioremap.c
 *
 * Re-map IO memory to kernel address space so that we can access it.
 * Needed for memory-mapped I/O devices mapped outside our normal DRAM
 * window (that is, all memory-mapped I/O devices).
 *
 * (C) Copyright 1995 1996 Linus Torvalds
 * CRIS-port by Axis Communications AB
 */

#include <linux/vmalloc.h>
#include <asm/io.h>
#include <asm/pgalloc.h>

static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
	unsigned long phys_addr, unsigned long flags)
{
	unsigned long end;
20
	unsigned long pfn;
Linus Torvalds's avatar
Linus Torvalds committed
21 22 23 24 25 26 27

	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	if (address >= end)
		BUG();
28
	pfn = phys_addr >> PAGE_SHIFT;
Linus Torvalds's avatar
Linus Torvalds committed
29 30 31 32 33
	do {
		if (!pte_none(*pte)) {
			printk("remap_area_pte: page already exists\n");
			BUG();
		}
34 35 36
		set_pte(pte, pfn_pte(pfn, __pgprot(_PAGE_PRESENT | __READABLE | 
						   __WRITEABLE | _PAGE_GLOBAL |
						   _PAGE_KERNEL | flags)));
Linus Torvalds's avatar
Linus Torvalds committed
37
		address += PAGE_SIZE;
38
		pfn++;
Linus Torvalds's avatar
Linus Torvalds committed
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
		pte++;
	} while (address && (address < end));
}

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;

	address &= ~PGDIR_MASK;
	end = address + size;
	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;
	phys_addr -= address;
	if (address >= end)
		BUG();
	do {
Linus Torvalds's avatar
Linus Torvalds committed
56
		pte_t * pte = pte_alloc(&init_mm, pmd, address);
Linus Torvalds's avatar
Linus Torvalds committed
57 58 59 60 61 62 63 64 65 66 67 68
		if (!pte)
			return -ENOMEM;
		remap_area_pte(pte, address, end - address, address + phys_addr, flags);
		address = (address + PMD_SIZE) & PMD_MASK;
		pmd++;
	} while (address && (address < end));
	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
69
	int error;
Linus Torvalds's avatar
Linus Torvalds committed
70 71 72 73 74 75 76 77
	pgd_t * dir;
	unsigned long end = address + size;

	phys_addr -= address;
	dir = pgd_offset(&init_mm, address);
	flush_cache_all();
	if (address >= end)
		BUG();
Linus Torvalds's avatar
Linus Torvalds committed
78
	spin_lock(&init_mm.page_table_lock);
Linus Torvalds's avatar
Linus Torvalds committed
79 80
	do {
		pmd_t *pmd;
Linus Torvalds's avatar
Linus Torvalds committed
81 82
		pmd = pmd_alloc(&init_mm, dir, address);
		error = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
83
		if (!pmd)
Linus Torvalds's avatar
Linus Torvalds committed
84
			break;
Linus Torvalds's avatar
Linus Torvalds committed
85
		if (remap_area_pmd(pmd, address, end - address,
Linus Torvalds's avatar
Linus Torvalds committed
86 87 88
				   phys_addr + address, flags))
			break;
		error = 0;
Linus Torvalds's avatar
Linus Torvalds committed
89 90 91
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (address && (address < end));
Linus Torvalds's avatar
Linus Torvalds committed
92
	spin_unlock(&init_mm.page_table_lock);
Linus Torvalds's avatar
Linus Torvalds committed
93
	flush_tlb_all();
Linus Torvalds's avatar
Linus Torvalds committed
94
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
}

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

/*
 * 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 * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
	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;

#if 0
	/* TODO: Here we can put checks for driver-writer abuse...  */

	/*
	 * Don't remap the low PCI/ISA area, it's always mapped..
	 */
	if (phys_addr >= 0xA0000 && last_addr < 0x100000)
		return phys_to_virt(phys_addr);

	/*
	 * 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);
	   
		for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
			if(!PageReserved(page))
				return NULL;
	}
#endif

	/*
	 * 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;
	if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
		vfree(addr);
		return NULL;
	}
	return (void *) (offset + (char *)addr);
}

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