diff --git a/src/arch/i386/core/relocate.c b/src/arch/i386/core/relocate.c index 436432e5..782062a8 100644 --- a/src/arch/i386/core/relocate.c +++ b/src/arch/i386/core/relocate.c @@ -1,7 +1,7 @@ -#ifndef NORELOCATE - -#include "etherboot.h" +#include "virtaddr.h" #include "memsizes.h" +#include "osdep.h" +#include "etherboot.h" /* by Eric Biederman */ @@ -16,10 +16,37 @@ * */ -void relocate(void) -{ +/* + * relocate() must be called without any hardware resources pointing + * at the current copy of Etherboot. The easiest way to achieve this + * is to call relocate() from within arch_initialise(), before the NIC + * gets touched in any way. + * + */ + +/* + * 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 _text[]; +extern char _end[]; + +#undef DBG +#ifdef DEBUG_RELOCATE +#define DBG(...) printf ( __VA_ARGS__ ) +#else +#define DBG(...) +#endif + +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 @@ -27,78 +54,133 @@ void relocate(void) * 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. + /* First find the size of etherboot, including enough space to + * pad it to the required alignment */ - if (eaddr > MAX_ADDR) { + size = _end - _text + max_align - 1; + + /* Current end address of Etherboot. If the current etherboot + * is beyond MAX_ADDR pretend it is at the lowest possible + * address. + */ + eaddr = virt_to_phys(_end); + if ( eaddr > MAX_ADDR ) { eaddr = 0; } - for(i = 0; i < meminfo.map_count; i++) { + DBG ( "Relocate: currently at [%x,%x)\n" + "...need %x bytes for %d-byte alignment\n", + virt_to_phys ( _text ), eaddr, size, max_align ); + + for ( i = 0; i < meminfo.map_count; i++ ) { unsigned long r_start, r_end; + + DBG ( "Considering [%x%x,%x%x)\n", + ( unsigned long ) ( meminfo.map[i].addr >> 32 ), + ( unsigned long ) meminfo.map[i].addr, + ( unsigned long ) + ( ( meminfo.map[i].addr + meminfo.map[i].size ) >> 32 ), + ( unsigned long ) + ( meminfo.map[i].addr + meminfo.map[i].size ) ); + + /* Check block is usable memory */ if (meminfo.map[i].type != E820_RAM) { + DBG ( "...not RAM\n" ); continue; } - if (meminfo.map[i].addr > MAX_ADDR) { - continue; - } - if (meminfo.map[i].size > MAX_ADDR) { + + /* 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 ( meminfo.map[i].addr > MAX_ADDR ) { + DBG ( "...starts after MAX_ADDR=%x\n", 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) { + if ( meminfo.map[i].addr + meminfo.map[i].size > MAX_ADDR ) { r_end = MAX_ADDR; + DBG ( "...end truncated to MAX_ADDR=%x\n", MAX_ADDR ); + } else { + r_end = meminfo.map[i].addr + meminfo.map[i].size; } - 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. + 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. */ - r_end = r_end & ~0xfffff; + r_end = ( r_end - 1 ) & ~0xfffff; + DBG ( "...end truncated to %x " + "(avoid ending in odd megabyte)\n", + r_end ); } 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 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 %x " + "(avoid starting in odd megabyte)\n", + r_end ); + } } + + DBG ( "...usable portion is [%x,%x)\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; } - if (eaddr < r_end - size) { - addr = r_end - size; + + /* Check that there is enough space to fit in Etherboot */ + if ( r_end - r_start < size ) { + DBG ( "...too small (need %x 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 > eaddr ) { eaddr = r_end; + DBG ( "...new best block found.\n" ); } } - 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 ( addr ) */ + + DBG ( "New location will be in [%x,%x)\n", eaddr - size, eaddr ); + + /* Calculate new location of Etherboot, and align it to the + * required alignemnt. + */ + addr = eaddr - size; + addr += ( virt_to_phys ( _text ) - addr ) & ( max_align - 1 ); + DBG ( "After alignment, new location is [%x,%x)\n", + addr, addr + _end - _text ); + + if ( addr != virt_to_phys ( _text ) ) { + DBG ( "Relocating _text from: [%lx,%lx) to [%lx,%lx)\n", + virt_to_phys ( _text ), virt_to_phys ( _end ), + addr, addr + _end - _text ); + + relocate_to ( addr ); } } - -#endif /* NORELOCATE */