diff options
Diffstat (limited to 'qemu/roms/openbios/arch/x86/segment.c')
-rw-r--r-- | qemu/roms/openbios/arch/x86/segment.c | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/qemu/roms/openbios/arch/x86/segment.c b/qemu/roms/openbios/arch/x86/segment.c new file mode 100644 index 000000000..5146cd302 --- /dev/null +++ b/qemu/roms/openbios/arch/x86/segment.c @@ -0,0 +1,133 @@ +/* Segmentation of the i386 architecture. + * + * 2003-07 by SONE Takeshi + */ + +#include "config.h" +#include "kernel/kernel.h" +#include "libopenbios/sys_info.h" +#include "relocate.h" +#include "segment.h" + +#define printf printk +#ifdef CONFIG_DEBUG_BOOT +#define debug printk +#else +#define debug(x...) +#endif + +/* i386 lgdt argument */ +struct gdtarg { + unsigned short limit; + unsigned int base; +} __attribute__((packed)); + +/* How far the virtual address (used in C) is different from physical + * address. Since we start in flat mode, the initial value is zero. */ +unsigned long virt_offset = 0; + +/* GDT, the global descriptor table */ +struct segment_desc gdt[NUM_SEG] = { + /* 0x00: null segment */ + {0, 0, 0, 0, 0, 0}, + /* 0x08: flat code segment */ + {0xffff, 0, 0, 0x9f, 0xcf, 0}, + /* 0x10: flat data segment */ + {0xffff, 0, 0, 0x93, 0xcf, 0}, + /* 0x18: code segment for relocated execution */ + {0xffff, 0, 0, 0x9f, 0xcf, 0}, + /* 0x20: data segment for relocated execution */ + {0xffff, 0, 0, 0x93, 0xcf, 0}, +}; + + +void relocate(struct sys_info *info) +{ + int i; + unsigned long prog_addr; + unsigned long prog_size; + unsigned long addr, new_base; + unsigned long long segsize; + unsigned long new_offset; + unsigned d0, d1, d2; + struct gdtarg gdtarg; +#define ALIGNMENT 16 + + prog_addr = virt_to_phys(&_start); + prog_size = virt_to_phys(&_end) - virt_to_phys(&_start); + debug("Current location: %#lx-%#lx\n", prog_addr, prog_addr+prog_size-1); + + new_base = 0; + for (i = 0; i < info->n_memranges; i++) { + if (info->memrange[i].base >= 1ULL<<32) + continue; + segsize = info->memrange[i].size; + if (info->memrange[i].base + segsize > 1ULL<<32) + segsize = (1ULL<<32) - info->memrange[i].base; + if (segsize < prog_size+ALIGNMENT) + continue; + addr = info->memrange[i].base + segsize - prog_size; + addr &= ~(ALIGNMENT-1); + if (addr >= prog_addr && addr < prog_addr + prog_size) + continue; + if (prog_addr >= addr && prog_addr < addr + prog_size) + continue; + if (addr > new_base) + new_base = addr; + } + if (new_base == 0) { + printf("Can't find address to relocate\n"); + return; + } + + debug("Relocating to %#lx-%#lx... ", + new_base, new_base + prog_size - 1); + + /* New virtual address offset */ + new_offset = new_base - (unsigned long) &_start; + + /* Tweak the GDT */ + gdt[RELOC_CODE].base_0 = (unsigned short) new_offset; + gdt[RELOC_CODE].base_16 = (unsigned char) (new_offset>>16); + gdt[RELOC_CODE].base_24 = (unsigned char) (new_offset>>24); + gdt[RELOC_DATA].base_0 = (unsigned short) new_offset; + gdt[RELOC_DATA].base_16 = (unsigned char) (new_offset>>16); + gdt[RELOC_DATA].base_24 = (unsigned char) (new_offset>>24); + + /* Load new GDT and reload segments */ + gdtarg.base = new_offset + (unsigned long) gdt; + gdtarg.limit = GDT_LIMIT; + __asm__ __volatile__ ( + "rep; movsb\n\t" /* copy everything */ + "lgdt %3\n\t" + "ljmp %4, $1f\n1:\t" + "movw %5, %%ds\n\t" + "movw %5, %%es\n\t" + "movw %5, %%fs\n\t" + "movw %5, %%gs\n\t" + "movw %5, %%ss\n" + : "=&S" (d0), "=&D" (d1), "=&c" (d2) + : "m" (gdtarg), "n" (RELOC_CS), "q" ((unsigned short) RELOC_DS), + "0" (&_start), "1" (new_base), "2" (prog_size)); + + virt_offset = new_offset; + debug("ok\n"); +} + +#if 0 +/* Copy GDT to new location and reload it */ +void move_gdt(unsigned long newgdt) +{ + struct gdtarg gdtarg; + + debug("Moving GDT to %#lx...", newgdt); + memcpy(phys_to_virt(newgdt), gdt, sizeof gdt); + gdtarg.base = newgdt; + gdtarg.limit = GDT_LIMIT; + debug("reloading GDT..."); + __asm__ __volatile__ ("lgdt %0\n\t" : : "m" (gdtarg)); + debug("reloading CS for fun..."); + __asm__ __volatile__ ("ljmp %0, $1f\n1:" : : "n" (RELOC_CS)); + debug("ok\n"); +} +#endif |