Reboot-by-BIOS-jump-patch

Matthias Urlichs (smurf@smurf.noris.de)
Mon, 21 Oct 1996 12:27:35 +0200 (MET DST)


Grumble. ;-) Apparently, enough people managed to miss the patch for BISO
reboots that's been posted here umpteen times already.

Well, here it is again; I modified the thing so that's (a) properly
indented and (b) you can control whether to boot via the BIOS by saying
"reboot=bios" or "reboot=hard" on the LILO command line (or its append=
string). Stuff like "reboot=cold,bios" works, too.

Linus: Please include in the kernel. Without it, a lot of boards with the
newer AMD 486/100 and /133 chips (mine included) don't want to reboot at
all.

Anyway, if the original author of this patch would kindly step forward and
accept the blame  credit for it...

Index: kernel/linux/arch/i386/kernel/process.c
diff -u kernel/linux/arch/i386/kernel/process.c:1.1.1.5 kernel/linux/arch/i386/kernel/process.c:1.3
--- kernel/linux/arch/i386/kernel/process.c:1.1.1.5 Tue Oct 8 21:01:25 1996
+++ kernel/linux/arch/i386/kernel/process.c Mon Oct 21 12:22:48 1996
@@ -183,44 +183,200 @@
*/
static long no_idt[2] = {0, 0};
static int reboot_mode = 0;
+static int reboot_thru_bios = 0;

void reboot_setup(char *str, int *ints)
{
- int mode = 0;
+ while(1) {
+ switch (*str) {
+ case 'w': /* "warm" reboot (no memory testing etc) */
+ reboot_mode = 0x1234;
+ break;
+ case 'c': /* "cold" reboot (with memory testing etc) */
+ reboot_mode = 0x0;
+ break;
+ 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 = 0;
+ break;
+ }
+ if((str = strchr(str,',')) != NULL)
+ str++;
+ else
+ break;
+ }
+}
+
+
+/* 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. */

- /* "w" for "warm" reboot (no memory testing etc) */
- if (str[0] == 'w')
- mode = 0x1234;
- reboot_mode = mode;
+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 */
+};
+
+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, 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 BIOSes don't assume much. */
+
+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, 0x08, /* invd */
+ 0x24, 0x10, /* f: andb $0x10,al */
+ 0x66, 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */
+ 0xea, 0x00, 0x00, 0xff, 0xff /* ljmp $0xffff,$0x0000 */
+};

static inline void kb_wait(void)
{
int i;
-
for (i=0; i<0x10000; i++)
if ((inb_p(0x64) & 0x02) == 0)
break;
}

-void hard_reset_now(void)
+void hard_reset_now (void)
{
- int i, j;

- sti();
-/* rebooting needs to touch the page at absolute addr 0 */
- pg0[0] = 7;
- *((unsigned short *)0x472) = reboot_mode;
- for (;;) {
- for (i=0; i<100; i++) {
- kb_wait();
- for(j = 0; j < 100000 ; j++)
- /* nothing */;
- outb(0xfe,0x64); /* pulse reset low */
- udelay(10);
+ if(!reboot_thru_bios) {
+ sti();
+ /* rebooting needs to touch the page at absolute addr 0 */
+ pg0[0] = 7;
+ *((unsigned short *)0x472) = reboot_mode;
+ for (;;) {
+ int i;
+ for (i=0; i<100; i++) {
+ int j;
+ kb_wait();
+ for(j = 0; j < 100000 ; j++)
+ /* nothing */;
+ outb(0xfe,0x64); /* pulse reset low */
+ udelay(10);
+ }
+ __asm__ __volatile__("\tlidt %0": "=m" (no_idt));
}
- __asm__ __volatile__("\tlidt %0": "=m" (no_idt));
}
+
+ cli ();
+
+ /* 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. I'm not sure if
+ `outb_p' is needed instead of just `outb'. Use it to be on the
+ safe side. */
+
+ outb_p (0x8f, 0x70);
+ outb_p (0x00, 0x71);
+
+ /* Remap the kernel at virtual address zero, as well as offset zero
+ from the kernel segment. This assumes the kernel segment starts at
+ virtual address 0xc0000000. */
+
+ memcpy (swapper_pg_dir, swapper_pg_dir + 768,
+ sizeof (swapper_pg_dir [0]) * 256);
+
+ /* Make sure the first page is mapped to the start of physical memory.
+ It is normally not mapped, to trap kernel NULL pointer dereferences. */
+
+ pg0 [0] = 7;
+
+ /* Use `swapper_pg_dir' as our page directory. Don't bother with
+ `SET_PAGE_DIR' because interrupts are disabled and we're rebooting.
+ This instruction flushes the TLB. */
+
+ __asm__ __volatile__ ("movl %0,%%cr3" : : "a" (swapper_pg_dir) : "memory");
+
+ /* Write 0x1234 to absolute memory location 0x472. The BIOS reads
+ this on booting to tell it to "Bypass memory test (also warm
+ boot)". This seems like a fairly standard thing that gets set by
+ REBOOT.COM programs, and the previous reset routine did this
+ 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)),
+ real_mode_switch, sizeof (real_mode_switch));
+
+ /* 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__ ("movw $0x0010,%%ax\n"
+ "\tmovw %%ax,%%ds\n"
+ "\tmovw %%ax,%%es\n"
+ "\tmovw %%ax,%%fs\n"
+ "\tmovw %%ax,%%gs\n"
+ "\tmovw %%ax,%%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))));
}

void show_regs(struct pt_regs * regs)
Why isn't there a special name for the tops of your feet?
-- Lily Tomlin

-- 
Matthias Urlichs         \  noris network GmbH  /  Xlink-POP Nürnberg 
Schleiermacherstraße 12   \   Linux+Internet   /   EMail: urlichs@noris.de
90491 Nürnberg (Germany)   \    Consulting+Programming+Networking+etc'ing
   PGP: 1024/4F578875   1B 89 E2 1C 43 EA 80 44  15 D2 29 CF C6 C7 E0 DE
       Click <A HREF="http://info.noris.de/~smurf/finger">here</A>.    42