Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 14 Sep 2002 16:59:08 +0600 (YEKST)
From:      Max Gotlib <max@cca.usart.ru>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   i386/42766: Proposal to perform reboot via jump to BIOS entry
Message-ID:  <200209141059.g8EAx8I5033574@relay2.cca.usart.ru>

next in thread | raw e-mail | index | archive | help

>Number:         42766
>Category:       i386
>Synopsis:       Proposal to perform reboot via jump to BIOS entry
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          update
>Submitter-Id:   current-users
>Arrival-Date:   Sat Sep 14 04:00:16 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator:     Max Gotlib
>Release:        FreeBSD 4.6.2-RELEASE i386
>Organization:
Urals State University for Railway Transport
>Environment:
System: FreeBSD relay2.cca.usart.ru 4.6.2-RELEASE FreeBSD 4.6.2-RELEASE #0: Mon Aug 12 17:58:17 YEKST 2002 root@relay2.cca.usart.ru:/usr/src/sys/compile/RELAY2 i386

>Description:
I've got a number of PC-104 boxies (STPC based with PC97307).
All of them contain several HW bugs and as a result they are not capable
to reboot both via KBD conreoller and via tripple-fault. The same boxies,
running Linux were capable to reboot via dirty hack called "reboot by jumping
through tht BIOS" in Linux kernel sources. So i decided to port that code to
the FreeBSD.

>How-To-Repeat:
Get the same broken box as I did and try to reboot it ...

>Fix:
The following is the patch for /sys/i386/i386/vm_machdep.c. There new sysctl
OID is introduced (reboot_via_bios), that controlls weather CPU reset will
be done (either via KBD controller or unmapping entire virtual space) or
CPU will be switched to real mode and jump to BIOS will be done.

-----------------------------------
--- ../../i386/i386/vm_machdep.c.orig	Thu Sep 12 13:35:26 2002
+++ ../../i386/i386/vm_machdep.c	Sat Sep 14 16:08:40 2002
@@ -86,6 +86,9 @@
 #endif
 
 static void	cpu_reset_real __P((void));
+#ifndef PC98
+static void	cpu_reset_via_bios __P((void));
+#endif
 #ifdef SMP
 static void	cpu_reset_proxy __P((void));
 static u_int	cpu_reset_proxyid;
@@ -487,6 +490,184 @@
 #endif
 }
 
