Re: APIC on laptop hangs the system @ powerstate change

From: Mikael Pettersson (mikpe@csd.uu.se)
Date: Tue Jan 01 2002 - 07:10:31 EST


On Tue, 01 Jan 2002 05:54:34 +0000, Todor Todorov wrote:
>I have a Dell Inspiron 8000 Laptop with PIII Mobile processor.
>... But I seem
>to have a problem with APIC and changes of the power state. APIC gets
>discovered and enabled when the kernel boots with no indication of any
>problem. ACPI works too. Now, if I detach the power cable when it's
>plugged in or vice versa, the system hangs without any chance for
>recovery than hard boot.
>...
>obeserved hang behavior allways reproducable with kernels 2.4.16,
>2.4.17-pre8, 2.4.17, 2.4.18-pre1, 2.5.1, 2.5.2-pre3 with APIC + either
>ACPI or APM, tried compiles with gcc-2.95.4, gcc-3.0.1, gcc-3.0.2, gcc-3.0.3
>This hangs occures allways when APIC support on uniprocessors is
>compiled in, no matter what power managemnet sheme is used. It never
>occures when APIC is not present, no matter of which kernel version
>you'd take. Besides this, with APIC compiled in, shutdown -h or even
>halt -f fails to pwer off the sistem (it goes down and halted but the
>power remains on).

Known problem. Dell's putting local APIC-capable Mobile P6s in their
recent laptops but haven't updated their BIOSen to handle enabled local
APICs. (Older Mobile P6s didn't have local APICs.) This results in
the problems you've described: hangs whenever the BIOS is activated,
i.e. at PM events and when accessing the BIOS setup screens.

The only known solution is to keep the local APIC disabled in Linux
when using a Dell laptop. The patch included below (for 2.4.17) will
do that automatically for you; alternatively you can reconfigure your
kernel with CONFIG_X86_UP_APIC=n (and CONFIG_SMP=n).

/Mikael

--- linux-2.4.17-init-order/arch/i386/kernel/apic.c.~1~ Fri Nov 23 22:40:14 2001
+++ linux-2.4.17-init-order/arch/i386/kernel/apic.c Sun Dec 23 18:51:40 2001
@@ -571,12 +571,17 @@
  * Detect and enable local APICs on non-SMP boards.
  * Original code written by Keir Fraser.
  */
+int dont_enable_local_apic __initdata = 0;
 
 static int __init detect_init_APIC (void)
 {
         u32 h, l, features;
         extern void get_cpu_vendor(struct cpuinfo_x86*);
 
+ /* Disabled by DMI scan or kernel option? */
+ if (dont_enable_local_apic)
+ return -1;
+
         /* Workaround for us being called before identify_cpu(). */
         get_cpu_vendor(&boot_cpu_data);
 
@@ -894,8 +899,14 @@
 
 static unsigned int calibration_result;
 
+int dont_use_local_apic_timer __initdata = 0;
+
 void __init setup_APIC_clocks (void)
 {
+ /* Disabled by DMI scan or kernel option? */
+ if (dont_use_local_apic_timer)
+ return;
+
         printk("Using local APIC timer interrupts.\n");
         using_apic_timer = 1;
 
--- linux-2.4.17-init-order/arch/i386/kernel/dmi_scan.c.~1~ Fri Dec 21 23:19:54 2001
+++ linux-2.4.17-init-order/arch/i386/kernel/dmi_scan.c Mon Dec 24 11:38:21 2001
@@ -9,6 +9,7 @@
 #include <linux/pm.h>
 #include <asm/keyboard.h>
 #include <asm/system.h>
+#include <linux/bootmem.h>
 
 unsigned long dmi_broken;
 int is_sony_vaio_laptop;
@@ -51,7 +52,7 @@
         u8 *data;
         int i=1;
                 
- buf = ioremap(base, len);
+ buf = bt_ioremap(base, len);
         if(buf==NULL)
                 return -1;
 
@@ -83,7 +84,7 @@
                 data+=2;
                 i++;
         }
- iounmap(buf);
+ bt_iounmap(buf, len);
         return 0;
 }
 
@@ -155,7 +156,7 @@
                 return;
         if (dmi_ident[slot])
                 return;
- dmi_ident[slot] = kmalloc(strlen(p)+1, GFP_KERNEL);
+ dmi_ident[slot] = alloc_bootmem(strlen(p)+1);
         if(dmi_ident[slot])
                 strcpy(dmi_ident[slot], p);
         else
@@ -414,6 +415,43 @@
         return 0;
 }
 
