PATCH: real mode POWER OFF with buggy BIOS

Raphael Bossek (raphael.bossek@solutions4linux.de)
Sat, 28 Aug 1999 16:54:31 +0200


--LG0Ll82vYr46+VA1
Content-Type: text/plain; charset=us-ascii

Hi,

the patch i get from Walter Hofmann is really great :-) First run, first success ! I patched 2.2.12 and everything works as expected, my (buggy) BIOS switch the box off, really crazy...

One improvement could be a option within config/menuconfig to switch the realmode-poweroff workarround on/off.

This mail contains the patch as attachment so please merge this stuff with the new linux release !

-- 
Raphal Bossek <raphael.bossek@solutions4linux.de> [ICQ #40047651]
PGP fingerprint: DF 6F 2C 76 46 1E B4 1C  5B A5 2E 1B FC E0 D3 F6
PGP public key: http://www.solutions4linux.de/private/pgpkey.asc

--LG0Ll82vYr46+VA1 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=realmodepoweroff

diff -u -r linux-2.2.9-isdn.vanilla/arch/i386/kernel/apm.c linux/arch/i386/kernel/apm.c --- linux-2.2.9-isdn.vanilla/arch/i386/kernel/apm.c Fri Jan 15 07:57:25 1999 +++ linux/arch/i386/kernel/apm.c Sat Jun 12 23:45:05 1999 @@ -554,7 +554,7 @@ * kernel option. */ if (apm_enabled || (smp_hack == 2)) - (void) apm_set_power_state(APM_STATE_OFF); + apm_realmode_poweroff(); } #ifdef CONFIG_APM_DISPLAY_BLANK diff -u -r linux-2.2.9-isdn.vanilla/arch/i386/kernel/process.c linux/arch/i386/kernel/process.c --- linux-2.2.9-isdn.vanilla/arch/i386/kernel/process.c Fri Apr 30 17:13:37 1999 +++ linux/arch/i386/kernel/process.c Sat Jun 12 23:45:16 1999 @@ -2,6 +2,10 @@ * linux/arch/i386/kernel/process.c * * Copyright (C) 1995 Linus Torvalds + * + * Change back to real mode for APM power off + * Walter Hofmann, March 1999 + * */ /* @@ -383,6 +387,102 @@ void machine_halt(void) { } + +#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF) + +/* This is 16-bit protected mode code to disable paging and the cache, + switch to real mode and jump to the BIOS APM poweroff code. */ + +unsigned char apm_real_mode_poweroff [] = +{ + 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, 0xed, 0x0f, 0x00, 0x00, /* ljmp g */ + 0xb8, 0x00, 0x10, /* g: movw $0x1000,ax */ + 0x8e, 0xd0, /* movw ax,ss */ + 0xbc, 0x00, 0xf0, /* movw $0xf000,sp */ + 0xb8, 0x07, 0x53, /* movw $0x5307,ax */ + 0xbb, 0x01, 0x00, /* movw $0x0001,bx */ + 0xb9, 0x03, 0x00, /* movw $0x0003,cx */ + 0xcd, 0x15 /* int $0x15 */ +}; + +void apm_realmode_poweroff(void) +{ + cli (); + + /* 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 + USER_PGD_PTRS, + sizeof (swapper_pg_dir [0]) * KERNEL_PGD_PTRS); + + /* 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. We bother with + * `SET_PAGE_DIR' because although might be rebooting, but if we change + * the way we set root page dir in the future, then we wont break a + * seldom used feature ;) + */ + + SET_PAGE_DIR(current,swapper_pg_dir); + + /* 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 (apm_real_mode_poweroff)), + apm_real_mode_poweroff, sizeof (apm_real_mode_poweroff)); + + /* 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 APM + entry point. */ + + __asm__ __volatile__ ("ljmp $0x0008,%0" + : + : "i" ((void *) (0x1000 - sizeof (apm_real_mode_poweroff)))); + +} +#endif void machine_power_off(void) { diff -u -r linux-2.2.9-isdn.vanilla/include/linux/reboot.h linux/include/linux/reboot.h --- linux-2.2.9-isdn.vanilla/include/linux/reboot.h Fri Aug 28 04:37:33 1998 +++ linux/include/linux/reboot.h Sat Jun 12 23:48:49 1999 @@ -47,6 +47,10 @@ extern void machine_halt(void); extern void machine_power_off(void); +#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF) +extern void apm_realmode_poweroff(void); +#endif + #endif #endif /* _LINUX_REBOOT_H */

--LG0Ll82vYr46+VA1--

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/