Messages in this thread Patch in this message | | | Date | Sat, 25 Dec 1999 22:22:41 +0100 | From | Walter Hofmann <> | Subject | Re: APM poweroff |
| |
On Sat, 25 Dec 1999, James Mulcahy wrote:
> I'm using a 2.2.13 kernel, compiled with the poweroff on shutdown option. > But when I execute halt -p (or poweroff) the kernel panics at the point > where I expect it to powerdown. I copied the oops message by hand and > then re-typed it once I had rebooted. I've included the ksymoops output, > but I'm not sure where to go from here, is this a known problem, should I > be able to find the problem using the ksymoops output ?
I have attached my patch. It will hopefully solve your problem (please report success or failure). You need to turn on the CONFIG_APM_REAL_MODE_POWER_OFF option.
> ps. My processor is a MAD k6-2 400, and I'm fairly sure the apm code in > the bios isn't faulty, because the machine switches itself off when it > gets shutdown from windows...
Award BIOS? It is most certainly the BIOS' fault.
> EIP: 0050:[<0000885>]
0050 means it is BIOS code that faulted.
Walter diff -u -r linux-2.2.12-int/Documentation/Configure.help linux/Documentation/Configure.help --- linux-2.2.12-int/Documentation/Configure.help Thu Aug 26 19:40:29 1999 +++ linux/Documentation/Configure.help Sun Aug 29 17:14:27 1999 @@ -9078,7 +9078,14 @@ anonymous) contain support for this ("halt -p" shuts down Linux and powers off the computer, if executed from runlevel 0). As with the other APM options, this option may not work reliably with some APM - BIOS implementations. + BIOS implementations. You should try to switch on the next option if + your computer crashes in this case. + +Use real mode APM BIOS call to power off +CONFIG_APM_REAL_MODE_POWER_OFF + Use real mode APM BIOS calls to switch off the computer. This is + a work-around for a number of buggy BIOSes. Switch this option on if + your computer crashes instead of powering off properly. Ignore multiple suspend/standby events CONFIG_APM_IGNORE_MULTIPLE_SUSPEND diff -u -r linux-2.2.12-int/arch/i386/config.in linux/arch/i386/config.in --- linux-2.2.12-int/arch/i386/config.in Thu Aug 26 19:40:29 1999 +++ linux/arch/i386/config.in Sun Aug 29 17:14:27 1999 @@ -110,6 +110,9 @@ bool ' Make CPU Idle calls when idle' CONFIG_APM_CPU_IDLE bool ' Enable console blanking using APM' CONFIG_APM_DISPLAY_BLANK bool ' Power off on shutdown' CONFIG_APM_POWER_OFF + if [ "$CONFIG_APM_POWER_OFF" = "y" ]; then + bool ' Use real mode APM BIOS call to power off' CONFIG_APM_REAL_MODE_POWER_OFF + fi bool ' Ignore multiple suspend' CONFIG_APM_IGNORE_MULTIPLE_SUSPEND bool ' Ignore multiple suspend/resume cycles' CONFIG_APM_IGNORE_SUSPEND_BOUNCE bool ' RTC stores time in GMT' CONFIG_APM_RTC_IS_GMT diff -u -r linux-2.2.12-int/arch/i386/kernel/apm.c linux/arch/i386/kernel/apm.c --- linux-2.2.12-int/arch/i386/kernel/apm.c Sun Aug 29 16:23:35 1999 +++ linux/arch/i386/kernel/apm.c Sun Aug 29 17:23:01 1999 @@ -128,6 +128,7 @@ #include <linux/miscdevice.h> #include <linux/apm_bios.h> #include <linux/init.h> +#include <linux/reboot.h> #include <asm/system.h> #include <asm/uaccess.h> @@ -554,7 +555,11 @@ * kernel option. */ if (apm_enabled || (smp_hack == 2)) +#ifdef CONFIG_APM_REAL_MODE_POWER_OFF + apm_real_mode_power_off(); +#else (void) apm_set_power_state(APM_STATE_OFF); +#endif /* CONFIG_APM_REAL_MODE_POWER_OFF */ } #ifdef CONFIG_APM_DISPLAY_BLANK diff -u -r linux-2.2.12-int/arch/i386/kernel/process.c linux/arch/i386/kernel/process.c --- linux-2.2.12-int/arch/i386/kernel/process.c Sun Aug 29 16:23:35 1999 +++ linux/arch/i386/kernel/process.c Sun Aug 29 17:24:22 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, August 1999 + * */ /* @@ -383,6 +387,104 @@ void machine_halt(void) { } + +#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF) \ + && defined(CONFIG_APM_REAL_MODE_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 power off code. */ + +unsigned char apm_real_mode_power_off_code [] = +{ + 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_real_mode_power_off(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_power_off_code)), + apm_real_mode_power_off_code, sizeof (apm_real_mode_power_off_code)); + + /* 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_power_off_code)))); + +} +#endif void machine_power_off(void) { diff -u -r linux-2.2.12-int/include/linux/reboot.h linux/include/linux/reboot.h --- linux-2.2.12-int/include/linux/reboot.h Sun Aug 29 16:23:35 1999 +++ linux/include/linux/reboot.h Sun Aug 29 17:34:46 1999 @@ -47,6 +47,11 @@ extern void machine_halt(void); extern void machine_power_off(void); +#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF) \ + && defined(CONFIG_APM_REAL_MODE_POWER_OFF) +extern void apm_real_mode_power_off(void); +#endif + #endif #endif /* _LINUX_REBOOT_H */ | |