emx: Fix "Missing Interrupt Following ICR read" errata
authorSepherosa Ziehau <sephe@dragonflybsd.org>
Sat, 7 Jul 2012 05:24:52 +0000 (13:24 +0800)
committerSepherosa Ziehau <sephe@dragonflybsd.org>
Sat, 7 Jul 2012 05:50:11 +0000 (13:50 +0800)
IMS should be set to 0 before reading ICR.  It should be noted that
once IMS is 0, ICR.INT_ASSERTED will not be set, which is not mentioned
in the datasheet.

This errata exists on 82571, 82572, 82573, 82574 and 82583, while 82583
is not covered by emx(4).  And the fix is needed only if the legacy
interrupt is used and the interrupt line is shared with other devices.

hw.emxX.irq.unshared tunable is added to give a hint to the driver that
the legacy interrupt is not shared with other devices.

sys/dev/netif/emx/if_emx.c
sys/dev/netif/emx/if_emx.h

index 4e24936..0814cf8 100644 (file)
@@ -201,6 +201,8 @@ static void emx_serialize_assert(struct ifnet *, enum ifnet_serialize,
 #endif
 
 static void    emx_intr(void *);
+static void    emx_intr_mask(void *);
+static void    emx_intr_body(struct emx_softc *, boolean_t);
 static void    emx_rxeof(struct emx_softc *, int, int);
 static void    emx_txeof(struct emx_softc *);
 static void    emx_tx_collect(struct emx_softc *);
@@ -416,6 +418,7 @@ emx_attach(device_t dev)
        int error = 0, i, throttle, msi_enable;
        u_int intr_flags;
        uint16_t eeprom_data, device_id, apme_mask;
+       driver_intr_t *intr_func;
 
        lwkt_serialize_init(&sc->main_serialize);
        lwkt_serialize_init(&sc->tx_serialize);
@@ -481,6 +484,21 @@ emx_attach(device_t dev)
        sc->intr_type = pci_alloc_1intr(dev, msi_enable,
            &sc->intr_rid, &intr_flags);
 
+       if (sc->intr_type == PCI_INTR_TYPE_LEGACY) {
+               int unshared;
+
+               unshared = device_getenv_int(dev, "irq.unshared", 0);
+               if (!unshared) {
+                       sc->flags |= EMX_FLAG_SHARED_INTR;
+                       if (bootverbose)
+                               device_printf(dev, "IRQ shared\n");
+               } else {
+                       intr_flags &= ~RF_SHAREABLE;
+                       if (bootverbose)
+                               device_printf(dev, "IRQ unshared\n");
+               }
+       }
+
        sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid,
            intr_flags);
        if (sc->intr_res == NULL) {
@@ -703,7 +721,22 @@ emx_attach(device_t dev)
        if (sc->has_manage && !sc->has_amt)
                emx_get_hw_control(sc);
 
-       error = bus_setup_intr(dev, sc->intr_res, INTR_MPSAFE, emx_intr, sc,
+       /*
+        * Missing Interrupt Following ICR read:
+        *
+        * 82571/82572 specification update #76
+        * 82573 specification update #31
+        * 82574 specification update #12
+        */
+       intr_func = emx_intr;
+       if ((sc->flags & EMX_FLAG_SHARED_INTR) &&
+           (sc->hw.mac.type == e1000_82571 ||
+            sc->hw.mac.type == e1000_82572 ||
+            sc->hw.mac.type == e1000_82573 ||
+            sc->hw.mac.type == e1000_82574))
+               intr_func = emx_intr_mask;
+
+       error = bus_setup_intr(dev, sc->intr_res, INTR_MPSAFE, intr_func, sc,
                               &sc->intr_tag, &sc->main_serialize);
        if (error) {
                device_printf(dev, "Failed to register interrupt handler");
@@ -1190,7 +1223,12 @@ emx_init(void *xsc)
 static void
 emx_intr(void *xsc)
 {
-       struct emx_softc *sc = xsc;
+       emx_intr_body(xsc, TRUE);
+}
+
+static void
+emx_intr_body(struct emx_softc *sc, boolean_t chk_asserted)
+{
        struct ifnet *ifp = &sc->arpcom.ac_if;
        uint32_t reg_icr;
 
@@ -1199,7 +1237,7 @@ emx_intr(void *xsc)
 
        reg_icr = E1000_READ_REG(&sc->hw, E1000_ICR);
 
-       if ((reg_icr & E1000_ICR_INT_ASSERTED) == 0) {
+       if (chk_asserted && (reg_icr & E1000_ICR_INT_ASSERTED) == 0) {
                logif(intr_end);
                return;
        }
@@ -1259,6 +1297,21 @@ emx_intr(void *xsc)
        logif(intr_end);
 }
 
+static void
+emx_intr_mask(void *xsc)
+{
+       struct emx_softc *sc = xsc;
+
+       E1000_WRITE_REG(&sc->hw, E1000_IMC, 0xffffffff);
+       /*
+        * NOTE:
+        * ICR.INT_ASSERTED bit will never be set if IMS is 0,
+        * so don't check it.
+        */
+       emx_intr_body(sc, FALSE);
+       E1000_WRITE_REG(&sc->hw, E1000_IMS, IMS_ENABLE_MASK);
+}
+
 static void
 emx_media_status(struct ifnet *ifp, struct ifmediareq *ifmr)
 {
index 0af06cb..5f97d97 100644 (file)
@@ -235,6 +235,8 @@ struct emx_rxdata {
 struct emx_softc {
        struct arpcom           arpcom;
        struct e1000_hw         hw;
+       int                     flags;
+#define EMX_FLAG_SHARED_INTR   0x1
 
        /* DragonFly operating-system-specific structures. */
        struct e1000_osdep      osdep;