2007-01-12 04:15:29 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
|
|
* License, or any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file
|
|
|
|
*
|
|
|
|
* External memory allocation
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2007-07-05 23:30:34 +02:00
|
|
|
#include <limits.h>
|
|
|
|
#include <errno.h>
|
2007-01-12 04:15:29 +01:00
|
|
|
#include <gpxe/uaccess.h>
|
|
|
|
#include <gpxe/hidemem.h>
|
2007-07-05 23:30:34 +02:00
|
|
|
#include <gpxe/memmap.h>
|
2007-01-16 09:36:42 +01:00
|
|
|
#include <gpxe/umalloc.h>
|
2007-01-12 04:15:29 +01:00
|
|
|
|
2007-01-12 04:28:03 +01:00
|
|
|
/** Alignment of external allocated memory */
|
|
|
|
#define EM_ALIGN ( 4 * 1024 )
|
|
|
|
|
2007-01-12 04:15:29 +01:00
|
|
|
/** Equivalent of NOWHERE for user pointers */
|
|
|
|
#define UNOWHERE ( ~UNULL )
|
|
|
|
|
|
|
|
/** An external memory block */
|
|
|
|
struct external_memory {
|
|
|
|
/** Size of this memory block (excluding this header) */
|
|
|
|
size_t size;
|
|
|
|
/** Block is currently in use */
|
|
|
|
int used;
|
|
|
|
};
|
|
|
|
|
2007-07-05 23:30:34 +02:00
|
|
|
/** Top of heap */
|
|
|
|
static userptr_t top = UNULL;
|
|
|
|
|
|
|
|
/** Bottom of heap (current lowest allocated block) */
|
|
|
|
static userptr_t bottom = UNULL;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialise external heap
|
2007-01-12 04:15:29 +01:00
|
|
|
*
|
2007-07-05 23:30:34 +02:00
|
|
|
* @ret rc Return status code
|
2007-01-12 04:15:29 +01:00
|
|
|
*/
|
2007-07-05 23:30:34 +02:00
|
|
|
static int init_eheap ( void ) {
|
|
|
|
struct memory_map memmap;
|
|
|
|
unsigned long heap_size = 0;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
DBG ( "Allocating external heap\n" );
|
|
|
|
|
|
|
|
get_memmap ( &memmap );
|
|
|
|
for ( i = 0 ; i < memmap.count ; i++ ) {
|
|
|
|
struct memory_region *region = &memmap.regions[i];
|
|
|
|
unsigned long r_start, r_end;
|
|
|
|
unsigned long r_size;
|
|
|
|
|
|
|
|
DBG ( "Considering [%llx,%llx)\n", region->start, region->end);
|
|
|
|
|
|
|
|
/* Truncate block to 4GB */
|
|
|
|
if ( region->start > UINT_MAX ) {
|
|
|
|
DBG ( "...starts after 4GB\n" );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
r_start = region->start;
|
|
|
|
if ( region->end > UINT_MAX ) {
|
|
|
|
DBG ( "...end truncated to 4GB\n" );
|
|
|
|
r_end = 0; /* =4GB, given the wraparound */
|
|
|
|
} else {
|
|
|
|
r_end = region->end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Use largest block */
|
|
|
|
r_size = ( r_end - r_start );
|
|
|
|
if ( r_size > heap_size ) {
|
|
|
|
DBG ( "...new best block found\n" );
|
|
|
|
top = bottom = phys_to_user ( r_end );
|
|
|
|
heap_size = r_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-02-15 08:50:23 +01:00
|
|
|
if ( ! heap_size ) {
|
2007-07-05 23:30:34 +02:00
|
|
|
DBG ( "No external heap available\n" );
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG ( "External heap grows downwards from %lx\n",
|
|
|
|
user_to_phys ( top, 0 ) );
|
|
|
|
return 0;
|
|
|
|
}
|
2007-01-12 04:15:29 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Collect free blocks
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void ecollect_free ( void ) {
|
|
|
|
struct external_memory extmem;
|
|
|
|
|
|
|
|
/* Walk the free list and collect empty blocks */
|
2007-07-05 23:30:34 +02:00
|
|
|
while ( bottom != top ) {
|
2007-01-12 04:15:29 +01:00
|
|
|
copy_from_user ( &extmem, bottom, -sizeof ( extmem ),
|
|
|
|
sizeof ( extmem ) );
|
|
|
|
if ( extmem.used )
|
|
|
|
break;
|
|
|
|
DBG ( "EXTMEM freeing [%lx,%lx)\n", user_to_phys ( bottom, 0 ),
|
|
|
|
user_to_phys ( bottom, extmem.size ) );
|
|
|
|
bottom = userptr_add ( bottom,
|
|
|
|
( extmem.size + sizeof ( extmem ) ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reallocate external memory
|
|
|
|
*
|
2007-01-16 09:36:42 +01:00
|
|
|
* @v old_ptr Memory previously allocated by umalloc(), or UNULL
|
2007-01-12 04:15:29 +01:00
|
|
|
* @v new_size Requested size
|
|
|
|
* @ret new_ptr Allocated memory, or UNULL
|
|
|
|
*
|
|
|
|
* Calling realloc() with a new size of zero is a valid way to free a
|
|
|
|
* memory block.
|
|
|
|
*/
|
2008-10-13 05:32:11 +02:00
|
|
|
static userptr_t memtop_urealloc ( userptr_t ptr, size_t new_size ) {
|
2007-01-12 04:15:29 +01:00
|
|
|
struct external_memory extmem;
|
|
|
|
userptr_t new = ptr;
|
2007-01-12 04:28:03 +01:00
|
|
|
size_t align;
|
2007-07-05 23:30:34 +02:00
|
|
|
int rc;
|
2007-01-12 04:15:29 +01:00
|
|
|
|
|
|
|
/* Initialise external memory allocator if necessary */
|
2009-02-15 08:50:23 +01:00
|
|
|
if ( bottom == top ) {
|
2007-07-05 23:30:34 +02:00
|
|
|
if ( ( rc = init_eheap() ) != 0 )
|
2009-02-15 08:50:23 +01:00
|
|
|
return UNULL;
|
2007-07-05 23:30:34 +02:00
|
|
|
}
|
2007-01-12 04:15:29 +01:00
|
|
|
|
|
|
|
/* Get block properties into extmem */
|
|
|
|
if ( ptr && ( ptr != UNOWHERE ) ) {
|
|
|
|
/* Determine old size */
|
|
|
|
copy_from_user ( &extmem, ptr, -sizeof ( extmem ),
|
|
|
|
sizeof ( extmem ) );
|
|
|
|
} else {
|
|
|
|
/* Create a zero-length block */
|
|
|
|
ptr = bottom = userptr_add ( bottom, -sizeof ( extmem ) );
|
2007-01-12 04:28:03 +01:00
|
|
|
DBG ( "EXTMEM allocating [%lx,%lx)\n",
|
|
|
|
user_to_phys ( ptr, 0 ), user_to_phys ( ptr, 0 ) );
|
2007-01-12 04:15:29 +01:00
|
|
|
extmem.size = 0;
|
|
|
|
}
|
|
|
|
extmem.used = ( new_size > 0 );
|
|
|
|
|
|
|
|
/* Expand/shrink block if possible */
|
|
|
|
if ( ptr == bottom ) {
|
|
|
|
/* Update block */
|
|
|
|
new = userptr_add ( ptr, - ( new_size - extmem.size ) );
|
2007-01-12 04:28:03 +01:00
|
|
|
align = ( user_to_phys ( new, 0 ) & ( EM_ALIGN - 1 ) );
|
|
|
|
new_size += align;
|
|
|
|
new = userptr_add ( new, -align );
|
2007-01-12 04:15:29 +01:00
|
|
|
DBG ( "EXTMEM expanding [%lx,%lx) to [%lx,%lx)\n",
|
|
|
|
user_to_phys ( ptr, 0 ),
|
|
|
|
user_to_phys ( ptr, extmem.size ),
|
|
|
|
user_to_phys ( new, 0 ),
|
|
|
|
user_to_phys ( new, new_size ));
|
|
|
|
memmove_user ( new, 0, ptr, 0, ( ( extmem.size < new_size ) ?
|
|
|
|
extmem.size : new_size ) );
|
|
|
|
extmem.size = new_size;
|
|
|
|
bottom = new;
|
|
|
|
} else {
|
|
|
|
/* Cannot expand; can only pretend to shrink */
|
|
|
|
if ( new_size > extmem.size ) {
|
|
|
|
/* Refuse to expand */
|
|
|
|
DBG ( "EXTMEM cannot expand [%lx,%lx)\n",
|
|
|
|
user_to_phys ( ptr, 0 ),
|
|
|
|
user_to_phys ( ptr, extmem.size ) );
|
|
|
|
return UNULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write back block properties */
|
|
|
|
copy_to_user ( new, -sizeof ( extmem ), &extmem,
|
|
|
|
sizeof ( extmem ) );
|
|
|
|
|
|
|
|
/* Collect any free blocks and update hidden memory region */
|
|
|
|
ecollect_free();
|
2008-08-18 08:14:27 +02:00
|
|
|
hide_umalloc ( user_to_phys ( bottom, -sizeof ( extmem ) ),
|
|
|
|
user_to_phys ( top, 0 ) );
|
2007-01-12 04:15:29 +01:00
|
|
|
|
|
|
|
return ( new_size ? new : UNOWHERE );
|
|
|
|
}
|
|
|
|
|
2008-10-13 05:32:11 +02:00
|
|
|
PROVIDE_UMALLOC ( memtop, urealloc, memtop_urealloc );
|