2012-01-12 23:08:07 +00:00
# include <linux/fs.h>
2009-11-19 15:07:04 +00:00
# include <linux/spinlock.h>
# include <linux/list.h>
2012-01-12 23:08:07 +00:00
# include <linux/proc_fs.h>
# include <linux/seq_file.h>
2009-11-19 15:07:04 +00:00
# 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 08:57:02 +01:00
arm_vmregion_alloc ( struct arm_vmregion_head * head , size_t align ,
2012-01-12 23:08:07 +00:00
size_t size , gfp_t gfp , const void * caller )
2009-11-19 15:07:04 +00:00
{
2011-01-08 11:49:20 +00:00
unsigned long start = head - > vm_start , addr = head - > vm_end ;
2009-11-19 15:07:04 +00: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 ;
2012-01-12 23:08:07 +00:00
new - > caller = caller ;
2009-11-19 15:07:04 +00:00
spin_lock_irqsave ( & head - > vm_lock , flags ) ;
2011-01-08 11:49:20 +00: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 15:07:04 +00:00
goto found ;
2011-01-08 11:49:20 +00:00
addr = rounddown ( c - > vm_start - size , align ) ;
if ( addr < start )
2009-11-19 15:07:04 +00:00
goto nospc ;
}
found :
/*
2011-01-08 11:49:20 +00:00
* Insert this entry after the one we found .
2009-11-19 15:07:04 +00:00
*/
2011-01-08 11:49:20 +00:00
list_add ( & new - > vm_list , & c - > vm_list ) ;
2009-11-19 15:07:04 +00: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 ) ;
}
2012-01-12 23:08:07 +00:00
# ifdef CONFIG_PROC_FS
static int arm_vmregion_show ( struct seq_file * m , void * p )
{
struct arm_vmregion * c = list_entry ( p , struct arm_vmregion , vm_list ) ;
seq_printf ( m , " 0x%08lx-0x%08lx %7lu " , c - > vm_start , c - > vm_end ,
c - > vm_end - c - > vm_start ) ;
if ( c - > caller )
seq_printf ( m , " %pS " , ( void * ) c - > caller ) ;
seq_putc ( m , ' \n ' ) ;
return 0 ;
}
static void * arm_vmregion_start ( struct seq_file * m , loff_t * pos )
{
struct arm_vmregion_head * h = m - > private ;
spin_lock_irq ( & h - > vm_lock ) ;
return seq_list_start ( & h - > vm_list , * pos ) ;
}
static void * arm_vmregion_next ( struct seq_file * m , void * p , loff_t * pos )
{
struct arm_vmregion_head * h = m - > private ;
return seq_list_next ( p , & h - > vm_list , pos ) ;
}
static void arm_vmregion_stop ( struct seq_file * m , void * p )
{
struct arm_vmregion_head * h = m - > private ;
spin_unlock_irq ( & h - > vm_lock ) ;
}
static const struct seq_operations arm_vmregion_ops = {
. start = arm_vmregion_start ,
. stop = arm_vmregion_stop ,
. next = arm_vmregion_next ,
. show = arm_vmregion_show ,
} ;
static int arm_vmregion_open ( struct inode * inode , struct file * file )
{
struct arm_vmregion_head * h = PDE ( inode ) - > data ;
int ret = seq_open ( file , & arm_vmregion_ops ) ;
if ( ! ret ) {
struct seq_file * m = file - > private_data ;
m - > private = h ;
}
return ret ;
}
static const struct file_operations arm_vmregion_fops = {
. open = arm_vmregion_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
int arm_vmregion_create_proc ( const char * path , struct arm_vmregion_head * h )
{
proc_create_data ( path , S_IRUSR , NULL , & arm_vmregion_fops , h ) ;
return 0 ;
}
# else
int arm_vmregion_create_proc ( const char * path , struct arm_vmregion_head * h )
{
return 0 ;
}
# endif