summaryrefslogtreecommitdiffstats
path: root/qemu/roms/openbios/arch/amd64/segment.c
blob: 09763bd144c8851b1405419899d36a0c662f2831 (plain)
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 "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