diff options
author | H. Peter Anvin <hpa@linux.intel.com> | 2011-02-14 21:36:03 -0500 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-02-18 00:05:34 -0500 |
commit | 3d35ac346e981162eeba391e496faceed4753e7b (patch) | |
tree | 1f902bd0b8d62aa65b9d216483cbecc9a712a72b /arch/x86/kernel/reboot.c | |
parent | 014eea518af3d141e276664cf40ef3da899eba35 (diff) |
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>
Diffstat (limited to 'arch/x86/kernel/reboot.c')
-rw-r--r-- | arch/x86/kernel/reboot.c | 120 |
1 files changed, 25 insertions, 95 deletions
diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c index fc7aae1e2bc7..10c6619c0543 100644 --- a/arch/x86/kernel/reboot.c +++ b/arch/x86/kernel/reboot.c | |||
@@ -295,68 +295,16 @@ static int __init reboot_init(void) | |||
295 | } | 295 | } |
296 | core_initcall(reboot_init); | 296 | core_initcall(reboot_init); |
297 | 297 | ||
298 | /* The following code and data reboots the machine by switching to real | 298 | extern const unsigned char machine_real_restart_asm[]; |
299 | mode and jumping to the BIOS reset entry point, as if the CPU has | 299 | extern const u64 machine_real_restart_gdt[3]; |
300 | really been reset. The previous version asked the keyboard | ||
301 | controller to pulse the CPU reset line, which is more thorough, but | ||
302 | doesn't work with at least one type of 486 motherboard. It is easy | ||
303 | to stop this code working; hence the copious comments. */ | ||
304 | static const unsigned long long | ||
305 | real_mode_gdt_entries [3] = | ||
306 | { | ||
307 | 0x0000000000000000ULL, /* Null descriptor */ | ||
308 | 0x00009b000000ffffULL, /* 16-bit real-mode 64k code at 0x00000000 */ | ||
309 | 0x000093000100ffffULL /* 16-bit real-mode 64k data at 0x00000100 */ | ||
310 | }; | ||
311 | 300 | ||
312 | static const struct desc_ptr | 301 | void machine_real_restart(unsigned int type) |
313 | real_mode_gdt = { sizeof (real_mode_gdt_entries) - 1, (long)real_mode_gdt_entries }, | ||
314 | real_mode_idt = { 0x3ff, 0 }; | ||
315 | |||
316 | /* This is 16-bit protected mode code to disable paging and the cache, | ||
317 | switch to real mode and jump to the BIOS reset code. | ||
318 | |||
319 | The instruction that switches to real mode by writing to CR0 must be | ||
320 | followed immediately by a far jump instruction, which set CS to a | ||
321 | valid value for real mode, and flushes the prefetch queue to avoid | ||
322 | running instructions that have already been decoded in protected | ||
323 | mode. | ||
324 | |||
325 | Clears all the flags except ET, especially PG (paging), PE | ||
326 | (protected-mode enable) and TS (task switch for coprocessor state | ||
327 | save). Flushes the TLB after paging has been disabled. Sets CD and | ||
328 | NW, to disable the cache on a 486, and invalidates the cache. This | ||
329 | is more like the state of a 486 after reset. I don't know if | ||
330 | something else should be done for other chips. | ||
331 | |||
332 | More could be done here to set up the registers as if a CPU reset had | ||
333 | occurred; hopefully real BIOSs don't assume much. */ | ||
334 | static const unsigned char real_mode_switch [] = | ||
335 | { | ||
336 | 0x66, 0x0f, 0x20, 0xc0, /* movl %cr0,%eax */ | ||
337 | 0x66, 0x83, 0xe0, 0x11, /* andl $0x00000011,%eax */ | ||
338 | 0x66, 0x0d, 0x00, 0x00, 0x00, 0x60, /* orl $0x60000000,%eax */ | ||
339 | 0x66, 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */ | ||
340 | 0x66, 0x0f, 0x22, 0xd8, /* movl %eax,%cr3 */ | ||
341 | 0x66, 0x0f, 0x20, 0xc3, /* movl %cr0,%ebx */ | ||
342 | 0x66, 0x81, 0xe3, 0x00, 0x00, 0x00, 0x60, /* andl $0x60000000,%ebx */ | ||
343 | 0x74, 0x02, /* jz f */ | ||
344 | 0x0f, 0x09, /* wbinvd */ | ||
345 | 0x24, 0x10, /* f: andb $0x10,al */ | ||
346 | 0x66, 0x0f, 0x22, 0xc0 /* movl %eax,%cr0 */ | ||
347 | }; | ||
348 | static const unsigned char jump_to_bios [] = | ||
349 | { | 302 | { |
350 | 0xea, 0x00, 0x00, 0xff, 0xff /* ljmp $0xffff,$0x0000 */ | 303 | void *restart_va; |
351 | }; | 304 | unsigned long restart_pa; |
305 | void (*restart_lowmem)(unsigned int); | ||
306 | u64 *lowmem_gdt; | ||
352 | 307 | ||
353 | /* | ||
354 | * Switch to real mode and then execute the code | ||
355 | * specified by the code and length parameters. | ||
356 | * We assume that length will aways be less that 100! | ||
357 | */ | ||
358 | void machine_real_restart(const unsigned char *code, int length) | ||
359 | { | ||
360 | local_irq_disable(); | 308 | local_irq_disable(); |
361 | 309 | ||
362 | /* Write zero to CMOS register number 0x0f, which the BIOS POST | 310 | /* 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) | |||
384 | too. */ | 332 | too. */ |
385 | *((unsigned short *)0x472) = reboot_mode; | 333 | *((unsigned short *)0x472) = reboot_mode; |
386 | 334 | ||
387 | /* For the switch to real mode, copy some code to low memory. It has | 335 | /* Patch the GDT in the low memory trampoline */ |
388 | to be in the first 64k because it is running in 16-bit mode, and it | 336 | lowmem_gdt = TRAMPOLINE_SYM(machine_real_restart_gdt); |
389 | has to have the same physical and virtual address, because it turns | 337 | |
390 | off paging. Copy it near the end of the first page, out of the way | 338 | restart_va = TRAMPOLINE_SYM(machine_real_restart_asm); |
391 | of BIOS variables. */ | 339 | restart_pa = virt_to_phys(restart_va); |
392 | memcpy((void *)(0x1000 - sizeof(real_mode_switch) - 100), | 340 | restart_lowmem = (void (*)(unsigned int))restart_pa; |
393 | real_mode_switch, sizeof (real_mode_switch)); | 341 | |
394 | memcpy((void *)(0x1000 - 100), code, length); | 342 | /* GDT[0]: GDT self-pointer */ |
395 | 343 | lowmem_gdt[0] = | |
396 | /* Set up the IDT for real mode. */ | 344 | (u64)(sizeof(machine_real_restart_gdt) - 1) + |
397 | load_idt(&real_mode_idt); | 345 | ((u64)virt_to_phys(lowmem_gdt) << 16); |
398 | 346 | /* GDT[1]: 64K real mode code segment */ | |
399 | /* Set up a GDT from which we can load segment descriptors for real | 347 | lowmem_gdt[1] = |
400 | mode. The GDT is not used in real mode; it is just needed here to | 348 | GDT_ENTRY(0x009b, restart_pa, 0xffff); |
401 | prepare the descriptors. */ | 349 | |
402 | load_gdt(&real_mode_gdt); | 350 | /* Jump to the identity-mapped low memory code */ |
403 | 351 | restart_lowmem(type); | |
404 | /* Load the data segment registers, and thus the descriptors ready for | ||
405 | real mode. The base address of each segment is 0x100, 16 times the | ||
406 | selector value being loaded here. This is so that the segment | ||
407 | registers don't have to be reloaded after switching to real mode: | ||
408 | the values are consistent for real mode operation already. */ | ||
409 | __asm__ __volatile__ ("movl $0x0010,%%eax\n" | ||
410 | "\tmovl %%eax,%%ds\n" | ||
411 | "\tmovl %%eax,%%es\n" | ||
412 | "\tmovl %%eax,%%fs\n" | ||
413 | "\tmovl %%eax,%%gs\n" | ||
414 | "\tmovl %%eax,%%ss" : : : "eax"); | ||
415 | |||
416 | /* Jump to the 16-bit code that we copied earlier. It disables paging | ||
417 | and the cache, switches to real mode, and jumps to the BIOS reset | ||
418 | entry point. */ | ||
419 | __asm__ __volatile__ ("ljmp $0x0008,%0" | ||
420 | : | ||
421 | : "i" ((void *)(0x1000 - sizeof (real_mode_switch) - 100))); | ||
422 | } | 352 | } |
423 | #ifdef CONFIG_APM_MODULE | 353 | #ifdef CONFIG_APM_MODULE |
424 | EXPORT_SYMBOL(machine_real_restart); | 354 | EXPORT_SYMBOL(machine_real_restart); |
@@ -573,7 +503,7 @@ static void native_machine_emergency_restart(void) | |||
573 | 503 | ||
574 | #ifdef CONFIG_X86_32 | 504 | #ifdef CONFIG_X86_32 |
575 | case BOOT_BIOS: | 505 | case BOOT_BIOS: |
576 | machine_real_restart(jump_to_bios, sizeof(jump_to_bios)); | 506 | machine_real_restart(MRR_BIOS); |
577 | 507 | ||
578 | reboot_type = BOOT_KBD; | 508 | reboot_type = BOOT_KBD; |
579 | break; | 509 | break; |