diff options
Diffstat (limited to 'arch/x86/kernel/reboot.c')
-rw-r--r-- | arch/x86/kernel/reboot.c | 129 |
1 files changed, 34 insertions, 95 deletions
diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c index fc7aae1e2bc7..08c44b08bf5b 100644 --- a/arch/x86/kernel/reboot.c +++ b/arch/x86/kernel/reboot.c | |||
@@ -6,6 +6,7 @@ | |||
6 | #include <linux/dmi.h> | 6 | #include <linux/dmi.h> |
7 | #include <linux/sched.h> | 7 | #include <linux/sched.h> |
8 | #include <linux/tboot.h> | 8 | #include <linux/tboot.h> |
9 | #include <linux/delay.h> | ||
9 | #include <acpi/reboot.h> | 10 | #include <acpi/reboot.h> |
10 | #include <asm/io.h> | 11 | #include <asm/io.h> |
11 | #include <asm/apic.h> | 12 | #include <asm/apic.h> |
@@ -285,6 +286,14 @@ static struct dmi_system_id __initdata reboot_dmi_table[] = { | |||
285 | DMI_MATCH(DMI_BOARD_NAME, "P4S800"), | 286 | DMI_MATCH(DMI_BOARD_NAME, "P4S800"), |
286 | }, | 287 | }, |
287 | }, | 288 | }, |
289 | { /* Handle problems with rebooting on VersaLogic Menlow boards */ | ||
290 | .callback = set_bios_reboot, | ||
291 | .ident = "VersaLogic Menlow based board", | ||
292 | .matches = { | ||
293 | DMI_MATCH(DMI_BOARD_VENDOR, "VersaLogic Corporation"), | ||
294 | DMI_MATCH(DMI_BOARD_NAME, "VersaLogic Menlow board"), | ||
295 | }, | ||
296 | }, | ||
288 | { } | 297 | { } |
289 | }; | 298 | }; |
290 | 299 | ||
@@ -295,68 +304,16 @@ static int __init reboot_init(void) | |||
295 | } | 304 | } |
296 | core_initcall(reboot_init); | 305 | core_initcall(reboot_init); |
297 | 306 | ||
298 | /* The following code and data reboots the machine by switching to real | 307 | extern const unsigned char machine_real_restart_asm[]; |
299 | mode and jumping to the BIOS reset entry point, as if the CPU has | 308 | 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 | 309 | ||
312 | static const struct desc_ptr | 310 | 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 | { | 311 | { |
350 | 0xea, 0x00, 0x00, 0xff, 0xff /* ljmp $0xffff,$0x0000 */ | 312 | void *restart_va; |
351 | }; | 313 | unsigned long restart_pa; |
314 | void (*restart_lowmem)(unsigned int); | ||
315 | u64 *lowmem_gdt; | ||
352 | 316 | ||
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(); | 317 | local_irq_disable(); |
361 | 318 | ||
362 | /* Write zero to CMOS register number 0x0f, which the BIOS POST | 319 | /* Write zero to CMOS register number 0x0f, which the BIOS POST |
@@ -384,41 +341,23 @@ void machine_real_restart(const unsigned char *code, int length) | |||
384 | too. */ | 341 | too. */ |
385 | *((unsigned short *)0x472) = reboot_mode; | 342 | *((unsigned short *)0x472) = reboot_mode; |
386 | 343 | ||
387 | /* For the switch to real mode, copy some code to low memory. It has | 344 | /* 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 | 345 | lowmem_gdt = TRAMPOLINE_SYM(machine_real_restart_gdt); |
389 | has to have the same physical and virtual address, because it turns | 346 | |
390 | off paging. Copy it near the end of the first page, out of the way | 347 | restart_va = TRAMPOLINE_SYM(machine_real_restart_asm); |
391 | of BIOS variables. */ | 348 | restart_pa = virt_to_phys(restart_va); |
392 | memcpy((void *)(0x1000 - sizeof(real_mode_switch) - 100), | 349 | restart_lowmem = (void (*)(unsigned int))restart_pa; |
393 | real_mode_switch, sizeof (real_mode_switch)); | 350 | |
394 | memcpy((void *)(0x1000 - 100), code, length); | 351 | /* GDT[0]: GDT self-pointer */ |
395 | 352 | lowmem_gdt[0] = | |
396 | /* Set up the IDT for real mode. */ | 353 | (u64)(sizeof(machine_real_restart_gdt) - 1) + |
397 | load_idt(&real_mode_idt); | 354 | ((u64)virt_to_phys(lowmem_gdt) << 16); |
398 | 355 | /* GDT[1]: 64K real mode code segment */ | |
399 | /* Set up a GDT from which we can load segment descriptors for real | 356 | lowmem_gdt[1] = |
400 | mode. The GDT is not used in real mode; it is just needed here to | 357 | GDT_ENTRY(0x009b, restart_pa, 0xffff); |
401 | prepare the descriptors. */ | 358 | |
402 | load_gdt(&real_mode_gdt); | 359 | /* Jump to the identity-mapped low memory code */ |
403 | 360 | 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 | } | 361 | } |
423 | #ifdef CONFIG_APM_MODULE | 362 | #ifdef CONFIG_APM_MODULE |
424 | EXPORT_SYMBOL(machine_real_restart); | 363 | EXPORT_SYMBOL(machine_real_restart); |
@@ -573,7 +512,7 @@ static void native_machine_emergency_restart(void) | |||
573 | 512 | ||
574 | #ifdef CONFIG_X86_32 | 513 | #ifdef CONFIG_X86_32 |
575 | case BOOT_BIOS: | 514 | case BOOT_BIOS: |
576 | machine_real_restart(jump_to_bios, sizeof(jump_to_bios)); | 515 | machine_real_restart(MRR_BIOS); |
577 | 516 | ||
578 | reboot_type = BOOT_KBD; | 517 | reboot_type = BOOT_KBD; |
579 | break; | 518 | break; |