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