diff options
Diffstat (limited to 'mm')
| -rw-r--r-- | mm/vmalloc.c | 45 | 
1 files changed, 40 insertions, 5 deletions
diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 0f751f2068c..ef9bdf74227 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -27,10 +27,30 @@  #include <linux/pfn.h>  #include <linux/kmemleak.h>  #include <linux/atomic.h> +#include <linux/llist.h>  #include <asm/uaccess.h>  #include <asm/tlbflush.h>  #include <asm/shmparam.h> +struct vfree_deferred { +	struct llist_head list; +	struct work_struct wq; +}; +static DEFINE_PER_CPU(struct vfree_deferred, vfree_deferred); + +static void __vunmap(const void *, int); + +static void free_work(struct work_struct *w) +{ +	struct vfree_deferred *p = container_of(w, struct vfree_deferred, wq); +	struct llist_node *llnode = llist_del_all(&p->list); +	while (llnode) { +		void *p = llnode; +		llnode = llist_next(llnode); +		__vunmap(p, 1); +	} +} +  /*** Page table manipulation functions ***/  static void vunmap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end) @@ -1184,10 +1204,14 @@ void __init vmalloc_init(void)  	for_each_possible_cpu(i) {  		struct vmap_block_queue *vbq; +		struct vfree_deferred *p;  		vbq = &per_cpu(vmap_block_queue, i);  		spin_lock_init(&vbq->lock);  		INIT_LIST_HEAD(&vbq->free); +		p = &per_cpu(vfree_deferred, i); +		init_llist_head(&p->list); +		INIT_WORK(&p->wq, free_work);  	}  	/* Import existing vmlist entries. */ @@ -1511,7 +1535,7 @@ static void __vunmap(const void *addr, int deallocate_pages)  	kfree(area);  	return;  } - +   /**   *	vfree  -  release memory allocated by vmalloc()   *	@addr:		memory base address @@ -1520,15 +1544,25 @@ static void __vunmap(const void *addr, int deallocate_pages)   *	obtained from vmalloc(), vmalloc_32() or __vmalloc(). If @addr is   *	NULL, no operation is performed.   * - *	Must not be called in interrupt context. + *	Must not be called in NMI context (strictly speaking, only if we don't + *	have CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG, but making the calling + *	conventions for vfree() arch-depenedent would be a really bad idea) + *	   */  void vfree(const void *addr)  { -	BUG_ON(in_interrupt()); +	BUG_ON(in_nmi());  	kmemleak_free(addr); -	__vunmap(addr, 1); +	if (!addr) +		return; +	if (unlikely(in_interrupt())) { +		struct vfree_deferred *p = &__get_cpu_var(vfree_deferred); +		llist_add((struct llist_node *)addr, &p->list); +		schedule_work(&p->wq); +	} else +		__vunmap(addr, 1);  }  EXPORT_SYMBOL(vfree); @@ -1545,7 +1579,8 @@ void vunmap(const void *addr)  {  	BUG_ON(in_interrupt());  	might_sleep(); -	__vunmap(addr, 0); +	if (addr) +		__vunmap(addr, 0);  }  EXPORT_SYMBOL(vunmap);  |