17c478bd9Sstevel@tonic-gate #include "etherboot.h"
27c478bd9Sstevel@tonic-gate #define DEBUG_BASEMEM
37c478bd9Sstevel@tonic-gate /* Routines to allocate base memory in a BIOS-compatible way, by
47c478bd9Sstevel@tonic-gate * updating the Free Base Memory Size counter at 40:13h.
57c478bd9Sstevel@tonic-gate *
67c478bd9Sstevel@tonic-gate * Michael Brown <mbrown@fensystems.co.uk> (mcb30)
77c478bd9Sstevel@tonic-gate * $Id: basemem.c,v 1.5 2004/06/17 12:48:08 fengshuo Exp $
87c478bd9Sstevel@tonic-gate */
97c478bd9Sstevel@tonic-gate
107c478bd9Sstevel@tonic-gate #define fbms ( ( uint16_t * ) phys_to_virt ( 0x413 ) )
117c478bd9Sstevel@tonic-gate #define BASE_MEMORY_MAX ( 640 )
127c478bd9Sstevel@tonic-gate #define FREE_BLOCK_MAGIC ( ('!'<<0) + ('F'<<8) + ('R'<<16) + ('E'<<24) )
137c478bd9Sstevel@tonic-gate
147c478bd9Sstevel@tonic-gate typedef struct free_base_memory_block {
157c478bd9Sstevel@tonic-gate uint32_t magic;
167c478bd9Sstevel@tonic-gate uint16_t size_kb;
177c478bd9Sstevel@tonic-gate } free_base_memory_block_t;
187c478bd9Sstevel@tonic-gate
197c478bd9Sstevel@tonic-gate /* Return amount of free base memory in bytes
207c478bd9Sstevel@tonic-gate */
217c478bd9Sstevel@tonic-gate
get_free_base_memory(void)227c478bd9Sstevel@tonic-gate uint32_t get_free_base_memory ( void ) {
237c478bd9Sstevel@tonic-gate return *fbms << 10;
247c478bd9Sstevel@tonic-gate }
257c478bd9Sstevel@tonic-gate
267c478bd9Sstevel@tonic-gate /* Adjust the real mode stack pointer. We keep the real mode stack at
277c478bd9Sstevel@tonic-gate * the top of free base memory, rather than allocating space for it.
287c478bd9Sstevel@tonic-gate */
297c478bd9Sstevel@tonic-gate
adjust_real_mode_stack(void)307c478bd9Sstevel@tonic-gate static inline void adjust_real_mode_stack ( void ) {
317c478bd9Sstevel@tonic-gate /* real_mode_stack = ( *fbms << 10 ); */
327c478bd9Sstevel@tonic-gate }
337c478bd9Sstevel@tonic-gate
347c478bd9Sstevel@tonic-gate /* Allocate N bytes of base memory. Amount allocated will be rounded
357c478bd9Sstevel@tonic-gate * up to the nearest kB, since that's the granularity of the BIOS FBMS
367c478bd9Sstevel@tonic-gate * counter. Returns NULL if memory cannot be allocated.
377c478bd9Sstevel@tonic-gate */
387c478bd9Sstevel@tonic-gate
allot_base_memory(size_t size)397c478bd9Sstevel@tonic-gate void * allot_base_memory ( size_t size ) {
407c478bd9Sstevel@tonic-gate uint16_t size_kb = ( size + 1023 ) >> 10;
417c478bd9Sstevel@tonic-gate void *ptr = NULL;
427c478bd9Sstevel@tonic-gate
437c478bd9Sstevel@tonic-gate #ifdef DEBUG_BASEMEM
447c478bd9Sstevel@tonic-gate printf ( "Trying to allocate %d kB of base memory, %d kB free\n",
457c478bd9Sstevel@tonic-gate size_kb, *fbms );
467c478bd9Sstevel@tonic-gate #endif
477c478bd9Sstevel@tonic-gate
487c478bd9Sstevel@tonic-gate /* Free up any unused memory before we start */
497c478bd9Sstevel@tonic-gate free_unused_base_memory();
507c478bd9Sstevel@tonic-gate
517c478bd9Sstevel@tonic-gate /* Check available base memory */
527c478bd9Sstevel@tonic-gate if ( size_kb > *fbms ) { return NULL; }
537c478bd9Sstevel@tonic-gate
547c478bd9Sstevel@tonic-gate /* Reduce available base memory */
557c478bd9Sstevel@tonic-gate *fbms -= size_kb;
567c478bd9Sstevel@tonic-gate
577c478bd9Sstevel@tonic-gate /* Calculate address of memory allocated */
587c478bd9Sstevel@tonic-gate ptr = phys_to_virt ( *fbms << 10 );
597c478bd9Sstevel@tonic-gate
607c478bd9Sstevel@tonic-gate #ifdef DEBUG_BASEMEM
617c478bd9Sstevel@tonic-gate /* Zero out memory. We do this so that allocation of
627c478bd9Sstevel@tonic-gate * already-used space will show up in the form of a crash as
637c478bd9Sstevel@tonic-gate * soon as possible.
647c478bd9Sstevel@tonic-gate */
657c478bd9Sstevel@tonic-gate memset ( ptr, 0, size_kb << 10 );
667c478bd9Sstevel@tonic-gate #endif
677c478bd9Sstevel@tonic-gate
687c478bd9Sstevel@tonic-gate /* Adjust real mode stack pointer */
697c478bd9Sstevel@tonic-gate adjust_real_mode_stack ();
707c478bd9Sstevel@tonic-gate
717c478bd9Sstevel@tonic-gate return ptr;
727c478bd9Sstevel@tonic-gate }
737c478bd9Sstevel@tonic-gate
747c478bd9Sstevel@tonic-gate /* Free base memory allocated by allot_base_memory. The BIOS provides
757c478bd9Sstevel@tonic-gate * nothing better than a LIFO mechanism for freeing memory (i.e. it
767c478bd9Sstevel@tonic-gate * just has the single "total free memory" counter), but we improve
777c478bd9Sstevel@tonic-gate * upon this slightly; as long as you free all the allotted blocks, it
787c478bd9Sstevel@tonic-gate * doesn't matter what order you free them in. (This will only work
797c478bd9Sstevel@tonic-gate * for blocks that are freed via forget_base_memory()).
807c478bd9Sstevel@tonic-gate *
817c478bd9Sstevel@tonic-gate * Yes, it's annoying that you have to remember the size of the blocks
827c478bd9Sstevel@tonic-gate * you've allotted. However, since our granularity of allocation is
837c478bd9Sstevel@tonic-gate * 1K, the alternative is to risk wasting the occasional kB of base
847c478bd9Sstevel@tonic-gate * memory, which is a Bad Thing. Really, you should be using as
857c478bd9Sstevel@tonic-gate * little base memory as possible, so consider the awkwardness of the
867c478bd9Sstevel@tonic-gate * API to be a feature! :-)
877c478bd9Sstevel@tonic-gate */
887c478bd9Sstevel@tonic-gate
forget_base_memory(void * ptr,size_t size)897c478bd9Sstevel@tonic-gate void forget_base_memory ( void *ptr, size_t size ) {
907c478bd9Sstevel@tonic-gate uint16_t remainder = virt_to_phys(ptr) & 1023;
917c478bd9Sstevel@tonic-gate uint16_t size_kb = ( size + remainder + 1023 ) >> 10;
927c478bd9Sstevel@tonic-gate free_base_memory_block_t *free_block =
937c478bd9Sstevel@tonic-gate ( free_base_memory_block_t * ) ( ptr - remainder );
947c478bd9Sstevel@tonic-gate
957c478bd9Sstevel@tonic-gate if ( ( ptr == NULL ) || ( size == 0 ) ) { return; }
967c478bd9Sstevel@tonic-gate
977c478bd9Sstevel@tonic-gate #ifdef DEBUG_BASEMEM
987c478bd9Sstevel@tonic-gate printf ( "Trying to free %d bytes base memory at 0x%x\n",
997c478bd9Sstevel@tonic-gate size, virt_to_phys ( ptr ) );
1007c478bd9Sstevel@tonic-gate if ( remainder > 0 ) {
1017c478bd9Sstevel@tonic-gate printf ( "WARNING: destructively expanding free block "
1027c478bd9Sstevel@tonic-gate "downwards to 0x%x\n",
1037c478bd9Sstevel@tonic-gate virt_to_phys ( ptr - remainder ) );
1047c478bd9Sstevel@tonic-gate }
1057c478bd9Sstevel@tonic-gate #endif
1067c478bd9Sstevel@tonic-gate
1077c478bd9Sstevel@tonic-gate /* Mark every kilobyte within this block as free. This is
1087c478bd9Sstevel@tonic-gate * overkill for normal purposes, but helps when something has
1097c478bd9Sstevel@tonic-gate * allocated base memory with a granularity finer than the
1107c478bd9Sstevel@tonic-gate * BIOS granularity of 1kB. PXE ROMs tend to do this when
1117c478bd9Sstevel@tonic-gate * they allocate their own memory. This method allows us to
1127c478bd9Sstevel@tonic-gate * free their blocks (admittedly in a rather dangerous,
1137c478bd9Sstevel@tonic-gate * tread-on-anything-either-side sort of way, but there's no
1147c478bd9Sstevel@tonic-gate * other way to do it).
1157c478bd9Sstevel@tonic-gate *
1167c478bd9Sstevel@tonic-gate * Since we're marking every kB as free, there's actually no
1177c478bd9Sstevel@tonic-gate * need for recording the size of the blocks. However, we
1187c478bd9Sstevel@tonic-gate * keep this in so that debug messages are friendlier. It
1197c478bd9Sstevel@tonic-gate * probably adds around 8 bytes to the overall code size.
1207c478bd9Sstevel@tonic-gate */
1217c478bd9Sstevel@tonic-gate while ( size_kb > 0 ) {
1227c478bd9Sstevel@tonic-gate /* Mark this block as unused */
1237c478bd9Sstevel@tonic-gate free_block->magic = FREE_BLOCK_MAGIC;
1247c478bd9Sstevel@tonic-gate free_block->size_kb = size_kb;
1257c478bd9Sstevel@tonic-gate /* Move up by 1 kB */
1267c478bd9Sstevel@tonic-gate free_block = (void *)free_block + ( 1 << 10 );
1277c478bd9Sstevel@tonic-gate size_kb--;
1287c478bd9Sstevel@tonic-gate }
1297c478bd9Sstevel@tonic-gate
1307c478bd9Sstevel@tonic-gate /* Free up unused base memory */
1317c478bd9Sstevel@tonic-gate free_unused_base_memory();
1327c478bd9Sstevel@tonic-gate }
1337c478bd9Sstevel@tonic-gate
1347c478bd9Sstevel@tonic-gate /* Do the actual freeing of memory. This is split out from
1357c478bd9Sstevel@tonic-gate * forget_base_memory() so that it may be called separately. It
1367c478bd9Sstevel@tonic-gate * should be called whenever base memory is deallocated by an external
1377c478bd9Sstevel@tonic-gate * entity (if we can detect that it has done so) so that we get the
1387c478bd9Sstevel@tonic-gate * chance to free up our own blocks.
1397c478bd9Sstevel@tonic-gate */
free_unused_base_memory(void)1407c478bd9Sstevel@tonic-gate void free_unused_base_memory ( void ) {
1417c478bd9Sstevel@tonic-gate free_base_memory_block_t *free_block = NULL;
1427c478bd9Sstevel@tonic-gate
1437c478bd9Sstevel@tonic-gate /* Try to release memory back to the BIOS. Free all
1447c478bd9Sstevel@tonic-gate * consecutive blocks marked as free.
1457c478bd9Sstevel@tonic-gate */
1467c478bd9Sstevel@tonic-gate while ( 1 ) {
1477c478bd9Sstevel@tonic-gate /* Calculate address of next potential free block */
1487c478bd9Sstevel@tonic-gate free_block = ( free_base_memory_block_t * )
1497c478bd9Sstevel@tonic-gate phys_to_virt ( *fbms << 10 );
1507c478bd9Sstevel@tonic-gate
1517c478bd9Sstevel@tonic-gate /* Stop processing if we're all the way up to 640K or
1527c478bd9Sstevel@tonic-gate * if this is not a free block
1537c478bd9Sstevel@tonic-gate */
1547c478bd9Sstevel@tonic-gate if ( ( *fbms == BASE_MEMORY_MAX ) ||
1557c478bd9Sstevel@tonic-gate ( free_block->magic != FREE_BLOCK_MAGIC ) ) {
1567c478bd9Sstevel@tonic-gate break;
1577c478bd9Sstevel@tonic-gate }
1587c478bd9Sstevel@tonic-gate
1597c478bd9Sstevel@tonic-gate /* Return memory to BIOS */
1607c478bd9Sstevel@tonic-gate *fbms += free_block->size_kb;
1617c478bd9Sstevel@tonic-gate
1627c478bd9Sstevel@tonic-gate #ifdef DEBUG_BASEMEM
1637c478bd9Sstevel@tonic-gate printf ( "Freed %d kB base memory, %d kB now free\n",
1647c478bd9Sstevel@tonic-gate free_block->size_kb, *fbms );
1657c478bd9Sstevel@tonic-gate
1667c478bd9Sstevel@tonic-gate /* Zero out freed block. We do this in case
1677c478bd9Sstevel@tonic-gate * the block contained any structures that
1687c478bd9Sstevel@tonic-gate * might be located by scanning through
1697c478bd9Sstevel@tonic-gate * memory.
1707c478bd9Sstevel@tonic-gate */
1717c478bd9Sstevel@tonic-gate memset ( free_block, 0, free_block->size_kb << 10 );
1727c478bd9Sstevel@tonic-gate #endif
1737c478bd9Sstevel@tonic-gate }
1747c478bd9Sstevel@tonic-gate
1757c478bd9Sstevel@tonic-gate /* Adjust real mode stack pointer */
1767c478bd9Sstevel@tonic-gate adjust_real_mode_stack ();
1777c478bd9Sstevel@tonic-gate }
1787c478bd9Sstevel@tonic-gate
179