This message generated a parse failure. Raw output follows here. Please use 'back' to navigate. From devnull@lkml.org Fri Dec 1 07:09:11 2023 Received: from nic.funet.fi (nic.funet.fi [128.214.248.6]) by herbie.ucs.indiana.edu (8.7.5/8.6.12) with ESMTP id FAA15386 for ; Mon, 21 Oct 1996 05:34:25 -0500 (EST) Received: from vger.rutgers.edu ([128.6.190.2]) by nic.funet.fi with ESMTP id <62312-20453>; Mon, 21 Oct 1996 12:35:04 +0200 Received: by vger.rutgers.edu id <105888-9553>; Mon, 21 Oct 1996 06:28:33 -0400 Subject: Reboot-by-BIOS-jump-patch From: Matthias Urlichs To: linux-kernel@vger.rutgers.edu Date: Mon, 21 Oct 1996 12:27:35 +0200 (MET DST) Cc: torvalds@cs.helsinki.fi Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit Message-Id: <96Oct21.122740-0200met_dst.2654-7330+5@work.smurf.noris.de> Sender: owner-linux-kernel@vger.rutgers.edu Precedence: bulk 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 here. 42