/* Segmentation of the AMD64 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}, }; extern char _start[], _end[]; 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