+
+#ifndef PC98
+
+/*
+  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.
+*/
+
+static int reboot_via_bios = 0;
+SYSCTL_INT(_machdep, OID_AUTO, reboot_via_bios,
+	CTLFLAG_RW, &reboot_via_bios, 0,
+	"Reboot via jump to BIOS entry");
+
+/* Temporary GDT */
+static struct segment_descriptor
+real_mode_gdt_entries[3] = {
+    /* Null descriptor */
+    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+    /* 16-bit real-mode 64k code at 0x00000000 */
+    {
+	0xffff,		/* 16 segment extent (lsb) */
+	0,		/* 24 segment base address (lsb) */
+	SDT_MEMERA,	/* 5  segment type */
+	0,		/* 2  segment descriptor priority level */
+	1,		/* 1  segment descriptor present */
+	0,		/* 4  segment extent (msb) */
+	0,		/* 2  unused */
+	0,		/* 1  default 32 vs 16 bit size */
+	0,		/* 1  limit granularity (byte/page units)*/
+	0		/* 8  segment base address  (msb) */
+    },
+    /* 16-bit real-mode 64k data at 0x00000000 */
+    {
+	0xffff,		/* segment extent (lsb) */
+	0x0100,		/* segment base address (lsb) */
+	SDT_MEMRWA,	/* segment type */
+	0,		/* segment descriptor priority level */
+	1,		/* segment descriptor present */
+	0,		/* segment extent (msb) */
+	0,		/* unused */
+	0,		/* default 32 vs 16 bit size */
+	0,		/* limit granularity (byte/page units)*/
+	0		/* segment base address  (msb) */
+    }
+};
+
+/* Region descriptors for temporary IDT and GDT */
+static struct region_descriptor real_mode_idt = { 0x3ff, 0 };
+static struct region_descriptor real_mode_gdt = {
+    sizeof(struct segment_descriptor) * 3 - 1, 0x700
+};
+
+/* 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.
+   
+   Finally, the code jumps to the BIOS entry just as if CPU reset ocuared. */
+
+static u_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  */
+};
+
+/*
+ * Switch to real mode and then jump to the BIOS entry point
+ */
+static void
+cpu_reset_via_bios( void )
+{
+    	u_int c;
+
+    /*
+      Write zero to CMOS register number 0x0f, which the BIOS POST
+      routine will recognize as telling it to do a proper reboot.
+      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.
+    */
+    	disable_intr();
+    	outb(IO_RTC, 0x8F);
+    	inb(0x84);
+    	outb(IO_RTC + 1, 0);
+    
+    /*
+      Employ the first (0) page of the physical memory for:
+      1. Page table for mapping 0x0000...0x1000 linear addresses to the same
+         physical addresses (PT starts at 0x0000);
+      2. Hold temporary GDT, IDT and their "region descriptors" (GDT starts at
+         physical location 0x0700, it's region descriptor - 0x800; IDT region
+         descriptor - 0x806);
+      3. Hold the 16-bit code to switch to real mode and jump to the BIOS
+         entry point ( 0x1000 - sizeof(code) );
+     */
+    
+    	/* Prepare page table */
+    	pmap_kenter((vm_offset_t)ptvmmap , 0);
+    	invltlb();
+    	*((u_int *) ptvmmap) = PG_RW | PG_V;
+    	pmap_kremove((vm_offset_t)ptvmmap);
+
+    	/* Install PT into PD */
+    	c = rcr3() & ~PAGE_MASK;	/* get current PD location */
+    	pmap_kenter((vm_offset_t)ptvmmap , c);
+    	invltlb();
+    	*((u_int *) ptvmmap) = PG_RW | PG_V;
+    	pmap_kremove((vm_offset_t)ptvmmap);
+
+    	/* Copy GDT image and region descriptors to the 0-page */
+    	memcpy((void *) 0x800, &real_mode_gdt, sizeof(real_mode_gdt));
+    	memcpy((void *) 0x806, &real_mode_idt, sizeof(real_mode_idt));
+    	memcpy((void *) 0x700, &real_mode_gdt_entries,
+	    	sizeof(struct segment_descriptor) * 4);
+    	/* Copy switch-code to the 0-page */
+    	memcpy((void *)(0x1000 - sizeof(real_mode_switch)),
+	    	real_mode_switch, sizeof(real_mode_switch));
+
+    	/* Tell the BIOS to perform cold-reboot */
+    	*(unsigned short *)0x472 = 0; /*x1234;*/
+
+    	/* Set up the IDT for real mode. */
+    	__asm__ __volatile__ ("  movl $0x0806,%%eax\n"
+			  	"\tlidt (%%eax)\n" : : : "eax");
+	
+    	/* 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__ ("  movl $0x0800,%%eax\n"
+			  	"\tlgdt (%%eax)\n" : : : "eax");
+
+    	/* 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__ ("  movl $0x0010,%%eax\n"
+			  	"\tmovl %%eax,%%ds\n"
+			  	"\tmovl %%eax,%%es\n"
+			  	"\tmovl %%eax,%%fs\n"
+			  	"\tmovl %%eax,%%gs\n"
+			  	"\tmovl %%eax,%%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))));
+}
+
+#endif /* PC98 */
+
 static void
 cpu_reset_real()
 {
@@ -509,15 +690,19 @@
 	 */
 
 #if !defined(BROKEN_KEYBOARD_RESET)
-	outb(IO_KBD + 4, 0xFE);
-	DELAY(500000);	/* wait 0.5 sec to see if that did it */
-	printf("Keyboard reset did not work, attempting CPU shutdown\n");
-	DELAY(1000000);	/* wait 1 sec for printf to complete */
+	if(reboot_via_bios == 0) {
+		outb(IO_KBD + 4, 0xFE);
+		DELAY(500000);	/* wait 1 sec for printf to complete */
+		printf("Keyboard reset did not work, attempting CPU shutdown\n");
+		DELAY(1000000);	/* wait 1 sec for printf to complete */
+	}
 #endif
+	if(reboot_via_bios)
+		cpu_reset_via_bios();
 #endif /* PC98 */
+
 	/* force a shutdown by unmapping entire address space ! */
 	bzero((caddr_t) PTD, PAGE_SIZE);
-
 	/* "good night, sweet prince .... <THUNK!>" */
 	invltlb();
 	/* NOTREACHED */
-----------------------------------

>Release-Note:
>Audit-Trail:
>Unformatted:

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200209141059.g8EAx8I5033574>