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