+/*
+ * Some machines, usually laptops, can't handle an enabled local APIC.
+ * The symptoms include hangs or reboots when suspending or resuming,
+ * attaching or detaching the power cord, or entering BIOS setup screens
+ * through magic key sequences.
+ */
+static int __init local_apic_kills_bios(struct dmi_blacklist *d)
+{
+#ifdef CONFIG_X86_LOCAL_APIC
+ extern int dont_enable_local_apic;
+ if (!dont_enable_local_apic) {
+ dont_enable_local_apic = 1;
+ printk(KERN_WARNING "%s with broken BIOS detected. "
+ "Refusing to enable the local APIC.\n",
+ d->ident);
+ }
+#endif
+ return 0;
+}
+
+/*
+ * The Intel AL440LX mainboard will hang randomly if the local APIC
+ * timer is running and the APM BIOS hasn't been disabled.
+ */
+static int __init apm_kills_local_apic_timer(struct dmi_blacklist *d)
+{
+#ifdef CONFIG_X86_LOCAL_APIC
+ extern int dont_use_local_apic_timer;
+ if (apm_info.bios.version && !dont_use_local_apic_timer) {
+ dont_use_local_apic_timer = 1;
+ printk(KERN_WARNING "%s with broken BIOS detected. "
+ "The local APIC timer will not be used.\n",
+ d->ident);
+ }
+#endif
+ return 0;
+}
 
 /*
  * Simple "print if true" callback
@@ -558,6 +596,25 @@
                         MATCH(DMI_BIOS_DATE, "09/12/00"), NO_MATCH
                         } },
 
+ /* Machines which have problems handling enabled local APICs */
+
+ { local_apic_kills_bios, "Dell Inspiron", {
+ MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+ MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+ NO_MATCH, NO_MATCH
+ } },
+
+ { local_apic_kills_bios, "Dell Latitude", {
+ MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+ MATCH(DMI_PRODUCT_NAME, "Latitude"),
+ NO_MATCH, NO_MATCH
+ } },
+
+ { apm_kills_local_apic_timer, "Intel AL440LX", {
+ MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
+ MATCH(DMI_BOARD_NAME, "AL440LX"),
+ NO_MATCH, NO_MATCH } },
+
         /* Problem Intel 440GX bioses */
 
         { broken_pirq, "SABR1 Bios", { /* Bad $PIR */
@@ -726,12 +783,10 @@
         }
 }
 
-static int __init dmi_scan_machine(void)
+int __init dmi_scan_machine(void)
 {
         int err = dmi_iterate(dmi_decode);
         if(err == 0)
                 dmi_check_blacklist();
         return err;
 }
-
-module_init(dmi_scan_machine);
--- linux-2.4.17-init-order/arch/i386/kernel/setup.c.~1~ Fri Dec 21 23:19:54 2001
+++ linux-2.4.17-init-order/arch/i386/kernel/setup.c Sun Dec 23 18:51:40 2001
@@ -992,7 +992,6 @@
          */
         if (smp_found_config)
                 get_smp_config();
- init_apic_mappings();
 #endif
 
 
@@ -1044,6 +1043,7 @@
         conswitchp = &dummy_con;
 #endif
 #endif
+ dmi_scan_machine();
 }
 
 static int cachesize_override __initdata = -1;
--- linux-2.4.17-init-order/arch/i386/kernel/traps.c.~1~ Thu Oct 11 13:34:39 2001
+++ linux-2.4.17-init-order/arch/i386/kernel/traps.c Sun Dec 23 18:51:40 2001
@@ -920,6 +920,10 @@
                 EISA_bus = 1;
 #endif
 
+#ifdef CONFIG_X86_LOCAL_APIC
+ init_apic_mappings();
+#endif
+
         set_trap_gate(0,&divide_error);
         set_trap_gate(1,&debug);
         set_intr_gate(2,&nmi);
--- linux-2.4.17-init-order/arch/i386/mm/init.c.~1~ Fri Dec 21 23:19:54 2001
+++ linux-2.4.17-init-order/arch/i386/mm/init.c Sun Dec 23 18:51:40 2001
@@ -128,7 +128,6 @@
 static inline void set_pte_phys (unsigned long vaddr,
                         unsigned long phys, pgprot_t flags)
 {
- pgprot_t prot;
         pgd_t *pgd;
         pmd_t *pmd;
         pte_t *pte;
@@ -144,10 +143,8 @@
                 return;
         }
         pte = pte_offset(pmd, vaddr);
