Since starting to use -current with ACPI on a Sony C1 laptop, I
noticed that after resume, occasionally IRQ 9 would get stuck and
not deliver any interrupts. IRQ 9 is shared by sound, USB and the
pccard slot. It turned out that something was not saving the ELCR
edge/level control registers in the PCI-ISA bridge, so on resume
IRQ 9 was configured in edge-triggered mode, making interrupt loss
inevitable.

The patch below makes the "isab" driver save and restore the ELCR
around suspends on the Intel 82371AB. Any comments on whether this
is the right way or the right place to solve the problem?

Ian

Index: isa_pci.c
===================================================================
RCS file: /dump/FreeBSD-CVS/src/sys/dev/pci/isa_pci.c,v
retrieving revision 1.6
diff -u -r1.6 isa_pci.c
--- isa_pci.c   21 Dec 2001 01:28:46 -0000      1.6
+++ isa_pci.c   29 Oct 2002 01:01:33 -0000
@@ -37,21 +37,36 @@
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/kernel.h>
+#include <machine/bus.h>
+#include <machine/resource.h>
 #include <sys/bus.h>
+#include <sys/rman.h>
 
 #include <pci/pcivar.h>
 #include <pci/pcireg.h>
 
+#define        ELCR_IOADDR     0x4d0   /* Interrupt Edge/Level Control Registers */
+#define        ELCR_IOLEN      2
+
+struct isab_softc {
+    struct resource *elcr_res;
+    u_char saved_elcr[ELCR_IOLEN];
+};
+
 static int     isab_probe(device_t dev);
 static int     isab_attach(device_t dev);
+static int     isab_detach(device_t dev);
+static int     isab_resume(device_t dev);
+static int     isab_suspend(device_t dev);
 
 static device_method_t isab_methods[] = {
     /* Device interface */
     DEVMETHOD(device_probe,            isab_probe),
     DEVMETHOD(device_attach,           isab_attach),
+    DEVMETHOD(device_detach,           isab_detach),
     DEVMETHOD(device_shutdown,         bus_generic_shutdown),
-    DEVMETHOD(device_suspend,          bus_generic_suspend),
-    DEVMETHOD(device_resume,           bus_generic_resume),
+    DEVMETHOD(device_suspend,          isab_suspend),
+    DEVMETHOD(device_resume,           isab_resume),
 
     /* Bus interface */
     DEVMETHOD(bus_print_child,         bus_generic_print_child),
@@ -68,7 +83,7 @@
 static driver_t isab_driver = {
     "isab",
     isab_methods,
-    0,
+    sizeof(struct isab_softc),
 };
 
 static devclass_t isab_devclass;
@@ -143,14 +158,82 @@
 isab_attach(device_t dev)
 {
     device_t   child;
+    struct isab_softc *sc = device_get_softc(dev);
+    int error, rid;
 
     /*
      * Attach an ISA bus.  Note that we can only have one ISA bus.
      */
     child = device_add_child(dev, "isa", 0);
-    if (child != NULL)
-       return(bus_generic_attach(dev));
+    if (child != NULL) {
+       error = bus_generic_attach(dev);
+       if (error)
+            return (error);
+    }
+
+    switch (pci_get_devid(dev)) {
+    case 0x71108086: /* Intel 82371AB */
+       /*
+        * Sometimes the ELCR (Edge/Level Control Register) is not restored
+        * correctly on resume by the BIOS, so we handle it ourselves.
+        */
+       rid = 0;
+       bus_set_resource(dev, SYS_RES_IOPORT, rid, ELCR_IOADDR, ELCR_IOLEN);
+       sc->elcr_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1,
+           RF_ACTIVE);
+       if (sc->elcr_res == NULL)
+           device_printf(dev, "failed to allocate ELCR resource\n");
+        break;
+    }
 
     return(0);
 }
 
+static int
+isab_detach(device_t dev)
+{
+    struct isab_softc *sc = device_get_softc(dev);
+
+    if (sc->elcr_res != NULL)
+       bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->elcr_res);
+
+     return (bus_generic_detach(dev));
+}
+
+static int
+isab_suspend(device_t dev)
+{
+    struct isab_softc *sc = device_get_softc(dev);
+    bus_space_tag_t bst;
+    bus_space_handle_t bsh;
+    int i;
+
+    /* Save the ELCR if required. */
+    if (sc->elcr_res != NULL) {
+       bst = rman_get_bustag(sc->elcr_res);
+       bsh = rman_get_bushandle(sc->elcr_res);
+       for (i = 0; i < ELCR_IOLEN; i++)
+           sc->saved_elcr[i] = bus_space_read_1(bst, bsh, i);
+    }
+
+    return (bus_generic_suspend(dev));
+}
+
+static int
+isab_resume(device_t dev)
+{
+    struct isab_softc *sc = device_get_softc(dev);
+    bus_space_tag_t bst;
+    bus_space_handle_t bsh;
+    int i;
+
+    /* Restore the ELCR if required. */
+    if (sc->elcr_res != NULL) {
+       bst = rman_get_bustag(sc->elcr_res);
+       bsh = rman_get_bushandle(sc->elcr_res);
+       for (i = 0; i < ELCR_IOLEN; i++)
+           bus_space_write_1(bst, bsh, i, sc->saved_elcr[i]);
+    }
+
+    return (bus_generic_resume(dev));
+}


To Unsubscribe: send mail to [EMAIL PROTECTED]
with "unsubscribe freebsd-current" in the body of the message

Reply via email to