2005-06-22 04:15:02 +04:00
/*
* 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 16 MB . 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 ) ;
2005-11-29 00:43:54 +03:00
i = fls ( size - 1 ) ;
s = 1 < < i ;
j = i - = ALLOC_MIN_SHIFT ;
2005-06-22 04:15:02 +04:00
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 ) ;
2005-11-29 00:43:54 +03:00
i = fls ( size - 1 ) ;
s = 1 < < i ;
i - = ALLOC_MIN_SHIFT ;
2005-06-22 04:15:02 +04:00
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 ) ;