x86, reboot: Move the real-mode reboot code to an assembly file
Move the real-mode reboot code out to an assembly file (reboot_32.S) which is allocated using the common lowmem trampoline allocator. Signed-off-by: H. Peter Anvin <hpa@linux.intel.com> LKML-Reference: <4D5DFBE4.7090104@intel.com> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Rafael J. Wysocki <rjw@sisk.pl> Cc: Matthieu Castet <castet.matthieu@free.fr>
This commit is contained in:
@@ -295,68 +295,16 @@ static int __init reboot_init(void)
|
||||
}
|
||||
core_initcall(reboot_init);
|
||||
|
||||
/* The following code and data reboots the machine by switching to real
|
||||
mode and jumping to the BIOS reset entry point, as if the CPU has
|
||||
really been reset. The previous version asked the keyboard
|
||||
controller to pulse the CPU reset line, which is more thorough, but
|
||||
doesn't work with at least one type of 486 motherboard. It is easy
|
||||
to stop this code working; hence the copious comments. */
|
||||
static const unsigned long long
|
||||
real_mode_gdt_entries [3] =
|
||||
extern const unsigned char machine_real_restart_asm[];
|
||||
extern const u64 machine_real_restart_gdt[3];
|
||||
|
||||
void machine_real_restart(unsigned int type)
|
||||
{
|
||||
0x0000000000000000ULL, /* Null descriptor */
|
||||
0x00009b000000ffffULL, /* 16-bit real-mode 64k code at 0x00000000 */
|
||||
0x000093000100ffffULL /* 16-bit real-mode 64k data at 0x00000100 */
|
||||
};
|
||||
void *restart_va;
|
||||
unsigned long restart_pa;
|
||||
void (*restart_lowmem)(unsigned int);
|
||||
u64 *lowmem_gdt;
|
||||
|
||||
static const struct desc_ptr
|
||||
real_mode_gdt = { sizeof (real_mode_gdt_entries) - 1, (long)real_mode_gdt_entries },
|
||||
real_mode_idt = { 0x3ff, 0 };
|
||||
|
||||
/* This is 16-bit protected mode code to disable paging and the cache,
|
||||
switch to real mode and jump to the BIOS reset code.
|
||||
|
||||
The instruction that switches to real mode by writing to CR0 must be
|
||||
followed immediately by a far jump instruction, which set CS to a
|
||||
valid value for real mode, and flushes the prefetch queue to avoid
|
||||
running instructions that have already been decoded in protected
|
||||
mode.
|
||||
|
||||
Clears all the flags except ET, especially PG (paging), PE
|
||||
(protected-mode enable) and TS (task switch for coprocessor state
|
||||
save). Flushes the TLB after paging has been disabled. Sets CD and
|
||||
NW, to disable the cache on a 486, and invalidates the cache. This
|
||||
is more like the state of a 486 after reset. I don't know if
|
||||
something else should be done for other chips.
|
||||
|
||||
More could be done here to set up the registers as if a CPU reset had
|
||||
occurred; hopefully real BIOSs don't assume much. */
|
||||
static const unsigned char real_mode_switch [] =
|
||||
{
|
||||
0x66, 0x0f, 0x20, 0xc0, /* movl %cr0,%eax */
|
||||
0x66, 0x83, 0xe0, 0x11, /* andl $0x00000011,%eax */
|
||||
0x66, 0x0d, 0x00, 0x00, 0x00, 0x60, /* orl $0x60000000,%eax */
|
||||
0x66, 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */
|
||||
0x66, 0x0f, 0x22, 0xd8, /* movl %eax,%cr3 */
|
||||
0x66, 0x0f, 0x20, 0xc3, /* movl %cr0,%ebx */
|
||||
0x66, 0x81, 0xe3, 0x00, 0x00, 0x00, 0x60, /* andl $0x60000000,%ebx */
|
||||
0x74, 0x02, /* jz f */
|
||||
0x0f, 0x09, /* wbinvd */
|
||||
0x24, 0x10, /* f: andb $0x10,al */
|
||||
0x66, 0x0f, 0x22, 0xc0 /* movl %eax,%cr0 */
|
||||
};
|
||||
static const unsigned char jump_to_bios [] =
|
||||
{
|
||||
0xea, 0x00, 0x00, 0xff, 0xff /* ljmp $0xffff,$0x0000 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Switch to real mode and then execute the code
|
||||
* specified by the code and length parameters.
|
||||
* We assume that length will aways be less that 100!
|
||||
*/
|
||||
void machine_real_restart(const unsigned char *code, int length)
|
||||
{
|
||||
local_irq_disable();
|
||||
|
||||
/* Write zero to CMOS register number 0x0f, which the BIOS POST
|
||||
@@ -384,41 +332,23 @@ void machine_real_restart(const unsigned char *code, int length)
|
||||
too. */
|
||||
*((unsigned short *)0x472) = reboot_mode;
|
||||
|
||||
/* For the switch to real mode, copy some code to low memory. It has
|
||||
to be in the first 64k because it is running in 16-bit mode, and it
|
||||
has to have the same physical and virtual address, because it turns
|
||||
off paging. Copy it near the end of the first page, out of the way
|
||||
of BIOS variables. */
|
||||
memcpy((void *)(0x1000 - sizeof(real_mode_switch) - 100),
|
||||
real_mode_switch, sizeof (real_mode_switch));
|
||||
memcpy((void *)(0x1000 - 100), code, length);
|
||||
/* Patch the GDT in the low memory trampoline */
|
||||
lowmem_gdt = TRAMPOLINE_SYM(machine_real_restart_gdt);
|
||||
|
||||
/* Set up the IDT for real mode. */
|
||||
load_idt(&real_mode_idt);
|
||||
restart_va = TRAMPOLINE_SYM(machine_real_restart_asm);
|
||||
restart_pa = virt_to_phys(restart_va);
|
||||
restart_lowmem = (void (*)(unsigned int))restart_pa;
|
||||
|
||||
/* Set up a GDT from which we can load segment descriptors for real
|
||||
mode. The GDT is not used in real mode; it is just needed here to
|
||||
prepare the descriptors. */
|
||||
load_gdt(&real_mode_gdt);
|
||||
/* GDT[0]: GDT self-pointer */
|
||||
lowmem_gdt[0] =
|
||||
(u64)(sizeof(machine_real_restart_gdt) - 1) +
|
||||
((u64)virt_to_phys(lowmem_gdt) << 16);
|
||||
/* GDT[1]: 64K real mode code segment */
|
||||
lowmem_gdt[1] =
|
||||
GDT_ENTRY(0x009b, restart_pa, 0xffff);
|
||||
|
||||
/* Load the data segment registers, and thus the descriptors ready for
|
||||
real mode. The base address of each segment is 0x100, 16 times the
|
||||
selector value being loaded here. This is so that the segment
|
||||
registers don't have to be reloaded after switching to real mode:
|
||||
the values are consistent for real mode operation already. */
|
||||
__asm__ __volatile__ ("movl $0x0010,%%eax\n"
|
||||
"\tmovl %%eax,%%ds\n"
|
||||
"\tmovl %%eax,%%es\n"
|
||||
"\tmovl %%eax,%%fs\n"
|
||||
"\tmovl %%eax,%%gs\n"
|
||||
"\tmovl %%eax,%%ss" : : : "eax");
|
||||
|
||||
/* Jump to the 16-bit code that we copied earlier. It disables paging
|
||||
and the cache, switches to real mode, and jumps to the BIOS reset
|
||||
entry point. */
|
||||
__asm__ __volatile__ ("ljmp $0x0008,%0"
|
||||
:
|
||||
: "i" ((void *)(0x1000 - sizeof (real_mode_switch) - 100)));
|
||||
/* Jump to the identity-mapped low memory code */
|
||||
restart_lowmem(type);
|
||||
}
|
||||
#ifdef CONFIG_APM_MODULE
|
||||
EXPORT_SYMBOL(machine_real_restart);
|
||||
@@ -573,7 +503,7 @@ static void native_machine_emergency_restart(void)
|
||||
|
||||
#ifdef CONFIG_X86_32
|
||||
case BOOT_BIOS:
|
||||
machine_real_restart(jump_to_bios, sizeof(jump_to_bios));
|
||||
machine_real_restart(MRR_BIOS);
|
||||
|
||||
reboot_type = BOOT_KBD;
|
||||
break;
|
||||
|
Reference in New Issue
Block a user