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
|
/* Segmentation of the AMD64 architecture.
*
* 2003-07 by SONE Takeshi
*/
#include "openbios/config.h"
#include "openbios/kernel.h"
#include "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
|