summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndi Kleen <ak@suse.de>2005-05-20 14:27:57 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-05-20 15:48:20 -0700
commit7856dfeb23c16ef3d8dac8871b4d5b93c70b59b9 (patch)
tree0e9f799c3882dce14b49356c6db10bb6f4ba1713
parentc4d1fcf3a2ea89b6d6221fa8b4588c77aff50995 (diff)
[PATCH] x86_64: Fixed guard page handling again in iounmap
Caused oopses again. Also fix potential mismatch in checking if change_page_attr was needed. To do it without races I needed to change mm/vmalloc.c to export a __remove_vm_area that does not take vmlist lock. Noticed by Terence Ripperda and based on a patch of his. Signed-off-by: Andi Kleen <ak@suse.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--arch/x86_64/mm/ioremap.c29
-rw-r--r--include/linux/vmalloc.h1
-rw-r--r--mm/vmalloc.c33
3 files changed, 29 insertions, 34 deletions
diff --git a/arch/x86_64/mm/ioremap.c b/arch/x86_64/mm/ioremap.c
index c6fb0cb6999..58aac23760e 100644
--- a/arch/x86_64/mm/ioremap.c
+++ b/arch/x86_64/mm/ioremap.c
@@ -133,7 +133,7 @@ ioremap_change_attr(unsigned long phys_addr, unsigned long size,
unsigned long flags)
{
int err = 0;
- if (flags && phys_addr + size - 1 < (end_pfn_map << PAGE_SHIFT)) {
+ if (phys_addr + size - 1 < (end_pfn_map << PAGE_SHIFT)) {
unsigned long npages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
unsigned long vaddr = (unsigned long) __va(phys_addr);
@@ -214,7 +214,7 @@ void __iomem * __ioremap(unsigned long phys_addr, unsigned long size, unsigned l
remove_vm_area((void *)(PAGE_MASK & (unsigned long) addr));
return NULL;
}
- if (ioremap_change_attr(phys_addr, size, flags) < 0) {
+ if (flags && ioremap_change_attr(phys_addr, size, flags) < 0) {
area->flags &= 0xffffff;
vunmap(addr);
return NULL;
@@ -251,7 +251,7 @@ void __iomem *ioremap_nocache (unsigned long phys_addr, unsigned long size)
void iounmap(volatile void __iomem *addr)
{
- struct vm_struct *p, **pprev;
+ struct vm_struct *p;
if (addr <= high_memory)
return;
@@ -260,24 +260,11 @@ void iounmap(volatile void __iomem *addr)
return;
write_lock(&vmlist_lock);
- for (p = vmlist, pprev = &vmlist; p != NULL; pprev = &p->next, p = *pprev)
- if (p->addr == (void *)(PAGE_MASK & (unsigned long)addr))
- break;
- if (!p) {
- printk("__iounmap: bad address %p\n", addr);
- goto out_unlock;
- }
- *pprev = p->next;
- unmap_vm_area(p);
- if ((p->flags >> 20) &&
- p->phys_addr + p->size - 1 < virt_to_phys(high_memory)) {
- /* p->size includes the guard page, but cpa doesn't like that */
- change_page_attr_addr((unsigned long)__va(p->phys_addr),
- p->size >> PAGE_SHIFT,
- PAGE_KERNEL);
- global_flush_tlb();
- }
-out_unlock:
+ p = __remove_vm_area((void *)((unsigned long)addr & PAGE_MASK));
+ if (!p)
+ printk("iounmap: bad address %p\n", addr);
+ else if (p->flags >> 20)
+ ioremap_change_attr(p->phys_addr, p->size, 0);
write_unlock(&vmlist_lock);
kfree(p);
}
diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
index 3a358c89518..6409d9cf596 100644
--- a/include/linux/vmalloc.h
+++ b/include/linux/vmalloc.h
@@ -41,6 +41,7 @@ extern struct vm_struct *get_vm_area(unsigned long size, unsigned long flags);
extern struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,
unsigned long start, unsigned long end);
extern struct vm_struct *remove_vm_area(void *addr);
+extern struct vm_struct *__remove_vm_area(void *addr);
extern int map_vm_area(struct vm_struct *area, pgprot_t prot,
struct page ***pages);
extern void unmap_vm_area(struct vm_struct *area);
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 2bd83e5c2bb..8ff16a1eee6 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -248,31 +248,20 @@ struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);
}
-/**
- * remove_vm_area - find and remove a contingous kernel virtual area
- *
- * @addr: base address
- *
- * Search for the kernel VM area starting at @addr, and remove it.
- * This function returns the found VM area, but using it is NOT safe
- * on SMP machines.
- */
-struct vm_struct *remove_vm_area(void *addr)
+/* Caller must hold vmlist_lock */
+struct vm_struct *__remove_vm_area(void *addr)
{
struct vm_struct **p, *tmp;
- write_lock(&vmlist_lock);
for (p = &vmlist ; (tmp = *p) != NULL ;p = &tmp->next) {
if (tmp->addr == addr)
goto found;
}
- write_unlock(&vmlist_lock);
return NULL;
found:
unmap_vm_area(tmp);
*p = tmp->next;
- write_unlock(&vmlist_lock);
/*
* Remove the guard page.
@@ -281,6 +270,24 @@ found:
return tmp;
}
+/**
+ * remove_vm_area - find and remove a contingous kernel virtual area
+ *
+ * @addr: base address
+ *
+ * Search for the kernel VM area starting at @addr, and remove it.
+ * This function returns the found VM area, but using it is NOT safe
+ * on SMP machines, except for its size or flags.
+ */
+struct vm_struct *remove_vm_area(void *addr)
+{
+ struct vm_struct *v;
+ write_lock(&vmlist_lock);
+ v = __remove_vm_area(addr);
+ write_unlock(&vmlist_lock);
+ return v;
+}
+
void __vunmap(void *addr, int deallocate_pages)
{
struct vm_struct *area;