- if (pte_val(*pte))
- pte_ERROR(*pte);
- pgprot_val(prot) = pgprot_val(PAGE_KERNEL) | pgprot_val(flags);
- set_pte(pte, mk_pte_phys(phys, prot));
+ /* <phys,flags> stored as-is, to permit clearing entries */
+ set_pte(pte, mk_pte_phys(phys, flags));
 
         /*
          * It's enough to flush this one mapping.
--- linux-2.4.17-init-order/arch/i386/mm/ioremap.c.~1~ Fri Mar 30 19:05:04 2001
+++ linux-2.4.17-init-order/arch/i386/mm/ioremap.c Mon Dec 24 11:42:12 2001
@@ -161,3 +161,69 @@
         if (addr > high_memory)
                 return vfree((void *) (PAGE_MASK & (unsigned long) addr));
 }
+
+#include <asm/fixmap.h>
+void __init *bt_ioremap(unsigned long phys_addr, unsigned long size)
+{
+ unsigned long offset, last_addr;
+ unsigned int nrpages;
+ enum fixed_addresses idx;
+
+ /* Don't allow wraparound or zero size */
+ last_addr = phys_addr + size - 1;
+ if (!size || last_addr < phys_addr)
+ return NULL;
+
+ /*
+ * Don't remap the low PCI/ISA area, it's always mapped..
+ */
+ if (phys_addr >= 0xA0000 && last_addr < 0x100000)
+ return phys_to_virt(phys_addr);
+
+ /*
+ * Mappings have to be page-aligned
+ */
+ offset = phys_addr & ~PAGE_MASK;
+ phys_addr &= PAGE_MASK;
+ size = PAGE_ALIGN(last_addr) - phys_addr;
+
+ /*
+ * Mappings have to fit in the FIX_BTMAP area.
+ */
+ nrpages = size >> PAGE_SHIFT;
+ if (nrpages > NR_FIX_BTMAPS)
+ return NULL;
+
+ /*
+ * Ok, go for it..
+ */
+ idx = FIX_BTMAP_BEGIN;
+ while (nrpages > 0) {
+ set_fixmap(idx, phys_addr);
+ phys_addr += PAGE_SIZE;
+ --idx;
+ --nrpages;
+ }
+ return (void*) (offset + fix_to_virt(FIX_BTMAP_BEGIN));
+}
+
+void __init bt_iounmap(void *addr, unsigned long size)
+{
+ unsigned long virt_addr;
+ unsigned long offset;
+ unsigned int nrpages;
+ enum fixed_addresses idx;
+
+ virt_addr = (unsigned long)addr;
+ if (virt_addr < fix_to_virt(FIX_BTMAP_BEGIN))
+ return;
+ offset = virt_addr & ~PAGE_MASK;
+ nrpages = PAGE_ALIGN(offset + size - 1) >> PAGE_SHIFT;
+
+ idx = FIX_BTMAP_BEGIN;
+ while (nrpages > 0) {
+ __set_fixmap(idx, 0, __pgprot(0));
+ --idx;
+ --nrpages;
+ }
+}
--- linux-2.4.17-init-order/include/asm-i386/fixmap.h.~1~ Sun Aug 12 11:35:53 2001
+++ linux-2.4.17-init-order/include/asm-i386/fixmap.h Sun Dec 23 18:51:41 2001
@@ -65,6 +65,11 @@
         FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
         FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
 #endif
+ __end_of_permanent_fixed_addresses,
+ /* temporary boot-time mappings, used before ioremap() is functional */
+#define NR_FIX_BTMAPS 16
+ FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
+ FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1,
         __end_of_fixed_addresses
 };
 
@@ -86,8 +91,8 @@
  * at the top of mem..
  */
 #define FIXADDR_TOP (0xffffe000UL)
-#define FIXADDR_SIZE (__end_of_fixed_addresses << PAGE_SHIFT)
-#define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
+#define __FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
+#define FIXADDR_START (FIXADDR_TOP - __FIXADDR_SIZE)
 
 #define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
 
--- linux-2.4.17-init-order/include/asm-i386/io.h.~1~ Tue Nov 6 12:12:07 2001
+++ linux-2.4.17-init-order/include/asm-i386/io.h Mon Dec 24 11:36:35 2001
@@ -98,6 +98,14 @@
 extern void iounmap(void *addr);
 
 /*
+ * bt_ioremap() and bt_iounmap() are for temporary early boot-time
+ * mappings, before the real ioremap() is functional.
+ * A boot-time mapping is currently limited to at most 16 pages.
+ */
+extern void *bt_ioremap(unsigned long offset, unsigned long size);
+extern void bt_iounmap(void *addr, unsigned long size);
+
+/*
  * IO bus memory addresses are also 1:1 with the physical address
  */
 #define virt_to_bus virt_to_phys
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Mon Jan 07 2002 - 21:00:14 EST