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