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>