1 #include "etherboot.h" 2 #define DEBUG_BASEMEM 3 /* Routines to allocate base memory in a BIOS-compatible way, by 4 * updating the Free Base Memory Size counter at 40:13h. 5 * 6 * Michael Brown <mbrown@fensystems.co.uk> (mcb30) 7 * $Id: basemem.c,v 1.5 2004/06/17 12:48:08 fengshuo Exp $ 8 */ 9 10 #define fbms ( ( uint16_t * ) phys_to_virt ( 0x413 ) ) 11 #define BASE_MEMORY_MAX ( 640 ) 12 #define FREE_BLOCK_MAGIC ( ('!'<<0) + ('F'<<8) + ('R'<<16) + ('E'<<24) ) 13 14 typedef struct free_base_memory_block { 15 uint32_t magic; 16 uint16_t size_kb; 17 } free_base_memory_block_t; 18 19 /* Return amount of free base memory in bytes 20 */ 21 22 uint32_t get_free_base_memory ( void ) { 23 return *fbms << 10; 24 } 25 26 /* Adjust the real mode stack pointer. We keep the real mode stack at 27 * the top of free base memory, rather than allocating space for it. 28 */ 29 30 static inline void adjust_real_mode_stack ( void ) { 31 /* real_mode_stack = ( *fbms << 10 ); */ 32 } 33 34 /* Allocate N bytes of base memory. Amount allocated will be rounded 35 * up to the nearest kB, since that's the granularity of the BIOS FBMS 36 * counter. Returns NULL if memory cannot be allocated. 37 */ 38 39 void * allot_base_memory ( size_t size ) { 40 uint16_t size_kb = ( size + 1023 ) >> 10; 41 void *ptr = NULL; 42 43 #ifdef DEBUG_BASEMEM 44 printf ( "Trying to allocate %d kB of base memory, %d kB free\n", 45 size_kb, *fbms ); 46 #endif 47 48 /* Free up any unused memory before we start */ 49 free_unused_base_memory(); 50 51 /* Check available base memory */ 52 if ( size_kb > *fbms ) { return NULL; } 53 54 /* Reduce available base memory */ 55 *fbms -= size_kb; 56 57 /* Calculate address of memory allocated */ 58 ptr = phys_to_virt ( *fbms << 10 ); 59 60 #ifdef DEBUG_BASEMEM 61 /* Zero out memory. We do this so that allocation of 62 * already-used space will show up in the form of a crash as 63 * soon as possible. 64 */ 65 memset ( ptr, 0, size_kb << 10 ); 66 #endif 67 68 /* Adjust real mode stack pointer */ 69 adjust_real_mode_stack (); 70 71 return ptr; 72 } 73 74 /* Free base memory allocated by allot_base_memory. The BIOS provides 75 * nothing better than a LIFO mechanism for freeing memory (i.e. it 76 * just has the single "total free memory" counter), but we improve 77 * upon this slightly; as long as you free all the allotted blocks, it 78 * doesn't matter what order you free them in. (This will only work 79 * for blocks that are freed via forget_base_memory()). 80 * 81 * Yes, it's annoying that you have to remember the size of the blocks 82 * you've allotted. However, since our granularity of allocation is 83 * 1K, the alternative is to risk wasting the occasional kB of base 84 * memory, which is a Bad Thing. Really, you should be using as 85 * little base memory as possible, so consider the awkwardness of the 86 * API to be a feature! :-) 87 */ 88 89 void forget_base_memory ( void *ptr, size_t size ) { 90 uint16_t remainder = virt_to_phys(ptr) & 1023; 91 uint16_t size_kb = ( size + remainder + 1023 ) >> 10; 92 free_base_memory_block_t *free_block = 93 ( free_base_memory_block_t * ) ( ptr - remainder ); 94 95 if ( ( ptr == NULL ) || ( size == 0 ) ) { return; } 96 97 #ifdef DEBUG_BASEMEM 98 printf ( "Trying to free %d bytes base memory at 0x%x\n", 99 size, virt_to_phys ( ptr ) ); 100 if ( remainder > 0 ) { 101 printf ( "WARNING: destructively expanding free block " 102 "downwards to 0x%x\n", 103 virt_to_phys ( ptr - remainder ) ); 104 } 105 #endif 106 107 /* Mark every kilobyte within this block as free. This is 108 * overkill for normal purposes, but helps when something has 109 * allocated base memory with a granularity finer than the 110 * BIOS granularity of 1kB. PXE ROMs tend to do this when 111 * they allocate their own memory. This method allows us to 112 * free their blocks (admittedly in a rather dangerous, 113 * tread-on-anything-either-side sort of way, but there's no 114 * other way to do it). 115 * 116 * Since we're marking every kB as free, there's actually no 117 * need for recording the size of the blocks. However, we 118 * keep this in so that debug messages are friendlier. It 119 * probably adds around 8 bytes to the overall code size. 120 */ 121 while ( size_kb > 0 ) { 122 /* Mark this block as unused */ 123 free_block->magic = FREE_BLOCK_MAGIC; 124 free_block->size_kb = size_kb; 125 /* Move up by 1 kB */ 126 free_block = (void *)free_block + ( 1 << 10 ); 127 size_kb--; 128 } 129 130 /* Free up unused base memory */ 131 free_unused_base_memory(); 132 } 133 134 /* Do the actual freeing of memory. This is split out from 135 * forget_base_memory() so that it may be called separately. It 136 * should be called whenever base memory is deallocated by an external 137 * entity (if we can detect that it has done so) so that we get the 138 * chance to free up our own blocks. 139 */ 140 void free_unused_base_memory ( void ) { 141 free_base_memory_block_t *free_block = NULL; 142 143 /* Try to release memory back to the BIOS. Free all 144 * consecutive blocks marked as free. 145 */ 146 while ( 1 ) { 147 /* Calculate address of next potential free block */ 148 free_block = ( free_base_memory_block_t * ) 149 phys_to_virt ( *fbms << 10 ); 150 151 /* Stop processing if we're all the way up to 640K or 152 * if this is not a free block 153 */ 154 if ( ( *fbms == BASE_MEMORY_MAX ) || 155 ( free_block->magic != FREE_BLOCK_MAGIC ) ) { 156 break; 157 } 158 159 /* Return memory to BIOS */ 160 *fbms += free_block->size_kb; 161 162 #ifdef DEBUG_BASEMEM 163 printf ( "Freed %d kB base memory, %d kB now free\n", 164 free_block->size_kb, *fbms ); 165 166 /* Zero out freed block. We do this in case 167 * the block contained any structures that 168 * might be located by scanning through 169 * memory. 170 */ 171 memset ( free_block, 0, free_block->size_kb << 10 ); 172 #endif 173 } 174 175 /* Adjust real mode stack pointer */ 176 adjust_real_mode_stack (); 177 } 178 179