186 lines · 4.4 KB · C
RawDownload
/* * Basic general purpose allocator for managing special purpose memory * not managed by the regular kmalloc/kfree interface. * Uses for this includes on-device special memory, uncached memory * etc. * * This code is based on the buddy allocator found in the sym53c8xx_2 * driver Copyright (C) 1999-2001  Gerard Roudier <groudier@free.fr>, * and adapted for general purpose use. * * Copyright 2005 (C) Jes Sorensen <jes@trained-monkey.org> * * This source code is licensed under the GNU General Public License, * Version 2.  See the file COPYING for more details. */ #include <linux/module.h>#include <linux/stddef.h>#include <linux/kernel.h>#include <linux/string.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/spinlock.h>#include <linux/genalloc.h> #include <asm/page.h>  struct gen_pool *gen_pool_create(int nr_chunks, int max_chunk_shift,				 unsigned long (*fp)(struct gen_pool *),				 unsigned long data){	struct gen_pool *poolp;	unsigned long tmp;	int i; 	/*	 * This is really an arbitrary limit, +10 is enough for	 * IA64_GRANULE_SHIFT, aka 16MB. If anyone needs a large limit	 * this can be increased without problems.	 */	if ((max_chunk_shift > (PAGE_SHIFT + 10)) ||	    ((max_chunk_shift < ALLOC_MIN_SHIFT) && max_chunk_shift))		return NULL; 	if (!max_chunk_shift)		max_chunk_shift = PAGE_SHIFT; 	poolp = kmalloc(sizeof(struct gen_pool), GFP_KERNEL);	if (!poolp)		return NULL;	memset(poolp, 0, sizeof(struct gen_pool));	poolp->h = kmalloc(sizeof(struct gen_pool_link) *			   (max_chunk_shift - ALLOC_MIN_SHIFT + 1),			   GFP_KERNEL);	if (!poolp->h) {		printk(KERN_WARNING "gen_pool_alloc() failed to allocate\n");		kfree(poolp);		return NULL;	}	memset(poolp->h, 0, sizeof(struct gen_pool_link) *	       (max_chunk_shift - ALLOC_MIN_SHIFT + 1)); 	spin_lock_init(&poolp->lock);	poolp->get_new_chunk = fp;	poolp->max_chunk_shift = max_chunk_shift;	poolp->private = data; 	for (i = 0; i < nr_chunks; i++) {		tmp = poolp->get_new_chunk(poolp);		printk(KERN_INFO "allocated %lx\n", tmp);		if (!tmp)			break;		gen_pool_free(poolp, tmp, (1 << poolp->max_chunk_shift));	} 	return poolp;}EXPORT_SYMBOL(gen_pool_create);  /* *  Simple power of two buddy-like generic allocator. *  Provides naturally aligned memory chunks. */unsigned long gen_pool_alloc(struct gen_pool *poolp, int size){	int j, i, s, max_chunk_size;	unsigned long a, flags;	struct gen_pool_link *h = poolp->h; 	max_chunk_size = 1 << poolp->max_chunk_shift; 	if (size > max_chunk_size)		return 0; 	size = max(size, 1 << ALLOC_MIN_SHIFT);	i = fls(size - 1);	s = 1 << i;	j = i -= ALLOC_MIN_SHIFT; 	spin_lock_irqsave(&poolp->lock, flags);	while (!h[j].next) {		if (s == max_chunk_size) {			struct gen_pool_link *ptr;			spin_unlock_irqrestore(&poolp->lock, flags);			ptr = (struct gen_pool_link *)poolp->get_new_chunk(poolp);			spin_lock_irqsave(&poolp->lock, flags);			h[j].next = ptr;			if (h[j].next)				h[j].next->next = NULL;			break;		}		j++;		s <<= 1;	}	a = (unsigned long) h[j].next;	if (a) {		h[j].next = h[j].next->next;		/*		 * This should be split into a seperate function doing		 * the chunk split in order to support custom		 * handling memory not physically accessible by host		 */		while (j > i) {			j -= 1;			s >>= 1;			h[j].next = (struct gen_pool_link *) (a + s);			h[j].next->next = NULL;		}	}	spin_unlock_irqrestore(&poolp->lock, flags);	return a;}EXPORT_SYMBOL(gen_pool_alloc);  /* *  Counter-part of the generic allocator. */void gen_pool_free(struct gen_pool *poolp, unsigned long ptr, int size){	struct gen_pool_link *q;	struct gen_pool_link *h = poolp->h;	unsigned long a, b, flags;	int i, s, max_chunk_size; 	max_chunk_size = 1 << poolp->max_chunk_shift; 	if (size > max_chunk_size)		return; 	size = max(size, 1 << ALLOC_MIN_SHIFT);	i = fls(size - 1);	s = 1 << i;	i -= ALLOC_MIN_SHIFT; 	a = ptr; 	spin_lock_irqsave(&poolp->lock, flags);	while (1) {		if (s == max_chunk_size) {			((struct gen_pool_link *)a)->next = h[i].next;			h[i].next = (struct gen_pool_link *)a;			break;		}		b = a ^ s;		q = &h[i]; 		while (q->next && q->next != (struct gen_pool_link *)b)			q = q->next; 		if (!q->next) {			((struct gen_pool_link *)a)->next = h[i].next;			h[i].next = (struct gen_pool_link *)a;			break;		}		q->next = q->next->next;		a = a & b;		s <<= 1;		i++;	}	spin_unlock_irqrestore(&poolp->lock, flags);}EXPORT_SYMBOL(gen_pool_free);