Standardised debug mechanism in place now.
[people/xl0/gpxe.git] / src / arch / i386 / core / relocate.c
1 #include "virtaddr.h"
2 #include "memsizes.h"
3 #include "osdep.h"
4 #include "etherboot.h"
5 #include "init.h"
6 #include "relocate.h"
7
8 #ifndef KEEP_IT_REAL
9
10 /* by Eric Biederman */
11
12 /* On some platforms etherboot is compiled as a shared library, and we use
13  * the ELF pic support to make it relocateable.  This works very nicely
14  * for code, but since no one has implemented PIC data yet pointer
15  * values in variables are a a problem.  Global variables are a
16  * pain but the return addresses on the stack are the worst.  On these
17  * platforms relocate_to will restart etherboot, to ensure the stack
18  * is reinitialize and hopefully get the global variables
19  * appropriately reinitialized as well.
20  * 
21  */
22
23 /*
24  * relocate() must be called without any hardware resources pointing
25  * at the current copy of Etherboot.  The easiest way to achieve this
26  * is to call relocate() from within arch_initialise(), before the NIC
27  * gets touched in any way.
28  *
29  */
30
31 /*
32  * The linker passes in the symbol _max_align, which is the alignment
33  * that we must preserve, in bytes.
34  *
35  */
36 extern char _max_align[];
37 #define max_align ( ( unsigned int ) _max_align )
38
39 /* Linker symbols */
40 extern char _text[];
41 extern char _end[];
42 extern struct post_reloc_fn post_reloc_fns[];
43 extern struct post_reloc_fn post_reloc_fns_end[];
44
45 static void relocate ( void ) {
46         unsigned long addr, eaddr, size;
47         unsigned i;
48         struct post_reloc_fn *post_reloc_fn;
49
50         /* Walk through the memory map and find the highest address
51          * below 4GB that etherboot will fit into.  Ensure etherboot
52          * lies entirely within a range with A20=0.  This means that
53          * even if something screws up the state of the A20 line, the
54          * etherboot code is still visible and we have a chance to
55          * diagnose the problem.
56          */
57
58         /* First find the size of etherboot, including enough space to
59          * pad it to the required alignment
60          */
61         size = _end - _text + max_align - 1;
62
63         /* Current end address of Etherboot.  If the current etherboot
64          * is beyond MAX_ADDR pretend it is at the lowest possible
65          * address.
66          */
67         eaddr = virt_to_phys(_end);
68         if ( eaddr > MAX_ADDR ) {
69                 eaddr = 0;
70         }
71
72         DBG ( "Relocate: currently at [%x,%x)\n"
73               "...need %x bytes for %d-byte alignment\n",
74               virt_to_phys ( _text ), eaddr, size, max_align );
75
76         for ( i = 0; i < meminfo.map_count; i++ ) {
77                 unsigned long r_start, r_end;
78
79                 DBG ( "Considering [%x%x,%x%x)\n",
80                       ( unsigned long ) ( meminfo.map[i].addr >> 32 ),
81                       ( unsigned long ) meminfo.map[i].addr,
82                       ( unsigned long )
83                        ( ( meminfo.map[i].addr + meminfo.map[i].size ) >> 32 ),
84                       ( unsigned long )
85                        ( meminfo.map[i].addr + meminfo.map[i].size ) );
86                 
87                 /* Check block is usable memory */
88                 if (meminfo.map[i].type != E820_RAM) {
89                         DBG ( "...not RAM\n" );
90                         continue;
91                 }
92
93                 /* Truncate block to MAX_ADDR.  This will be less than
94                  * 4GB, which means that we can get away with using
95                  * just 32-bit arithmetic after this stage.
96                  */
97                 if ( meminfo.map[i].addr > MAX_ADDR ) {
98                         DBG ( "...starts after MAX_ADDR=%x\n", MAX_ADDR );
99                         continue;
100                 }
101                 r_start = meminfo.map[i].addr;
102                 if ( meminfo.map[i].addr + meminfo.map[i].size > MAX_ADDR ) {
103                         r_end = MAX_ADDR;
104                         DBG ( "...end truncated to MAX_ADDR=%x\n", MAX_ADDR );
105                 } else {
106                         r_end = meminfo.map[i].addr + meminfo.map[i].size;
107                 }
108
109                 /* Shrink the range down to use only even megabytes
110                  * (i.e. A20=0).
111                  */
112                 if ( ( r_end - 1 ) & 0x100000 ) {
113                         /* If last byte that might be used (r_end-1)
114                          * is in an odd megabyte, round down r_end to
115                          * the top of the next even megabyte.
116                          */
117                         r_end = ( r_end - 1 ) & ~0xfffff;
118                         DBG ( "...end truncated to %x "
119                               "(avoid ending in odd megabyte)\n",
120                               r_end );
121                 } else if ( ( r_end - size ) & 0x100000 ) {
122                         /* If the last byte that might be used
123                          * (r_end-1) is in an even megabyte, but the
124                          * first byte that might be used (r_end-size)
125                          * is an odd megabyte, round down to the top
126                          * of the next even megabyte.
127                          * 
128                          * Make sure that we don't accidentally wrap
129                          * r_end below 0.
130                          */
131                         if ( r_end > 0x100000 ) {
132                                 r_end = ( r_end - 0x100000 ) & ~0xfffff;
133                                 DBG ( "...end truncated to %x "
134                                       "(avoid starting in odd megabyte)\n",
135                                       r_end );
136                         }
137                 }
138
139                 DBG ( "...usable portion is [%x,%x)\n", r_start, r_end );
140
141                 /* If we have rounded down r_end below r_ start, skip
142                  * this block.
143                  */
144                 if ( r_end < r_start ) {
145                         DBG ( "...truncated to negative size\n" );
146                         continue;
147                 }
148
149                 /* Check that there is enough space to fit in Etherboot */
150                 if ( r_end - r_start < size ) {
151                         DBG ( "...too small (need %x bytes)\n", size );
152                         continue;
153                 }
154
155                 /* If the start address of the Etherboot we would
156                  * place in this block is higher than the end address
157                  * of the current highest block, use this block.
158                  *
159                  * Note that this avoids overlaps with the current
160                  * Etherboot, as well as choosing the highest of all
161                  * viable blocks.
162                  */
163                 if ( r_end - size > eaddr ) {
164                         eaddr = r_end;
165                         DBG ( "...new best block found.\n" );
166                 }
167         }
168
169         DBG ( "New location will be in [%x,%x)\n", eaddr - size, eaddr );
170
171         /* Calculate new location of Etherboot, and align it to the
172          * required alignemnt.
173          */
174         addr = eaddr - size;
175         addr += ( virt_to_phys ( _text ) - addr ) & ( max_align - 1 );
176         DBG ( "After alignment, new location is [%x,%x)\n",
177               addr, addr + _end - _text );
178
179         if ( addr != virt_to_phys ( _text ) ) {
180                 DBG ( "Relocating _text from: [%lx,%lx) to [%lx,%lx)\n",
181                       virt_to_phys ( _text ), virt_to_phys ( _end ),
182                       addr, addr + _end - _text );
183
184                 relocate_to ( addr );
185                 /* Note that we cannot make real-mode calls
186                  * (e.g. printf) at this point, because librm has just
187                  * been moved to high memory.
188                  */
189
190                 /* Call any registered post-relocation functions.
191                  * librm has a post-relocation function to install a
192                  * new librm into base memory.
193                  */
194                 for ( post_reloc_fn = post_reloc_fns;
195                       post_reloc_fn < post_reloc_fns_end ; post_reloc_fn++ ) {
196                         if ( post_reloc_fn->post_reloc )
197                                 post_reloc_fn->post_reloc ();
198                 }
199                 
200         }
201 }
202
203 INIT_FN ( INIT_RELOCATE, relocate, NULL, NULL );
204
205 #endif /* ! KEEP_IT_REAL */