ce29a1f8bd
Apparently XEN does not keep the contents of the 48-bit gdt_48 data structure that is passed to lgdt in the XEN machine state. Instead it appears to save the _address_ of the 48-bit descriptor somewhere. Unfortunately this data happens to reside on the stack and is probably no longer availiable at the time of the actual protected mode jump. This is Xen bug but given that there is a one-line patch to work around this problem, the linux kernel should probably do this. My fix is to make the gdt_48 description in setup_gdt static (in setup_idt this is already the case). This allows the kernel to boot under Xen HVM again. Signed-off-by: Christian Ehrhardt <lk@c--e.de> Signed-off-by: H. Peter Anvin <hpa@zytor.com>
174 lines
4.1 KiB
C
174 lines
4.1 KiB
C
/* -*- linux-c -*- ------------------------------------------------------- *
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
* Copyright 2007 rPath, Inc. - All Rights Reserved
|
|
*
|
|
* This file is part of the Linux kernel, and is made available under
|
|
* the terms of the GNU General Public License version 2.
|
|
*
|
|
* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* arch/i386/boot/pm.c
|
|
*
|
|
* Prepare the machine for transition to protected mode.
|
|
*/
|
|
|
|
#include "boot.h"
|
|
#include <asm/segment.h>
|
|
|
|
/*
|
|
* Invoke the realmode switch hook if present; otherwise
|
|
* disable all interrupts.
|
|
*/
|
|
static void realmode_switch_hook(void)
|
|
{
|
|
if (boot_params.hdr.realmode_swtch) {
|
|
asm volatile("lcallw *%0"
|
|
: : "m" (boot_params.hdr.realmode_swtch)
|
|
: "eax", "ebx", "ecx", "edx");
|
|
} else {
|
|
asm volatile("cli");
|
|
outb(0x80, 0x70); /* Disable NMI */
|
|
io_delay();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A zImage kernel is loaded at 0x10000 but wants to run at 0x1000.
|
|
* A bzImage kernel is loaded and runs at 0x100000.
|
|
*/
|
|
static void move_kernel_around(void)
|
|
{
|
|
/* Note: rely on the compile-time option here rather than
|
|
the LOADED_HIGH flag. The Qemu kernel loader unconditionally
|
|
sets the loadflags to zero. */
|
|
#ifndef __BIG_KERNEL__
|
|
u16 dst_seg, src_seg;
|
|
u32 syssize;
|
|
|
|
dst_seg = 0x1000 >> 4;
|
|
src_seg = 0x10000 >> 4;
|
|
syssize = boot_params.hdr.syssize; /* Size in 16-byte paragraphs */
|
|
|
|
while (syssize) {
|
|
int paras = (syssize >= 0x1000) ? 0x1000 : syssize;
|
|
int dwords = paras << 2;
|
|
|
|
asm volatile("pushw %%es ; "
|
|
"pushw %%ds ; "
|
|
"movw %1,%%es ; "
|
|
"movw %2,%%ds ; "
|
|
"xorw %%di,%%di ; "
|
|
"xorw %%si,%%si ; "
|
|
"rep;movsl ; "
|
|
"popw %%ds ; "
|
|
"popw %%es"
|
|
: "+c" (dwords)
|
|
: "r" (dst_seg), "r" (src_seg)
|
|
: "esi", "edi");
|
|
|
|
syssize -= paras;
|
|
dst_seg += paras;
|
|
src_seg += paras;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Disable all interrupts at the legacy PIC.
|
|
*/
|
|
static void mask_all_interrupts(void)
|
|
{
|
|
outb(0xff, 0xa1); /* Mask all interrupts on the secondary PIC */
|
|
io_delay();
|
|
outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */
|
|
io_delay();
|
|
}
|
|
|
|
/*
|
|
* Reset IGNNE# if asserted in the FPU.
|
|
*/
|
|
static void reset_coprocessor(void)
|
|
{
|
|
outb(0, 0xf0);
|
|
io_delay();
|
|
outb(0, 0xf1);
|
|
io_delay();
|
|
}
|
|
|
|
/*
|
|
* Set up the GDT
|
|
*/
|
|
#define GDT_ENTRY(flags,base,limit) \
|
|
(((u64)(base & 0xff000000) << 32) | \
|
|
((u64)flags << 40) | \
|
|
((u64)(limit & 0x00ff0000) << 32) | \
|
|
((u64)(base & 0x00ffff00) << 16) | \
|
|
((u64)(limit & 0x0000ffff)))
|
|
|
|
struct gdt_ptr {
|
|
u16 len;
|
|
u32 ptr;
|
|
} __attribute__((packed));
|
|
|
|
static void setup_gdt(void)
|
|
{
|
|
/* There are machines which are known to not boot with the GDT
|
|
being 8-byte unaligned. Intel recommends 16 byte alignment. */
|
|
static const u64 boot_gdt[] __attribute__((aligned(16))) = {
|
|
/* CS: code, read/execute, 4 GB, base 0 */
|
|
[GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
|
|
/* DS: data, read/write, 4 GB, base 0 */
|
|
[GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
|
|
};
|
|
/* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
|
|
of the gdt_ptr contents. Thus, make it static so it will
|
|
stay in memory, at least long enough that we switch to the
|
|
proper kernel GDT. */
|
|
static struct gdt_ptr gdt;
|
|
|
|
gdt.len = sizeof(boot_gdt)-1;
|
|
gdt.ptr = (u32)&boot_gdt + (ds() << 4);
|
|
|
|
asm volatile("lgdtl %0" : : "m" (gdt));
|
|
}
|
|
|
|
/*
|
|
* Set up the IDT
|
|
*/
|
|
static void setup_idt(void)
|
|
{
|
|
static const struct gdt_ptr null_idt = {0, 0};
|
|
asm volatile("lidtl %0" : : "m" (null_idt));
|
|
}
|
|
|
|
/*
|
|
* Actual invocation sequence
|
|
*/
|
|
void go_to_protected_mode(void)
|
|
{
|
|
/* Hook before leaving real mode, also disables interrupts */
|
|
realmode_switch_hook();
|
|
|
|
/* Move the kernel/setup to their final resting places */
|
|
move_kernel_around();
|
|
|
|
/* Enable the A20 gate */
|
|
if (enable_a20()) {
|
|
puts("A20 gate not responding, unable to boot...\n");
|
|
die();
|
|
}
|
|
|
|
/* Reset coprocessor (IGNNE#) */
|
|
reset_coprocessor();
|
|
|
|
/* Mask all interrupts in the PIC */
|
|
mask_all_interrupts();
|
|
|
|
/* Actual transition to protected mode... */
|
|
setup_idt();
|
|
setup_gdt();
|
|
protected_mode_jump(boot_params.hdr.code32_start,
|
|
(u32)&boot_params + (ds() << 4));
|
|
}
|