3a2702a1f3c7eeeafbdaf0039ff6e90c8f994ad4
[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 #undef DBG
46 #ifdef DEBUG_RELOCATE
47 #define DBG(...) printf ( __VA_ARGS__ )
48 #else
49 #define DBG(...)
50 #endif
51
52 static void relocate ( void ) {
53         unsigned long addr, eaddr, size;
54         unsigned i;
55         struct post_reloc_fn *post_reloc_fn;
56
57         /* Walk through the memory map and find the highest address
58          * below 4GB that etherboot will fit into.  Ensure etherboot
59          * lies entirely within a range with A20=0.  This means that
60          * even if something screws up the state of the A20 line, the
61          * etherboot code is still visible and we have a chance to
62          * diagnose the problem.
63          */
64
65         /* First find the size of etherboot, including enough space to
66          * pad it to the required alignment
67          */
68         size = _end - _text + max_align - 1;
69
70         /* Current end address of Etherboot.  If the current etherboot
71          * is beyond MAX_ADDR pretend it is at the lowest possible
72          * address.
73          */
74         eaddr = virt_to_phys(_end);
75         if ( eaddr > MAX_ADDR ) {
76                 eaddr = 0;
77         }
78
79         DBG ( "Relocate: currently at [%x,%x)\n"
80               "...need %x bytes for %d-byte alignment\n",
81               virt_to_phys ( _text ), eaddr, size, max_align );
82
83         for ( i = 0; i < meminfo.map_count; i++ ) {
84                 unsigned long r_start, r_end;
85
86                 DBG ( "Considering [%x%x,%x%x)\n",
87                       ( unsigned long ) ( meminfo.map[i].addr >> 32 ),
88                       ( unsigned long ) meminfo.map[i].addr,
89                       ( unsigned long )
90                        ( ( meminfo.map[i].addr + meminfo.map[i].size ) >> 32 ),
91                       ( unsigned long )
92                        ( meminfo.map[i].addr + meminfo.map[i].size ) );
93                 
94                 /* Check block is usable memory */
95                 if (meminfo.map[i].type != E820_RAM) {
96                         DBG ( "...not RAM\n" );
97                         continue;
98                 }
99
100                 /* Truncate block to MAX_ADDR.  This will be less than
101                  * 4GB, which means that we can get away with using
102                  * just 32-bit arithmetic after this stage.
103                  */
104                 if ( meminfo.map[i].addr > MAX_ADDR ) {
105                         DBG ( "...starts after MAX_ADDR=%x\n", MAX_ADDR );
106                         continue;
107                 }
108                 r_start = meminfo.map[i].addr;
109                 if ( meminfo.map[i].addr + meminfo.map[i].size > MAX_ADDR ) {
110                         r_end = MAX_ADDR;
111                         DBG ( "...end truncated to MAX_ADDR=%x\n", MAX_ADDR );
112                 } else {
113                         r_end = meminfo.map[i].addr + meminfo.map[i].size;
114                 }
115
116                 /* Shrink the range down to use only even megabytes
117                  * (i.e. A20=0).
118                  */
119                 if ( ( r_end - 1 ) & 0x100000 ) {
120                         /* If last byte that might be used (r_end-1)
121                          * is in an odd megabyte, round down r_end to
122                          * the top of the next even megabyte.
123                          */
124                         r_end = ( r_end - 1 ) & ~0xfffff;
125                         DBG ( "...end truncated to %x "
126                               "(avoid ending in odd megabyte)\n",
127                               r_end );
128                 } else if ( ( r_end - size ) & 0x100000 ) {
129                         /* If the last byte that might be used
130                          * (r_end-1) is in an even megabyte, but the
131                          * first byte that might be used (r_end-size)
132                          * is an odd megabyte, round down to the top
133                          * of the next even megabyte.
134                          * 
135                          * Make sure that we don't accidentally wrap
136                          * r_end below 0.
137                          */
138                         if ( r_end > 0x100000 ) {
139                                 r_end = ( r_end - 0x100000 ) & ~0xfffff;
140                                 DBG ( "...end truncated to %x "
141                                       "(avoid starting in odd megabyte)\n",
142                                       r_end );
143                         }
144                 }
145
146                 DBG ( "...usable portion is [%x,%x)\n", r_start, r_end );
147
148                 /* If we have rounded down r_end below r_ start, skip
149                  * this block.
150                  */
151                 if ( r_end < r_start ) {
152                         DBG ( "...truncated to negative size\n" );
153                         continue;
154                 }
155
156                 /* Check that there is enough space to fit in Etherboot */
157                 if ( r_end - r_start < size ) {
158                         DBG ( "...too small (need %x bytes)\n", size );
159                         continue;
160                 }
161
162                 /* If the start address of the Etherboot we would
163                  * place in this block is higher than the end address
164                  * of the current highest block, use this block.
165                  *
166                  * Note that this avoids overlaps with the current
167                  * Etherboot, as well as choosing the highest of all
168                  * viable blocks.
169                  */
170                 if ( r_end - size > eaddr ) {
171                         eaddr = r_end;
172                         DBG ( "...new best block found.\n" );
173                 }
174         }
175
176         DBG ( "New location will be in [%x,%x)\n", eaddr - size, eaddr );
177
178         /* Calculate new location of Etherboot, and align it to the
179          * required alignemnt.
180          */
181         addr = eaddr - size;
182         addr += ( virt_to_phys ( _text ) - addr ) & ( max_align - 1 );
183         DBG ( "After alignment, new location is [%x,%x)\n",
184               addr, addr + _end - _text );
185
186         if ( addr != virt_to_phys ( _text ) ) {
187                 DBG ( "Relocating _text from: [%lx,%lx) to [%lx,%lx)\n",
188                       virt_to_phys ( _text ), virt_to_phys ( _end ),
189                       addr, addr + _end - _text );
190
191                 relocate_to ( addr );
192                 /* Note that we cannot make real-mode calls
193                  * (e.g. printf) at this point, because librm has just
194                  * been moved to high memory.
195                  */
196
197                 /* Call any registered post-relocation functions.
198                  * librm has a post-relocation function to install a
199                  * new librm into base memory.
200                  */
201                 for ( post_reloc_fn = post_reloc_fns;
202                       post_reloc_fn < post_reloc_fns_end ; post_reloc_fn++ ) {
203                         if ( post_reloc_fn->post_reloc )
204                                 post_reloc_fn->post_reloc ();
205                 }
206                 
207         }
208 }
209
210 INIT_FN ( INIT_RELOCATE, relocate, NULL, NULL );
211
212 #endif /* ! KEEP_IT_REAL */