aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mm
diff options
context:
space:
mode:
authorRussell King <rmk@dyn-67.arm.linux.org.uk>2006-06-29 15:17:15 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2006-06-29 17:14:30 -0400
commitff0daca525dde796382b9ccd563f169df2571211 (patch)
treeacb3feb60cec39f316447f702a2277a7448cbad9 /arch/arm/mm
parentba53201180e267bd1f0792e6c375ced7c100738e (diff)
[ARM] Add section support to ioremap
Allow section mappings to be setup using ioremap() and torn down with iounmap(). This requires additional support in the MM context switch to ensure that mappings are properly synchronised when mapped in. Based an original implementation by Deepak Saxena, reworked and ARMv6 support added by rmk. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/mm')
-rw-r--r--arch/arm/mm/ioremap.c167
1 files changed, 163 insertions, 4 deletions
diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c
index 7691cfdba567..6aa13d59c858 100644
--- a/arch/arm/mm/ioremap.c
+++ b/arch/arm/mm/ioremap.c
@@ -27,7 +27,16 @@
27 27
28#include <asm/cacheflush.h> 28#include <asm/cacheflush.h>
29#include <asm/io.h> 29#include <asm/io.h>
30#include <asm/mmu_context.h>
31#include <asm/pgalloc.h>
30#include <asm/tlbflush.h> 32#include <asm/tlbflush.h>
33#include <asm/sizes.h>
34
35/*
36 * Used by ioremap() and iounmap() code to mark section-mapped I/O regions
37 * in vm_struct->flags field.
38 */
39#define VM_ARM_SECTION_MAPPING 0x80000000
31 40
32static inline void 41static inline void
33remap_area_pte(pte_t * pte, unsigned long address, unsigned long size, 42remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
@@ -113,10 +122,120 @@ remap_area_pages(unsigned long start, unsigned long pfn,
113 dir++; 122 dir++;
114 } while (address && (address < end)); 123 } while (address && (address < end));
115 124
116 flush_cache_vmap(start, end);
117 return err; 125 return err;
118} 126}
119 127
128
129void __check_kvm_seq(struct mm_struct *mm)
130{
131 unsigned int seq;
132
133 do {
134 seq = init_mm.context.kvm_seq;
135 memcpy(pgd_offset(mm, VMALLOC_START),
136 pgd_offset_k(VMALLOC_START),
137 sizeof(pgd_t) * (pgd_index(VMALLOC_END) -
138 pgd_index(VMALLOC_START)));
139 mm->context.kvm_seq = seq;
140 } while (seq != init_mm.context.kvm_seq);
141}
142
143#ifndef CONFIG_SMP
144/*
145 * Section support is unsafe on SMP - If you iounmap and ioremap a region,
146 * the other CPUs will not see this change until their next context switch.
147 * Meanwhile, (eg) if an interrupt comes in on one of those other CPUs
148 * which requires the new ioremap'd region to be referenced, the CPU will
149 * reference the _old_ region.
150 *
151 * Note that get_vm_area() allocates a guard 4K page, so we need to mask
152 * the size back to 1MB aligned or we will overflow in the loop below.
153 */
154static void unmap_area_sections(unsigned long virt, unsigned long size)
155{
156 unsigned long addr = virt, end = virt + (size & ~SZ_1M);
157 pgd_t *pgd;
158
159 flush_cache_vunmap(addr, end);
160 pgd = pgd_offset_k(addr);
161 do {
162 pmd_t pmd, *pmdp = pmd_offset(pgd, addr);
163
164 pmd = *pmdp;
165 if (!pmd_none(pmd)) {
166 /*
167 * Clear the PMD from the page table, and
168 * increment the kvm sequence so others
169 * notice this change.
170 *
171 * Note: this is still racy on SMP machines.
172 */
173 pmd_clear(pmdp);
174 init_mm.context.kvm_seq++;
175
176 /*
177 * Free the page table, if there was one.
178 */
179 if ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_TABLE)
180 pte_free_kernel(pmd_page_kernel(pmd));
181 }
182
183 addr += PGDIR_SIZE;
184 pgd++;
185 } while (addr < end);
186
187 /*
188 * Ensure that the active_mm is up to date - we want to
189 * catch any use-after-iounmap cases.
190 */
191 if (current->active_mm->context.kvm_seq != init_mm.context.kvm_seq)
192 __check_kvm_seq(current->active_mm);
193
194 flush_tlb_kernel_range(virt, end);
195}
196
197static int
198remap_area_sections(unsigned long virt, unsigned long pfn,
199 unsigned long size, unsigned long flags)
200{
201 unsigned long prot, addr = virt, end = virt + size;
202 pgd_t *pgd;
203
204 /*
205 * Remove and free any PTE-based mapping, and
206 * sync the current kernel mapping.
207 */
208 unmap_area_sections(virt, size);
209
210 prot = PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_DOMAIN(DOMAIN_IO) |
211 (flags & (L_PTE_CACHEABLE | L_PTE_BUFFERABLE));
212
213 /*
214 * ARMv6 and above need XN set to prevent speculative prefetches
215 * hitting IO.
216 */
217 if (cpu_architecture() >= CPU_ARCH_ARMv6)
218 prot |= PMD_SECT_XN;
219
220 pgd = pgd_offset_k(addr);
221 do {
222 pmd_t *pmd = pmd_offset(pgd, addr);
223
224 pmd[0] = __pmd(__pfn_to_phys(pfn) | prot);
225 pfn += SZ_1M >> PAGE_SHIFT;
226 pmd[1] = __pmd(__pfn_to_phys(pfn) | prot);
227 pfn += SZ_1M >> PAGE_SHIFT;
228 flush_pmd_entry(pmd);
229
230 addr += PGDIR_SIZE;
231 pgd++;
232 } while (addr < end);
233
234 return 0;
235}
236#endif
237
238
120/* 239/*
121 * Remap an arbitrary physical address space into the kernel virtual 240 * Remap an arbitrary physical address space into the kernel virtual
122 * address space. Needed when the kernel wants to access high addresses 241 * address space. Needed when the kernel wants to access high addresses
@@ -133,6 +252,7 @@ void __iomem *
133__ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size, 252__ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size,
134 unsigned long flags) 253 unsigned long flags)
135{ 254{
255 int err;
136 unsigned long addr; 256 unsigned long addr;
137 struct vm_struct * area; 257 struct vm_struct * area;
138 258
@@ -140,11 +260,22 @@ __ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size,
140 if (!area) 260 if (!area)
141 return NULL; 261 return NULL;
142 addr = (unsigned long)area->addr; 262 addr = (unsigned long)area->addr;
143 if (remap_area_pages(addr, pfn, size, flags)) { 263
264#ifndef CONFIG_SMP
265 if (!((__pfn_to_phys(pfn) | size | addr) & ~PMD_MASK)) {
266 area->flags |= VM_ARM_SECTION_MAPPING;
267 err = remap_area_sections(addr, pfn, size, flags);
268 } else
269#endif
270 err = remap_area_pages(addr, pfn, size, flags);
271
272 if (err) {
144 vunmap((void *)addr); 273 vunmap((void *)addr);
145 return NULL; 274 return NULL;
146 } 275 }
147 return (void __iomem *) (offset + (char *)addr); 276
277 flush_cache_vmap(addr, addr + size);
278 return (void __iomem *) (offset + addr);
148} 279}
149EXPORT_SYMBOL(__ioremap_pfn); 280EXPORT_SYMBOL(__ioremap_pfn);
150 281
@@ -173,6 +304,34 @@ EXPORT_SYMBOL(__ioremap);
173 304
174void __iounmap(void __iomem *addr) 305void __iounmap(void __iomem *addr)
175{ 306{
176 vunmap((void *)(PAGE_MASK & (unsigned long)addr)); 307 struct vm_struct **p, *tmp;
308 unsigned int section_mapping = 0;
309
310 addr = (void __iomem *)(PAGE_MASK & (unsigned long)addr);
311
312 /*
313 * If this is a section based mapping we need to handle it
314 * specially as the VM subysystem does not know how to handle
315 * such a beast. We need the lock here b/c we need to clear
316 * all the mappings before the area can be reclaimed
317 * by someone else.
318 */
319 write_lock(&vmlist_lock);
320 for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
321 if((tmp->flags & VM_IOREMAP) && (tmp->addr == addr)) {
322 if (tmp->flags & VM_ARM_SECTION_MAPPING) {
323 *p = tmp->next;
324 unmap_area_sections((unsigned long)tmp->addr,
325 tmp->size);
326 kfree(tmp);
327 section_mapping = 1;
328 }
329 break;
330 }
331 }
332 write_unlock(&vmlist_lock);
333
334 if (!section_mapping)
335 vunmap(addr);
177} 336}
178EXPORT_SYMBOL(__iounmap); 337EXPORT_SYMBOL(__iounmap);