File: /usr/src/linux/arch/m68k/mm/kmap.c

1     /*
2      *  linux/arch/m68k/mm/kmap.c
3      *
4      *  Copyright (C) 1997 Roman Hodek
5      *
6      *  10/01/99 cleaned up the code and changing to the same interface
7      *	     used by other architectures		/Roman Zippel
8      */
9     
10     #include <linux/config.h>
11     #include <linux/mm.h>
12     #include <linux/kernel.h>
13     #include <linux/string.h>
14     #include <linux/types.h>
15     #include <linux/slab.h>
16     #include <linux/vmalloc.h>
17     
18     #include <asm/setup.h>
19     #include <asm/segment.h>
20     #include <asm/page.h>
21     #include <asm/pgalloc.h>
22     #include <asm/io.h>
23     #include <asm/system.h>
24     
25     #undef DEBUG
26     
27     #define PTRTREESIZE	(256*1024)
28     
29     /*
30      * For 040/060 we can use the virtual memory area like other architectures,
31      * but for 020/030 we want to use early termination page descriptor and we
32      * can't mix this with normal page descriptors, so we have to copy that code
33      * (mm/vmalloc.c) and return appriorate aligned addresses.
34      */
35     
36     #ifdef CPU_M68040_OR_M68060_ONLY
37     
38     #define IO_SIZE		PAGE_SIZE
39     
40     static inline struct vm_struct *get_io_area(unsigned long size)
41     {
42     	return get_vm_area(size, VM_IOREMAP);
43     }
44     
45     
46     static inline void free_io_area(void *addr)
47     {
48     	return vfree((void *)(PAGE_MASK & (unsigned long)addr));
49     }
50     
51     #else
52     
53     #define IO_SIZE		(256*1024)
54     
55     static struct vm_struct *iolist = NULL;
56     
57     static struct vm_struct *get_io_area(unsigned long size)
58     {
59     	unsigned long addr;
60     	struct vm_struct **p, *tmp, *area;
61     
62     	area = (struct vm_struct *)kmalloc(sizeof(*area), GFP_KERNEL);
63     	if (!area)
64     		return NULL;
65     	addr = KMAP_START;
66     	for (p = &iolist; (tmp = *p) ; p = &tmp->next) {
67     		if (size + addr < (unsigned long)tmp->addr)
68     			break;
69     		if (addr > KMAP_END-size)
70     			return NULL;
71     		addr = tmp->size + (unsigned long)tmp->addr;
72     	}
73     	area->addr = (void *)addr;
74     	area->size = size + IO_SIZE;
75     	area->next = *p;
76     	*p = area;
77     	return area;
78     }
79     
80     static inline void free_io_area(void *addr)
81     {
82     	struct vm_struct **p, *tmp;
83     
84     	if (!addr)
85     		return;
86     	addr = (void *)((unsigned long)addr & -IO_SIZE);
87     	for (p = &iolist ; (tmp = *p) ; p = &tmp->next) {
88     		if (tmp->addr == addr) {
89     			*p = tmp->next;
90     			__iounmap(tmp->addr, tmp->size);
91     			kfree(tmp);
92     			return;
93     		}
94     	}
95     }
96     
97     #endif
98     
99     /*
100      * Map some physical address range into the kernel address space. The
101      * code is copied and adapted from map_chunk().
102      */
103     /* Rewritten by Andreas Schwab to remove all races. */
104     
105     void *__ioremap(unsigned long physaddr, unsigned long size, int cacheflag)
106     {
107     	struct vm_struct *area;
108     	unsigned long virtaddr, retaddr;
109     	long offset;
110     	pgd_t *pgd_dir;
111     	pmd_t *pmd_dir;
112     	pte_t *pte_dir;
113     
114     	/*
115     	 * Don't allow mappings that wrap..
116     	 */
117     	if (!size || size > physaddr + size)
118     		return NULL;
119     
120     #ifdef CONFIG_AMIGA
121     	if (MACH_IS_AMIGA) {
122     		if ((physaddr >= 0x40000000) && (physaddr + size < 0x60000000)
123     		    && (cacheflag == IOMAP_NOCACHE_SER))
124     			return (void *)physaddr;
125     	}
126     #endif
127     
128     #ifdef DEBUG
129     	printk("ioremap: 0x%lx,0x%lx(%d) - ", physaddr, size, cacheflag);
130     #endif
131     	/*
132     	 * Mappings have to be aligned
133     	 */
134     	offset = physaddr & (IO_SIZE - 1);
135     	physaddr &= -IO_SIZE;
136     	size = (size + offset + IO_SIZE - 1) & -IO_SIZE;
137     
138     	/*
139     	 * Ok, go for it..
140     	 */
141     	area = get_io_area(size);
142     	if (!area)
143     		return NULL;
144     
145     	virtaddr = (unsigned long)area->addr;
146     	retaddr = virtaddr + offset;
147     #ifdef DEBUG
148     	printk("0x%lx,0x%lx,0x%lx", physaddr, virtaddr, retaddr);
149     #endif
150     
151     	/*
152     	 * add cache and table flags to physical address
153     	 */
154     	if (CPU_IS_040_OR_060) {
155     		physaddr |= (_PAGE_PRESENT | _PAGE_GLOBAL040 |
156     			     _PAGE_ACCESSED | _PAGE_DIRTY);
157     		switch (cacheflag) {
158     		case IOMAP_FULL_CACHING:
159     			physaddr |= _PAGE_CACHE040;
160     			break;
161     		case IOMAP_NOCACHE_SER:
162     		default:
163     			physaddr |= _PAGE_NOCACHE_S;
164     			break;
165     		case IOMAP_NOCACHE_NONSER:
166     			physaddr |= _PAGE_NOCACHE;
167     			break;
168     		case IOMAP_WRITETHROUGH:
169     			physaddr |= _PAGE_CACHE040W;
170     			break;
171     		}
172     	} else {
173     		physaddr |= (_PAGE_PRESENT | _PAGE_ACCESSED | _PAGE_DIRTY);
174     		switch (cacheflag) {
175     		case IOMAP_NOCACHE_SER:
176     		case IOMAP_NOCACHE_NONSER:
177     		default:
178     			physaddr |= _PAGE_NOCACHE030;
179     			break;
180     		case IOMAP_FULL_CACHING:
181     		case IOMAP_WRITETHROUGH:
182     			break;
183     		}
184     	}
185     
186     	while ((long)size > 0) {
187     #ifdef DEBUG
188     		if (!(virtaddr & (PTRTREESIZE-1)))
189     			printk ("\npa=%#lx va=%#lx ", physaddr, virtaddr);
190     #endif
191     		pgd_dir = pgd_offset_k(virtaddr);
192     		pmd_dir = pmd_alloc_kernel(pgd_dir, virtaddr);
193     		if (!pmd_dir) {
194     			printk("ioremap: no mem for pmd_dir\n");
195     			return NULL;
196     		}
197     
198     		if (CPU_IS_020_OR_030) {
199     			pmd_dir->pmd[(virtaddr/PTRTREESIZE) & 15] = physaddr;
200     			physaddr += PTRTREESIZE;
201     			virtaddr += PTRTREESIZE;
202     			size -= PTRTREESIZE;
203     		} else {
204     			pte_dir = pte_alloc_kernel(pmd_dir, virtaddr);
205     			if (!pte_dir) {
206     				printk("ioremap: no mem for pte_dir\n");
207     				return NULL;
208     			}
209     
210     			pte_val(*pte_dir) = physaddr;
211     			virtaddr += PAGE_SIZE;
212     			physaddr += PAGE_SIZE;
213     			size -= PAGE_SIZE;
214     		}
215     	}
216     #ifdef DEBUG
217     	printk("\n");
218     #endif
219     	flush_tlb_all();
220     
221     	return (void *)retaddr;
222     }
223     
224     /*
225      * Unmap a ioremap()ed region again
226      */
227     void iounmap(void *addr)
228     {
229     #ifdef CONFIG_AMIGA
230     	if ((!MACH_IS_AMIGA) ||
231     	    (((unsigned long)addr < 0x40000000) ||
232     	     ((unsigned long)addr > 0x60000000)))
233     			free_io_area(addr);
234     #else
235     	free_io_area(addr);
236     #endif
237     }
238     
239     /*
240      * __iounmap unmaps nearly everything, so be careful
241      * it doesn't free currently pointer/page tables anymore but it
242      * wans't used anyway and might be added later.
243      */
244     void __iounmap(void *addr, unsigned long size)
245     {
246     	unsigned long virtaddr = (unsigned long)addr;
247     	pgd_t *pgd_dir;
248     	pmd_t *pmd_dir;
249     	pte_t *pte_dir;
250     
251     	while ((long)size > 0) {
252     		pgd_dir = pgd_offset_k(virtaddr);
253     		if (pgd_bad(*pgd_dir)) {
254     			printk("iounmap: bad pgd(%08lx)\n", pgd_val(*pgd_dir));
255     			pgd_clear(pgd_dir);
256     			return;
257     		}
258     		pmd_dir = pmd_offset(pgd_dir, virtaddr);
259     
260     		if (CPU_IS_020_OR_030) {
261     			int pmd_off = (virtaddr/PTRTREESIZE) & 15;
262     
263     			if ((pmd_dir->pmd[pmd_off] & _DESCTYPE_MASK) == _PAGE_PRESENT) {
264     				pmd_dir->pmd[pmd_off] = 0;
265     				virtaddr += PTRTREESIZE;
266     				size -= PTRTREESIZE;
267     				continue;
268     			}
269     		}
270     
271     		if (pmd_bad(*pmd_dir)) {
272     			printk("iounmap: bad pmd (%08lx)\n", pmd_val(*pmd_dir));
273     			pmd_clear(pmd_dir);
274     			return;
275     		}
276     		pte_dir = pte_offset(pmd_dir, virtaddr);
277     
278     		pte_val(*pte_dir) = 0;
279     		virtaddr += PAGE_SIZE;
280     		size -= PAGE_SIZE;
281     	}
282     
283     	flush_tlb_all();
284     }
285     
286     /*
287      * Set new cache mode for some kernel address space.
288      * The caller must push data for that range itself, if such data may already
289      * be in the cache.
290      */
291     void kernel_set_cachemode(void *addr, unsigned long size, int cmode)
292     {
293     	unsigned long virtaddr = (unsigned long)addr;
294     	pgd_t *pgd_dir;
295     	pmd_t *pmd_dir;
296     	pte_t *pte_dir;
297     
298     	if (CPU_IS_040_OR_060) {
299     		switch (cmode) {
300     		case IOMAP_FULL_CACHING:
301     			cmode = _PAGE_CACHE040;
302     			break;
303     		case IOMAP_NOCACHE_SER:
304     		default:
305     			cmode = _PAGE_NOCACHE_S;
306     			break;
307     		case IOMAP_NOCACHE_NONSER:
308     			cmode = _PAGE_NOCACHE;
309     			break;
310     		case IOMAP_WRITETHROUGH:
311     			cmode = _PAGE_CACHE040W;
312     			break;
313     		}
314     	} else {
315     		switch (cmode) {
316     		case IOMAP_NOCACHE_SER:
317     		case IOMAP_NOCACHE_NONSER:
318     		default:
319     			cmode = _PAGE_NOCACHE030;
320     			break;
321     		case IOMAP_FULL_CACHING:
322     		case IOMAP_WRITETHROUGH:
323     			cmode = 0;
324     		}
325     	}
326     
327     	while ((long)size > 0) {
328     		pgd_dir = pgd_offset_k(virtaddr);
329     		if (pgd_bad(*pgd_dir)) {
330     			printk("iocachemode: bad pgd(%08lx)\n", pgd_val(*pgd_dir));
331     			pgd_clear(pgd_dir);
332     			return;
333     		}
334     		pmd_dir = pmd_offset(pgd_dir, virtaddr);
335     
336     		if (CPU_IS_020_OR_030) {
337     			int pmd_off = (virtaddr/PTRTREESIZE) & 15;
338     
339     			if ((pmd_dir->pmd[pmd_off] & _DESCTYPE_MASK) == _PAGE_PRESENT) {
340     				pmd_dir->pmd[pmd_off] = (pmd_dir->pmd[pmd_off] &
341     							 _CACHEMASK040) | cmode;
342     				virtaddr += PTRTREESIZE;
343     				size -= PTRTREESIZE;
344     				continue;
345     			}
346     		}
347     
348     		if (pmd_bad(*pmd_dir)) {
349     			printk("iocachemode: bad pmd (%08lx)\n", pmd_val(*pmd_dir));
350     			pmd_clear(pmd_dir);
351     			return;
352     		}
353     		pte_dir = pte_offset(pmd_dir, virtaddr);
354     
355     		pte_val(*pte_dir) = (pte_val(*pte_dir) & _CACHEMASK040) | cmode;
356     		virtaddr += PAGE_SIZE;
357     		size -= PAGE_SIZE;
358     	}
359     
360     	flush_tlb_all();
361     }
362