Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 29 Oct 2002 01:26:36 +0000
From:      Ian Dowse <iedowse@maths.tcd.ie>
To:        current@freebsd.org
Subject:   ELCR in PCI-ISA bridge not getting set on resume
Message-ID:   <200210290126.aa73484@salmon.maths.tcd.ie>

next in thread | raw e-mail | index | archive | help

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 majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi? <200210290126.aa73484>