2009-11-19 18:07:04 +03:00
# include <linux/spinlock.h>
# include <linux/list.h>
# include <linux/slab.h>
# include "vmregion.h"
/*
* VM region handling support .
*
* This should become something generic , handling VM region allocations for
* vmalloc and similar ( ioremap , module space , etc ) .
*
* I envisage vmalloc ( ) ' s supporting vm_struct becoming :
*
* struct vm_struct {
* struct vmregion region ;
* unsigned long flags ;
* struct page * * pages ;
* unsigned int nr_pages ;
* unsigned long phys_addr ;
* } ;
*
* get_vm_area ( ) would then call vmregion_alloc with an appropriate
* struct vmregion head ( eg ) :
*
* struct vmregion vmalloc_head = {
* . vm_list = LIST_HEAD_INIT ( vmalloc_head . vm_list ) ,
* . vm_start = VMALLOC_START ,
* . vm_end = VMALLOC_END ,
* } ;
*
* However , vmalloc_head . vm_start is variable ( typically , it is dependent on
* the amount of RAM found at boot time . ) I would imagine that get_vm_area ( )
* would have to initialise this each time prior to calling vmregion_alloc ( ) .
*/
struct arm_vmregion *
2010-07-25 11:57:02 +04:00
arm_vmregion_alloc ( struct arm_vmregion_head * head , size_t align ,
size_t size , gfp_t gfp )
2009-11-19 18:07:04 +03:00
{
2011-01-08 14:49:20 +03:00
unsigned long start = head - > vm_start , addr = head - > vm_end ;
2009-11-19 18:07:04 +03:00
unsigned long flags ;
struct arm_vmregion * c , * new ;
if ( head - > vm_end - head - > vm_start < size ) {
printk ( KERN_WARNING " %s: allocation too big (requested %#x) \n " ,
__func__ , size ) ;
goto out ;
}
new = kmalloc ( sizeof ( struct arm_vmregion ) , gfp ) ;
if ( ! new )
goto out ;
spin_lock_irqsave ( & head - > vm_lock , flags ) ;
2011-01-08 14:49:20 +03:00
addr = rounddown ( addr - size , align ) ;
list_for_each_entry_reverse ( c , & head - > vm_list , vm_list ) {
if ( addr > = c - > vm_end )
2009-11-19 18:07:04 +03:00
goto found ;
2011-01-08 14:49:20 +03:00
addr = rounddown ( c - > vm_start - size , align ) ;
if ( addr < start )
2009-11-19 18:07:04 +03:00
goto nospc ;
}
found :
/*
2011-01-08 14:49:20 +03:00
* Insert this entry after the one we found .
2009-11-19 18:07:04 +03:00
*/
2011-01-08 14:49:20 +03:00
list_add ( & new - > vm_list , & c - > vm_list ) ;
2009-11-19 18:07:04 +03:00
new - > vm_start = addr ;
new - > vm_end = addr + size ;
new - > vm_active = 1 ;
spin_unlock_irqrestore ( & head - > vm_lock , flags ) ;
return new ;
nospc :
spin_unlock_irqrestore ( & head - > vm_lock , flags ) ;
kfree ( new ) ;
out :
return NULL ;
}
static struct arm_vmregion * __arm_vmregion_find ( struct arm_vmregion_head * head , unsigned long addr )
{
struct arm_vmregion * c ;
list_for_each_entry ( c , & head - > vm_list , vm_list ) {
if ( c - > vm_active & & c - > vm_start = = addr )
goto out ;
}
c = NULL ;
out :
return c ;
}
struct arm_vmregion * arm_vmregion_find ( struct arm_vmregion_head * head , unsigned long addr )
{
struct arm_vmregion * c ;
unsigned long flags ;
spin_lock_irqsave ( & head - > vm_lock , flags ) ;
c = __arm_vmregion_find ( head , addr ) ;
spin_unlock_irqrestore ( & head - > vm_lock , flags ) ;
return c ;
}
struct arm_vmregion * arm_vmregion_find_remove ( struct arm_vmregion_head * head , unsigned long addr )
{
struct arm_vmregion * c ;
unsigned long flags ;
spin_lock_irqsave ( & head - > vm_lock , flags ) ;
c = __arm_vmregion_find ( head , addr ) ;
if ( c )
c - > vm_active = 0 ;
spin_unlock_irqrestore ( & head - > vm_lock , flags ) ;
return c ;
}
void arm_vmregion_free ( struct arm_vmregion_head * head , struct arm_vmregion * c )
{
unsigned long flags ;
spin_lock_irqsave ( & head - > vm_lock , flags ) ;
list_del ( & c - > vm_list ) ;
spin_unlock_irqrestore ( & head - > vm_lock , flags ) ;
kfree ( c ) ;
}