summaryrefslogtreecommitdiff
path: root/gpxe/src/arch/i386/core/relocate.c
blob: 44e764fe04b6244c1c157d45acc01249e457452a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include <gpxe/io.h>
#include <registers.h>
#include <gpxe/memmap.h>

/*
 * Originally by Eric Biederman
 *
 * Heavily modified by Michael Brown 
 *
 */

FILE_LICENCE ( GPL2_OR_LATER );

/*
 * The linker passes in the symbol _max_align, which is the alignment
 * that we must preserve, in bytes.
 *
 */
extern char _max_align[];
#define max_align ( ( unsigned int ) _max_align )

/* Linker symbols */
extern char _textdata[];
extern char _etextdata[];

/* within 1MB of 4GB is too close. 
 * MAX_ADDR is the maximum address we can easily do DMA to.
 *
 * Not sure where this constraint comes from, but kept it from Eric's
 * old code - mcb30
 */
#define MAX_ADDR (0xfff00000UL)

/**
 * Relocate Etherboot
 *
 * @v ix86		x86 register dump from prefix
 * @ret ix86		x86 registers to return to prefix
 *
 * This finds a suitable location for Etherboot near the top of 32-bit
 * address space, and returns the physical address of the new location
 * to the prefix in %edi.
 */
__asmcall void relocate ( struct i386_all_regs *ix86 ) {
	struct memory_map memmap;
	unsigned long start, end, size, padded_size;
	unsigned long new_start, new_end;
	unsigned i;

	/* Get memory map and current location */
	get_memmap ( &memmap );
	start = virt_to_phys ( _textdata );
	end = virt_to_phys ( _etextdata );
	size = ( end - start );
	padded_size = ( size + max_align - 1 );

	DBG ( "Relocate: currently at [%lx,%lx)\n"
	      "...need %lx bytes for %d-byte alignment\n",
	      start, end, padded_size, max_align );

	/* Walk through the memory map and find the highest address
	 * below 4GB that etherboot will fit into.  Ensure etherboot
	 * lies entirely within a range with A20=0.  This means that
	 * even if something screws up the state of the A20 line, the
	 * etherboot code is still visible and we have a chance to
	 * diagnose the problem.
	 */
	new_end = end;
	for ( i = 0 ; i < memmap.count ; i++ ) {
		struct memory_region *region = &memmap.regions[i];
		unsigned long r_start, r_end;

		DBG ( "Considering [%llx,%llx)\n", region->start, region->end);
		
		/* Truncate block to MAX_ADDR.  This will be less than
		 * 4GB, which means that we can get away with using
		 * just 32-bit arithmetic after this stage.
		 */
		if ( region->start > MAX_ADDR ) {
			DBG ( "...starts after MAX_ADDR=%lx\n", MAX_ADDR );
			continue;
		}
		r_start = region->start;
		if ( region->end > MAX_ADDR ) {
			DBG ( "...end truncated to MAX_ADDR=%lx\n", MAX_ADDR );
			r_end = MAX_ADDR;
		} else {
			r_end = region->end;
		}
		
		/* Shrink the range down to use only even megabytes
		 * (i.e. A20=0).
		 */
		if ( ( r_end - 1 ) & 0x100000 ) {
			/* If last byte that might be used (r_end-1)
			 * is in an odd megabyte, round down r_end to
			 * the top of the next even megabyte.
			 *
			 * Make sure that we don't accidentally wrap
			 * r_end below 0.
			 */
			if ( r_end >= 1 ) {
				r_end = ( r_end - 1 ) & ~0xfffff;
				DBG ( "...end truncated to %lx "
				      "(avoid ending in odd megabyte)\n",
				      r_end );
			}
		} else if ( ( r_end - size ) & 0x100000 ) {
			/* If the last byte that might be used
			 * (r_end-1) is in an even megabyte, but the
			 * first byte that might be used (r_end-size)
			 * is an odd megabyte, round down to the top
			 * of the next even megabyte.
			 * 
			 * Make sure that we don't accidentally wrap
			 * r_end below 0.
			 */
			if ( r_end >= 0x100000 ) {
				r_end = ( r_end - 0x100000 ) & ~0xfffff;
				DBG ( "...end truncated to %lx "
				      "(avoid starting in odd megabyte)\n",
				      r_end );
			}
		}

		DBG ( "...usable portion is [%lx,%lx)\n", r_start, r_end );

		/* If we have rounded down r_end below r_ start, skip
		 * this block.
		 */
		if ( r_end < r_start ) {
			DBG ( "...truncated to negative size\n" );
			continue;
		}

		/* Check that there is enough space to fit in Etherboot */
		if ( ( r_end - r_start ) < size ) {
			DBG ( "...too small (need %lx bytes)\n", size );
			continue;
		}

		/* If the start address of the Etherboot we would
		 * place in this block is higher than the end address
		 * of the current highest block, use this block.
		 *
		 * Note that this avoids overlaps with the current
		 * Etherboot, as well as choosing the highest of all
		 * viable blocks.
		 */
		if ( ( r_end - size ) > new_end ) {
			new_end = r_end;
			DBG ( "...new best block found.\n" );
		}
	}

	/* Calculate new location of Etherboot, and align it to the
	 * required alignemnt.
	 */
	new_start = new_end - padded_size;
	new_start += ( start - new_start ) & ( max_align - 1 );
	new_end = new_start + size;

	DBG ( "Relocating from [%lx,%lx) to [%lx,%lx)\n",
	      start, end, new_start, new_end );
	
	/* Let prefix know what to copy */
	ix86->regs.esi = start;
	ix86->regs.edi = new_start;
	ix86->regs.ecx = size;
}