From owner-freebsd-bugs Sat Sep 14 4: 0:26 2002 Delivered-To: freebsd-bugs@hub.freebsd.org Received: from mx1.FreeBSD.org (mx1.FreeBSD.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id DB02D37B400 for ; Sat, 14 Sep 2002 04:00:17 -0700 (PDT) Received: from freefall.freebsd.org (freefall.FreeBSD.org [216.136.204.21]) by mx1.FreeBSD.org (Postfix) with ESMTP id 0607343E72 for ; Sat, 14 Sep 2002 04:00:17 -0700 (PDT) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (gnats@localhost [127.0.0.1]) by freefall.freebsd.org (8.12.4/8.12.4) with ESMTP id g8EB0GJU086517 for ; Sat, 14 Sep 2002 04:00:16 -0700 (PDT) (envelope-from gnats@freefall.freebsd.org) Received: (from gnats@localhost) by freefall.freebsd.org (8.12.4/8.12.4/Submit) id g8EB0Gpf086515; Sat, 14 Sep 2002 04:00:16 -0700 (PDT) Received: from mx1.FreeBSD.org (mx1.FreeBSD.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 2C6D937B400 for ; Sat, 14 Sep 2002 03:58:14 -0700 (PDT) Received: from relay.cca.usart.ru (relay.cca.usart.ru [194.226.230.170]) by mx1.FreeBSD.org (Postfix) with ESMTP id B055C43E6E for ; Sat, 14 Sep 2002 03:58:12 -0700 (PDT) (envelope-from max@relay2.cca.usart.ru) Received: from relay2.cca.usart.ru (relay2.cca.usart.ru [194.226.230.173]) by relay.cca.usart.ru (8.11.1/8.11.1) with ESMTP id g8EAwAc82165 for ; Sat, 14 Sep 2002 16:58:10 +0600 (YEKST) (envelope-from max@relay2.cca.usart.ru) Received: from relay2.cca.usart.ru (relay2 [194.226.230.173]) by relay2.cca.usart.ru (8.12.3/8.12.3) with ESMTP id g8EAx8Vq033575 for ; Sat, 14 Sep 2002 16:59:08 +0600 (YEKST) (envelope-from max@relay2.cca.usart.ru) Received: (from max@localhost) by relay2.cca.usart.ru (8.12.3/8.12.3/Submit) id g8EAx8I5033574; Sat, 14 Sep 2002 16:59:08 +0600 (YEKST) Message-Id: <200209141059.g8EAx8I5033574@relay2.cca.usart.ru> Date: Sat, 14 Sep 2002 16:59:08 +0600 (YEKST) From: Max Gotlib Reply-To: Max Gotlib To: FreeBSD-gnats-submit@FreeBSD.org X-Send-Pr-Version: 3.113 Subject: i386/42766: Proposal to perform reboot via jump to BIOS entry Sender: owner-freebsd-bugs@FreeBSD.ORG Precedence: bulk List-ID: List-Archive: (Web Archive) List-Help: (List Instructions) List-Subscribe: List-Unsubscribe: X-Loop: FreeBSD.org >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 .... " */ 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