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