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:
parent
014eea518a
commit
3d35ac346e
@ -18,7 +18,10 @@ extern struct machine_ops machine_ops;
|
||||
|
||||
void native_machine_crash_shutdown(struct pt_regs *regs);
|
||||
void native_machine_shutdown(void);
|
||||
void machine_real_restart(const unsigned char *code, int length);
|
||||
void machine_real_restart(unsigned int type);
|
||||
/* These must match dispatch_table in reboot_32.S */
|
||||
#define MRR_BIOS 0
|
||||
#define MRR_APM 1
|
||||
|
||||
typedef void (*nmi_shootdown_cb)(int, struct die_args*);
|
||||
void nmi_shootdown_cpus(nmi_shootdown_cb callback);
|
||||
|
@ -59,6 +59,7 @@ obj-$(CONFIG_STACKTRACE) += stacktrace.o
|
||||
obj-y += cpu/
|
||||
obj-y += acpi/
|
||||
obj-y += reboot.o
|
||||
obj-$(CONFIG_X86_32) += reboot_32.o
|
||||
obj-$(CONFIG_MCA) += mca_32.o
|
||||
obj-$(CONFIG_X86_MSR) += msr.o
|
||||
obj-$(CONFIG_X86_CPUID) += cpuid.o
|
||||
|
@ -975,20 +975,10 @@ static void apm_cpu_idle(void)
|
||||
|
||||
static void apm_power_off(void)
|
||||
{
|
||||
unsigned char po_bios_call[] = {
|
||||
0xb8, 0x00, 0x10, /* movw $0x1000,ax */
|
||||
0x8e, 0xd0, /* movw ax,ss */
|
||||
0xbc, 0x00, 0xf0, /* movw $0xf000,sp */
|
||||
0xb8, 0x07, 0x53, /* movw $0x5307,ax */
|
||||
0xbb, 0x01, 0x00, /* movw $0x0001,bx */
|
||||
0xb9, 0x03, 0x00, /* movw $0x0003,cx */
|
||||
0xcd, 0x15 /* int $0x15 */
|
||||
};
|
||||
|
||||
/* Some bioses don't like being called from CPU != 0 */
|
||||
if (apm_info.realmode_power_off) {
|
||||
set_cpus_allowed_ptr(current, cpumask_of(0));
|
||||
machine_real_restart(po_bios_call, sizeof(po_bios_call));
|
||||
machine_real_restart(MRR_APM);
|
||||
} else {
|
||||
(void)set_system_power_state(APM_STATE_OFF);
|
||||
}
|
||||
|
@ -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;
|
||||
|
131
arch/x86/kernel/reboot_32.S
Normal file
131
arch/x86/kernel/reboot_32.S
Normal file
@ -0,0 +1,131 @@
|
||||
#include <linux/linkage.h>
|
||||
#include <linux/init.h>
|
||||
#include <asm/segment.h>
|
||||
#include <asm/page_types.h>
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* This code is called with the restart type (0 = BIOS, 1 = APM) in %eax.
|
||||
*/
|
||||
.section ".x86_trampoline","a"
|
||||
.balign 16
|
||||
.code32
|
||||
ENTRY(machine_real_restart_asm)
|
||||
r_base = .
|
||||
/* Get our own relocated address */
|
||||
call 1f
|
||||
1: popl %ebx
|
||||
subl $1b, %ebx
|
||||
|
||||
/* Patch post-real-mode segment jump */
|
||||
movw dispatch_table(%ebx,%ecx,2),%cx
|
||||
movw %cx, 101f(%ebx)
|
||||
movw %ax, 102f(%ebx)
|
||||
|
||||
/* Set up the IDT for real mode. */
|
||||
lidtl machine_real_restart_idt(%ebx)
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
lgdtl machine_real_restart_gdt(%ebx)
|
||||
|
||||
/*
|
||||
* Load the data segment registers with 16-bit compatible values
|
||||
*/
|
||||
movl $16, %ecx
|
||||
movl %ecx, %ds
|
||||
movl %ecx, %es
|
||||
movl %ecx, %fs
|
||||
movl %ecx, %gs
|
||||
movl %ecx, %ss
|
||||
ljmpl $8, $1f - r_base
|
||||
|
||||
/*
|
||||
* 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. This is not the
|
||||
* actual BIOS entry point, anyway (that is at 0xfffffff0).
|
||||
*
|
||||
* Most of this work is probably excessive, but it is what is tested.
|
||||
*/
|
||||
.code16
|
||||
1:
|
||||
xorl %ecx, %ecx
|
||||
movl %cr0, %eax
|
||||
andl $0x00000011, %eax
|
||||
orl $0x60000000, %eax
|
||||
movl %eax, %cr0
|
||||
movl %ecx, %cr3
|
||||
movl %cr0, %edx
|
||||
andl $0x60000000, %edx /* If no cache bits -> no wbinvd */
|
||||
jz 2f
|
||||
wbinvd
|
||||
2:
|
||||
andb $0x10, %al
|
||||
movl %eax, %cr0
|
||||
.byte 0xea /* ljmpw */
|
||||
101: .word 0 /* Offset */
|
||||
102: .word 0 /* Segment */
|
||||
|
||||
bios:
|
||||
ljmpw $0xf000, $0xfff0
|
||||
|
||||
apm:
|
||||
movw $0x1000, %ax
|
||||
movw %ax, %ss
|
||||
movw $0xf000, %sp
|
||||
movw $0x5307, %ax
|
||||
movw $0x0001, %bx
|
||||
movw $0x0003, %cx
|
||||
int $0x15
|
||||
|
||||
END(machine_real_restart_asm)
|
||||
|
||||
.balign 16
|
||||
/* These must match <asm/reboot.h */
|
||||
dispatch_table:
|
||||
.word bios - r_base
|
||||
.word apm - r_base
|
||||
END(dispatch_table)
|
||||
|
||||
.balign 16
|
||||
machine_real_restart_idt:
|
||||
.word 0xffff /* Length - real mode default value */
|
||||
.long 0 /* Base - real mode default value */
|
||||
END(machine_real_restart_idt)
|
||||
|
||||
.balign 16
|
||||
ENTRY(machine_real_restart_gdt)
|
||||
.quad 0 /* Self-pointer, filled in by PM code */
|
||||
.quad 0 /* 16-bit code segment, filled in by PM code */
|
||||
/*
|
||||
* 16-bit data segment with the selector value 16 = 0x10 and
|
||||
* base value 0x100; since this is consistent with real mode
|
||||
* semantics we don't have to reload the segments once CR0.PE = 0.
|
||||
*/
|
||||
.quad GDT_ENTRY(0x0093, 0x100, 0xffff)
|
||||
END(machine_real_restart_gdt)
|
Loading…
Reference in New Issue
Block a user