1/*
2 * arch/cris/mm/ioremap.c
3 *
4 * Re-map IO memory to kernel address space so that we can access it.
5 * Needed for memory-mapped I/O devices mapped outside our normal DRAM
6 * window (that is, all memory-mapped I/O devices).
7 *
8 * (C) Copyright 1995 1996 Linus Torvalds
9 * CRIS-port by Axis Communications AB
10 */
11
12#include <linux/vmalloc.h>
13#include <asm/io.h>
14#include <asm/pgalloc.h>
15
16static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
17	unsigned long phys_addr, unsigned long flags)
18{
19	unsigned long end;
20
21	address &= ~PMD_MASK;
22	end = address + size;
23	if (end > PMD_SIZE)
24		end = PMD_SIZE;
25	if (address >= end)
26		BUG();
27	do {
28		if (!pte_none(*pte)) {
29			printk("remap_area_pte: page already exists\n");
30			BUG();
31		}
32		set_pte(pte, mk_pte_phys(phys_addr, __pgprot(_PAGE_PRESENT | __READABLE |
33							     __WRITEABLE | _PAGE_GLOBAL |
34							     _PAGE_KERNEL | flags)));
35		address += PAGE_SIZE;
36		phys_addr += PAGE_SIZE;
37		pte++;
38	} while (address && (address < end));
39}
40
41static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
42	unsigned long phys_addr, unsigned long flags)
43{
44	unsigned long end;
45
46	address &= ~PGDIR_MASK;
47	end = address + size;
48	if (end > PGDIR_SIZE)
49		end = PGDIR_SIZE;
50	phys_addr -= address;
51	if (address >= end)
52		BUG();
53	do {
54		pte_t * pte = pte_alloc(&init_mm, pmd, address);
55		if (!pte)
56			return -ENOMEM;
57		remap_area_pte(pte, address, end - address, address + phys_addr, flags);
58		address = (address + PMD_SIZE) & PMD_MASK;
59		pmd++;
60	} while (address && (address < end));
61	return 0;
62}
63
64static int remap_area_pages(unsigned long address, unsigned long phys_addr,
65				 unsigned long size, unsigned long flags)
66{
67	int error;
68	pgd_t * dir;
69	unsigned long end = address + size;
70
71	phys_addr -= address;
72	dir = pgd_offset(&init_mm, address);
73	flush_cache_all();
74	if (address >= end)
75		BUG();
76	spin_lock(&init_mm.page_table_lock);
77	do {
78		pmd_t *pmd;
79		pmd = pmd_alloc(&init_mm, dir, address);
80		error = -ENOMEM;
81		if (!pmd)
82			break;
83		if (remap_area_pmd(pmd, address, end - address,
84				   phys_addr + address, flags))
85			break;
86		error = 0;
87		address = (address + PGDIR_SIZE) & PGDIR_MASK;
88		dir++;
89	} while (address && (address < end));
90	spin_unlock(&init_mm.page_table_lock);
91	flush_tlb_all();
92	return error;
93}
94
95/*
96 * Generic mapping function (not visible outside):
97 */
98
99/*
100 * Remap an arbitrary physical address space into the kernel virtual
101 * address space. Needed when the kernel wants to access high addresses
102 * directly.
103 *
104 * NOTE! We need to allow non-page-aligned mappings too: we will obviously
105 * have to convert them into an offset in a page-aligned mapping, but the
106 * caller shouldn't need to know that small detail.
107 */
108void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
109{
110	void * addr;
111	struct vm_struct * area;
112	unsigned long offset, last_addr;
113
114	/* Don't allow wraparound or zero size */
115	last_addr = phys_addr + size - 1;
116	if (!size || last_addr < phys_addr)
117		return NULL;
118
119
120	/*
121	 * Mappings have to be page-aligned
122	 */
123	offset = phys_addr & ~PAGE_MASK;
124	phys_addr &= PAGE_MASK;
125	size = PAGE_ALIGN(last_addr) - phys_addr;
126
127	/*
128	 * Ok, go for it..
129	 */
130	area = get_vm_area(size, VM_IOREMAP);
131	if (!area)
132		return NULL;
133	addr = area->addr;
134	if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
135		vfree(addr);
136		return NULL;
137	}
138	return (void *) (offset + (char *)addr);
139}
140
141void iounmap(void *addr)
142{
143	if (addr > high_memory)
144		return vfree((void *) (PAGE_MASK & (unsigned long) addr));
145}
146