--- /home/jbeulich/src/xen/2006-03-26/xen/arch/x86/domain.c 2006-03-20 16:43:29.000000000 +0100 +++ 2006-03-26/xen/arch/x86/domain.c 2006-04-04 12:39:28.000000000 +0200 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,10 @@ static int opt_noreboot = 0; boolean_param("noreboot", opt_noreboot); +/* reboot_str: comma-separated list of reboot options. */ +static char __initdata reboot_str[10] = ""; +string_param("reboot", reboot_str); + struct percpu_ctxt { struct vcpu *curr_vcpu; unsigned int dirty_segment_mask; @@ -128,6 +133,147 @@ void machine_halt(void) __machine_halt(NULL); } +#ifdef __i386__ + +static int reboot_thru_bios; + +/* 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 unsigned long long +real_mode_gdt_entries [3] = +{ + 0x0000000000000000ULL, /* Null descriptor */ + 0x00009a000000ffffULL, /* 16-bit real-mode 64k code at 0x00000000 */ + 0x000092000100ffffULL /* 16-bit real-mode 64k data at 0x00000100 */ +}; + +static const struct +{ + unsigned short size __attribute__ ((packed)); + unsigned long long * base __attribute__ ((packed)); +} +real_mode_gdt = { sizeof (real_mode_gdt_entries) - 1, real_mode_gdt_entries }, +real_mode_idt = { 0x3ff, NULL }; + + +/* 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 [] = +{ + 0x0f, 0x20, 0xc0, /* movl %cr0,%eax */ + 0x66, 0x83, 0xe0, 0x11, /* andl $0x00000011,%eax */ + 0x66, 0x0d, 0x00, 0x00, 0x00, 0x60, /* orl $0x60000000,%eax */ + 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */ + 0x0f, 0x22, 0xd8, /* movl %eax,%cr3 */ + 0x0f, 0x20, 0xc2, /* movl %cr0,%edx */ + 0x66, 0x81, 0xe2, 0x00, 0x00, 0x00, 0x60, /* andl $0x60000000,%edx */ + 0x74, 0x02, /* jz f */ + 0x0f, 0x09, /* wbinvd */ + 0x24, 0x10, /* f: andb $0x10,al */ + 0x0f, 0x22, 0xc0 /* movl %eax,%cr0 */ +}; +#define MAX_LENGTH 0x40 +static const unsigned char jump_to_bios [] = +{ + 0xea, 0xf0, 0xff, 0x00, 0xf0 /* ljmp $0xf000,$0xfff0 */ +}; + +/* + * 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 MAX_LENGTH! + */ +void machine_real_restart(const unsigned char *code, unsigned length) +{ + local_irq_disable(); + + /* Write zero to CMOS register number 0x0f, which the BIOS POST + routine will recognize as telling it to do a proper reboot. (Well + that's what this book in front of me says -- it may only apply to + the Phoenix BIOS though, it's not clear). At the same time, + disable NMIs by setting the top bit in the CMOS address register, + as we're about to do peculiar things to the CPU. */ + + spin_lock(&rtc_lock); + CMOS_WRITE(0x00, 0x8f); + spin_unlock(&rtc_lock); + + /* Identity-map virtual address zero. */ + + map_pages_to_xen(0, 0, 1, __PAGE_HYPERVISOR|MAP_SMALL_PAGES); + set_current(idle_vcpu[0]); + write_ptbase(idle_vcpu[0]); + + /* 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 *) (PAGE_SIZE - sizeof (real_mode_switch) - MAX_LENGTH), + real_mode_switch, sizeof (real_mode_switch)); + memcpy ((void *) (PAGE_SIZE - MAX_LENGTH), code, length); + + /* Set up the IDT for real mode. */ + + __asm__ __volatile__("lidt %0": : "m" (real_mode_idt)); + + /* 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. */ + + __asm__ __volatile__("lgdt %0": : "m" (real_mode_gdt)); + + /* 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__ ("\tmov %0,%%ds\n" + "\tmov %0,%%es\n" + "\tmov %0,%%fs\n" + "\tmov %0,%%gs\n" + "\tmov %0,%%ss" + : + : "r" (0x0010)); + + /* 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 *) (PAGE_SIZE - sizeof (real_mode_switch) - MAX_LENGTH))); +} + +#else +# define machine_real_restart(x, y) +# define reboot_thru_bios 0 +#endif + void machine_restart(char * __unused) { int i; @@ -162,22 +308,117 @@ void machine_restart(char * __unused) /* Rebooting needs to touch the page at absolute address 0. */ *((unsigned short *)__va(0x472)) = reboot_mode; - for ( ; ; ) + if (reboot_thru_bios <= 0) { - /* Pulse the keyboard reset line. */ - for ( i = 0; i < 100; i++ ) + for ( ; ; ) { - kb_wait(); - udelay(50); - outb(0xfe,0x64); /* pulse reset low */ - udelay(50); + /* Pulse the keyboard reset line. */ + for ( i = 0; i < 100; i++ ) + { + kb_wait(); + udelay(50); + outb(0xfe,0x64); /* pulse reset low */ + udelay(50); + } + + /* That didn't work - force a triple fault.. */ + __asm__ __volatile__("lidt %0": "=m" (no_idt)); + __asm__ __volatile__("int3"); } + } + machine_real_restart(jump_to_bios, sizeof(jump_to_bios)); +} + +#ifndef reboot_thru_bios +/* + * Reboot options and system auto-detection code provided by + * Dell Inc. so their systems "just work". :-) + */ - /* That didn't work - force a triple fault.. */ - __asm__ __volatile__("lidt %0": "=m" (no_idt)); - __asm__ __volatile__("int3"); +/* + * Some machines require the "reboot=b" commandline option, this quirk makes that automatic. + */ +static int __init set_bios_reboot(struct dmi_system_id *d) +{ + if (!reboot_thru_bios) + { + reboot_thru_bios = 1; + printk("%s series board detected. Selecting BIOS-method for reboots.\n", d->ident); } + return 0; +} + +static struct dmi_system_id __initdata reboot_dmi_table[] = { + { /* Handle problems with rebooting on Dell 1300's */ + .callback = set_bios_reboot, + .ident = "Dell PowerEdge 1300", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge 1300/"), + }, + }, + { /* Handle problems with rebooting on Dell 300's */ + .callback = set_bios_reboot, + .ident = "Dell PowerEdge 300", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge 300/"), + }, + }, + { /* Handle problems with rebooting on Dell 2400's */ + .callback = set_bios_reboot, + .ident = "Dell PowerEdge 2400", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "PowerEdge 2400"), + }, + }, + { /* Handle problems with rebooting on HP laptops */ + .callback = set_bios_reboot, + .ident = "HP Compaq Laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Compaq"), + }, + }, + { } +}; +#endif + +static int __init reboot_init(void) +{ + const char *str; + + for (str = reboot_str; *str; ++str) + { + switch (*str) { + case 'n': /* no reboot */ + opt_noreboot = 1; + break; + case 'w': /* "warm" reboot (no memory testing etc) */ + reboot_mode = 0x1234; + break; + case 'c': /* "cold" reboot (with memory testing etc) */ + reboot_mode = 0x0; + break; +#ifndef reboot_thru_bios + case 'b': /* "bios" reboot by jumping through the BIOS */ + reboot_thru_bios = 1; + break; + case 'h': /* "hard" reboot by toggling RESET and/or crashing the CPU */ + reboot_thru_bios = -1; + break; +#endif + } + if((str = strchr(str,',')) == NULL) + break; + } +#ifndef reboot_thru_bios + dmi_check_system(reboot_dmi_table); +#endif + return 0; } +__initcall(reboot_init); void dump_pageframe_info(struct domain *d)