diff options
| author | Michael Brown | 2005-03-08 19:53:11 +0100 |
|---|---|---|
| committer | Michael Brown | 2005-03-08 19:53:11 +0100 |
| commit | 3d6123e69ab879c72ff489afc5bf93ef0b7a94ce (patch) | |
| tree | 9f3277569153a550fa8d81ebd61bd88f266eb8da /src/core/relocate.c | |
| download | ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.gz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.xz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.zip | |
Initial revision
Diffstat (limited to 'src/core/relocate.c')
| -rw-r--r-- | src/core/relocate.c | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/src/core/relocate.c b/src/core/relocate.c new file mode 100644 index 000000000..d20846a88 --- /dev/null +++ b/src/core/relocate.c @@ -0,0 +1,103 @@ +#ifndef NORELOCATE + +#include "etherboot.h" + +/* by Eric Biederman */ + +/* On some platforms etherboot is compiled as a shared library, and we use + * the ELF pic support to make it relocateable. This works very nicely + * for code, but since no one has implemented PIC data yet pointer + * values in variables are a a problem. Global variables are a + * pain but the return addresses on the stack are the worst. On these + * platforms relocate_to will restart etherboot, to ensure the stack + * is reinitialize and hopefully get the global variables + * appropriately reinitialized as well. + * + */ + +void relocate(void) +{ + unsigned long addr, eaddr, size; + unsigned i; + /* 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. + */ + /* First find the size of etherboot */ + addr = virt_to_phys(_text); + eaddr = virt_to_phys(_end); + size = (eaddr - addr + 0xf) & ~0xf; + + /* If the current etherboot is beyond MAX_ADDR pretend it is + * at the lowest possible address. + */ + if (eaddr > MAX_ADDR) { + eaddr = 0; + } + + for(i = 0; i < meminfo.map_count; i++) { + unsigned long r_start, r_end; + if (meminfo.map[i].type != E820_RAM) { + continue; + } + if (meminfo.map[i].addr > MAX_ADDR) { + continue; + } + if (meminfo.map[i].size > MAX_ADDR) { + continue; + } + r_start = meminfo.map[i].addr; + r_end = r_start + meminfo.map[i].size; + /* Make the addresses 16 byte (128 bit) aligned */ + r_start = (r_start + 15) & ~15; + r_end = r_end & ~15; + if (r_end < r_start) { + r_end = MAX_ADDR; + } + if (r_end < size) { + /* Avoid overflow weirdness when r_end - size < 0 */ + continue; + } + /* Shrink the range down to use only even megabytes + * (i.e. A20=0). + */ + if ( r_end & 0x100000 ) { + /* If r_end is in an odd megabyte, round down + * r_end to the top of the next even megabyte. + */ + r_end = r_end & ~0xfffff; + } else if ( ( r_end - size ) & 0x100000 ) { + /* If r_end is in an even megabyte, but the + * start of Etherboot would be in an odd + * megabyte, round down to the top of the next + * even megabyte. + */ + r_end = ( r_end - 0x100000 ) & ~0xfffff; + } + /* If we have rounded down r_end below r_ start, skip + * this block. + */ + if ( r_end < r_start ) { + continue; + } + if (eaddr < r_end - size) { + addr = r_end - size; + eaddr = r_end; + } + } + if (addr != virt_to_phys(_text)) { + unsigned long old_addr = virt_to_phys(_text); + printf("Relocating _text from: [%lx,%lx) to [%lx,%lx)\n", + old_addr, virt_to_phys(_end), + addr, eaddr); + arch_relocate_to(addr); + cleanup(); + relocate_to(addr); + arch_relocated_from(old_addr); + } +} + +#endif /* NORELOCATE */ |
