patch-2.1.20 linux/arch/i386/kernel/process.c

Next file: linux/arch/i386/kernel/ptrace.c
Previous file: linux/arch/i386/kernel/ldt.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.19/linux/arch/i386/kernel/process.c linux/arch/i386/kernel/process.c
@@ -184,16 +184,90 @@
  */
 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)
 {
@@ -204,22 +278,107 @@
 			break;
 }
 
-void hard_reset_now(void)
+void hard_reset_now (void)
 {
-	int i, j;
 
-	sti();
-	*((unsigned short *)__va(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(100);
+	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)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov