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