d3b42d0da00b79e26dd4a59f55efb86dcf5a929b
[people/adir/gpxe.git] / src / arch / i386 / core / relocate.c
1 #include <io.h>
2 #include <registers.h>
3 #include <gpxe/memmap.h>
4
5 /*
6  * Originally by Eric Biederman
7  *
8  * Heavily modified by Michael Brown 
9  *
10  */
11
12 /*
13  * The linker passes in the symbol _max_align, which is the alignment
14  * that we must preserve, in bytes.
15  *
16  */
17 extern char _max_align[];
18 #define max_align ( ( unsigned int ) _max_align )
19
20 /* Linker symbols */
21 extern char _text[];
22 extern char _end[];
23
24 /* within 1MB of 4GB is too close. 
25  * MAX_ADDR is the maximum address we can easily do DMA to.
26  *
27  * Not sure where this constraint comes from, but kept it from Eric's
28  * old code - mcb30
29  */
30 #define MAX_ADDR (0xfff00000UL)
31
32 /**
33  * Relocate Etherboot
34  *
35  * @v ix86              x86 register dump from prefix
36  * @ret ix86            x86 registers to return to prefix
37  *
38  * This finds a suitable location for Etherboot near the top of 32-bit
39  * address space, and returns the physical address of the new location
40  * to the prefix in %edi.
41  */
42 void relocate ( struct i386_all_regs *ix86 ) {
43         struct memory_map memmap;
44         unsigned long start, end, size, padded_size;
45         unsigned long new_start, new_end;
46         unsigned i;
47
48         /* Get memory map and current location */
49         get_memmap ( &memmap );
50         start = virt_to_phys ( _text );
51         end = virt_to_phys ( _end );
52         size = ( end - start );
53         padded_size = ( size + max_align - 1 );
54
55         DBG ( "Relocate: currently at [%lx,%lx)\n"
56               "...need %lx bytes for %d-byte alignment\n",
57               start, end, padded_size, max_align );
58
59         /* Walk through the memory map and find the highest address
60          * below 4GB that etherboot will fit into.  Ensure etherboot
61          * lies entirely within a range with A20=0.  This means that
62          * even if something screws up the state of the A20 line, the
63          * etherboot code is still visible and we have a chance to
64          * diagnose the problem.
65          */
66         new_end = end;
67         for ( i = 0 ; i < memmap.count ; i++ ) {
68                 struct memory_region *region = &memmap.regions[i];
69                 unsigned long r_start, r_end;
70
71                 DBG ( "Considering [%llx,%llx)\n", region->start, region->end);
72                 
73                 /* Truncate block to MAX_ADDR.  This will be less than
74                  * 4GB, which means that we can get away with using
75                  * just 32-bit arithmetic after this stage.
76                  */
77                 if ( region->start > MAX_ADDR ) {
78                         DBG ( "...starts after MAX_ADDR=%lx\n", MAX_ADDR );
79                         continue;
80                 }
81                 r_start = region->start;
82                 if ( region->end > MAX_ADDR ) {
83                         DBG ( "...end truncated to MAX_ADDR=%lx\n", MAX_ADDR );
84                         r_end = MAX_ADDR;
85                 } else {
86                         r_end = region->end;
87                 }
88                 
89                 /* Shrink the range down to use only even megabytes
90                  * (i.e. A20=0).
91                  */
92                 if ( ( r_end - 1 ) & 0x100000 ) {
93                         /* If last byte that might be used (r_end-1)
94                          * is in an odd megabyte, round down r_end to
95                          * the top of the next even megabyte.
96                          */
97                         r_end = ( r_end - 1 ) & ~0xfffff;
98                         DBG ( "...end truncated to %lx "
99                               "(avoid ending in odd megabyte)\n",
100                               r_end );
101                 } else if ( ( r_end - size ) & 0x100000 ) {
102                         /* If the last byte that might be used
103                          * (r_end-1) is in an even megabyte, but the
104                          * first byte that might be used (r_end-size)
105                          * is an odd megabyte, round down to the top
106                          * of the next even megabyte.
107                          * 
108                          * Make sure that we don't accidentally wrap
109                          * r_end below 0.
110                          */
111                         if ( r_end > 0x100000 ) {
112                                 r_end = ( r_end - 0x100000 ) & ~0xfffff;
113                                 DBG ( "...end truncated to %lx "
114                                       "(avoid starting in odd megabyte)\n",
115                                       r_end );
116                         }
117                 }
118
119                 DBG ( "...usable portion is [%lx,%lx)\n", r_start, r_end );
120
121                 /* If we have rounded down r_end below r_ start, skip
122                  * this block.
123                  */
124                 if ( r_end < r_start ) {
125                         DBG ( "...truncated to negative size\n" );
126                         continue;
127                 }
128
129                 /* Check that there is enough space to fit in Etherboot */
130                 if ( ( r_end - r_start ) < size ) {
131                         DBG ( "...too small (need %lx bytes)\n", size );
132                         continue;
133                 }
134
135                 /* If the start address of the Etherboot we would
136                  * place in this block is higher than the end address
137                  * of the current highest block, use this block.
138                  *
139                  * Note that this avoids overlaps with the current
140                  * Etherboot, as well as choosing the highest of all
141                  * viable blocks.
142                  */
143                 if ( ( r_end - size ) > new_end ) {
144                         new_end = r_end;
145                         DBG ( "...new best block found.\n" );
146                 }
147         }
148
149         /* Calculate new location of Etherboot, and align it to the
150          * required alignemnt.
151          */
152         new_start = new_end - padded_size;
153         new_start += ( start - new_start ) & ( max_align - 1 );
154         new_end = new_start + size;
155
156         DBG ( "Relocating from [%lx,%lx) to [%lx,%lx)\n",
157               start, end, new_start, new_end );
158         
159         /* Let prefix know what to copy */
160         ix86->regs.esi = start;
161         ix86->regs.edi = new_start;
162         ix86->regs.ecx = size;
163 }