aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Paul <wpaul@FreeBSD.org>1998-12-31 17:19:21 +0000
committerBill Paul <wpaul@FreeBSD.org>1998-12-31 17:19:21 +0000
commitd1b5b058f7b10ab566b60dd35ba2de7d895c87f6 (patch)
tree97f9803ed7d2be593a5747c3724a08aead123386
parent56f7a84010f27e82abb3d22633c390587ec73972 (diff)
Notes
-rw-r--r--sys/pci/if_pn.c182
-rw-r--r--sys/pci/if_pnreg.h10
2 files changed, 179 insertions, 13 deletions
diff --git a/sys/pci/if_pn.c b/sys/pci/if_pn.c
index f0b7577612df..843d61575dee 100644
--- a/sys/pci/if_pn.c
+++ b/sys/pci/if_pn.c
@@ -29,7 +29,7 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
- * $Id: if_pn.c,v 1.3 1998/12/07 21:58:46 archie Exp $
+ * $Id: if_pn.c,v 1.35 1998/12/31 16:51:01 wpaul Exp $
*/
/*
@@ -91,11 +91,13 @@
/* #define PN_BACKGROUND_AUTONEG */
+#define PN_PROMISC_BUG_WAR
+
#include <pci/if_pnreg.h>
#ifndef lint
static const char rcsid[] =
- "$Id: if_pn.c,v 1.3 1998/12/07 21:58:46 archie Exp $";
+ "$Id: if_pn.c,v 1.35 1998/12/31 16:51:01 wpaul Exp $";
#endif
/*
@@ -132,6 +134,10 @@ static int pn_newbuf __P((struct pn_softc *,
static int pn_encap __P((struct pn_softc *, struct pn_chain *,
struct mbuf *));
+#ifdef PN_PROMISC_BUG_WAR
+static void pn_promisc_bug_war __P((struct pn_softc *,
+ struct pn_chain_onefrag *));
+#endif
static void pn_rxeof __P((struct pn_softc *));
static void pn_rxeoc __P((struct pn_softc *));
static void pn_txeof __P((struct pn_softc *));
@@ -785,6 +791,9 @@ pn_attach(config_id, unit)
caddr_t roundptr;
struct pn_type *p;
u_int16_t phy_vid, phy_did, phy_sts;
+#ifdef PN_PROMISC_BUG_WAR
+ u_int32_t revision = 0;
+#endif
s = splimp();
@@ -886,7 +895,7 @@ pn_attach(config_id, unit)
if (sc->pn_ldata_ptr == NULL) {
free(sc, M_DEVBUF);
printf("pn%d: no memory for list buffers!\n", unit);
- return;
+ goto fail;
}
sc->pn_ldata = (struct pn_list_data *)sc->pn_ldata_ptr;
@@ -902,6 +911,20 @@ pn_attach(config_id, unit)
sc->pn_ldata = (struct pn_list_data *)roundptr;
bzero(sc->pn_ldata, sizeof(struct pn_list_data));
+#ifdef PN_PROMISC_BUG_WAR
+ revision = pci_conf_read(config_id, PN_PCI_REVISION) & 0x000000FF;
+ if (revision == PN_169B_REV) {
+ sc->pn_promisc_war = 1;
+ sc->pn_promisc_buf = malloc(PN_RXLEN * 5, M_DEVBUF, M_NOWAIT);
+ if (sc->pn_promisc_buf == NULL) {
+ printf("pn%d: no memory for workaround buffer\n", unit);
+ goto fail;
+ }
+ } else {
+ sc->pn_promisc_war = 0;
+ }
+#endif
+
ifp = &sc->arpcom.ac_if;
ifp->if_softc = sc;
ifp->if_unit = unit;
@@ -1078,14 +1101,130 @@ static int pn_newbuf(sc, c)
return(ENOBUFS);
}
+ /*
+ * Zero the buffer. This is part of the workaround for the
+ * promiscuous mode bug in the revision 33 PNIC chips.
+ */
+ bzero((char *)mtod(m_new, char *), MCLBYTES);
+ m_new->m_len = m_new->m_pkthdr.len = MCLBYTES;
+
c->pn_mbuf = m_new;
c->pn_ptr->pn_status = PN_RXSTAT;
c->pn_ptr->pn_data = vtophys(mtod(m_new, caddr_t));
- c->pn_ptr->pn_ctl = PN_RXCTL_RLINK | (MCLBYTES - 1);
+ c->pn_ptr->pn_ctl = PN_RXCTL_RLINK | PN_RXLEN;
return(0);
}
+#ifdef PN_PROMISC_BUG_WAR
+/*
+ * Grrrrr.
+ * Revision 33 of the PNIC chip has a terrible bug in it that manifests
+ * itself when you enable promiscuous mode. Sometimes instead of uploading
+ * one complete frame, it uploads its entire FIFO memory. The frame we
+ * want is at the end of this whole mess, but we never know exactly
+ * how much data has been uploaded, so finding it can be hard.
+ *
+ * There is only one way to do it reliably, and it's disgusting.
+ * Here's what we know:
+ *
+ * - We know there will always be somewhere between one and three extra
+ * descriptors uploaded.
+ *
+ * - We know the desired received frame will always be at the end of the
+ * total data upload.
+ *
+ * - We know the size of the desired received frame because it will be
+ * provided in the length field of the status word in the last descriptor.
+ *
+ * Here's what we do:
+ *
+ * - When we allocate buffers for the receive ring, we bzero() them.
+ * This means that we know that the buffer contents should be all
+ * zeros, except for data uploaded by the chip.
+ *
+ * - We also force the PNIC chip to upload frames that include the
+ * ethernet CRC at the end.
+ *
+ * - We gather all of the bogus frame data into a single buffer.
+ *
+ * - We then position a pointer at the end of this buffer and scan
+ * backwards until we encounter the first non-zero byte of data.
+ * This is the end of the received frame. We know we will encounter
+ * some data at the end of the frame because the CRC will always be
+ * there, so even if the sender transmits a packet of all zeros,
+ * we won't be fooled.
+ *
+ * - We know the size of the actual received frame, so we subtract
+ * that value from the current pointer location. This brings us
+ * to the start of the actual received packet.
+ *
+ * - We copy this into an mbuf and pass it on, along with the actual
+ * frame length.
+ *
+ * The performance hit is tremendous, but it beats dropping frames all
+ * the time.
+ */
+
+#define PN_WHOLEFRAME (PN_RXSTAT_FIRSTFRAG|PN_RXSTAT_LASTFRAG)
+static void pn_promisc_bug_war(sc, cur_rx)
+ struct pn_softc *sc;
+ struct pn_chain_onefrag *cur_rx;
+{
+ struct pn_chain_onefrag *c;
+ unsigned char *ptr;
+ int total_len;
+ u_int32_t rxstat = 0;
+
+ c = sc->pn_promisc_bug_save;
+ ptr = sc->pn_promisc_buf;
+ bzero(ptr, sizeof(PN_RXLEN * 5));
+
+ /* Copy all the bytes from the bogus buffers. */
+ while ((c->pn_ptr->pn_status & PN_WHOLEFRAME) != PN_WHOLEFRAME) {
+ rxstat = c->pn_ptr->pn_status;
+ m_copydata(c->pn_mbuf, 0, PN_RXLEN, ptr);
+ ptr += PN_RXLEN - 2; /* round down to 32-bit boundary */
+ if (c == cur_rx)
+ break;
+ if (rxstat & PN_RXSTAT_LASTFRAG)
+ break;
+ c->pn_ptr->pn_status = PN_RXSTAT;
+ c->pn_ptr->pn_ctl = PN_RXCTL_RLINK | PN_RXLEN;
+ bzero((char *)mtod(c->pn_mbuf, char *), MCLBYTES);
+ c = c->pn_nextdesc;
+ }
+
+
+ /* Find the length of the actual receive frame. */
+ total_len = PN_RXBYTES(rxstat);
+
+ /* Scan backwards until we hit a non-zero byte. */
+ while(*ptr == 0x00) {
+ ptr--;
+ }
+
+ if ((u_int32_t)(ptr) & 0x3)
+ ptr -= 1;
+
+ /* Now find the start of the frame. */
+ ptr -= total_len;
+ if (ptr < sc->pn_promisc_buf)
+ ptr = sc->pn_promisc_buf;
+
+ /*
+ * Now copy the salvaged frame to the last mbuf and fake up
+ * the status word to make it look like a successful
+ * frame reception.
+ */
+ m_copyback(cur_rx->pn_mbuf, 0, total_len, ptr);
+ cur_rx->pn_mbuf->m_len = c->pn_mbuf->m_pkthdr.len = MCLBYTES;
+ cur_rx->pn_ptr->pn_status |= PN_RXSTAT_FIRSTFRAG;
+
+ return;
+}
+#endif
+
/*
* A frame has been uploaded: pass the resulting mbuf chain up to
* the higher level protocols.
@@ -1107,6 +1246,24 @@ static void pn_rxeof(sc)
cur_rx = sc->pn_cdata.pn_rx_head;
sc->pn_cdata.pn_rx_head = cur_rx->pn_nextdesc;
+#ifdef PN_PROMISC_BUG_WAR
+ /*
+ * XXX The PNIC seems to have a bug that manifests
+ * when the promiscuous mode bit is set: we have to
+ * watch for it and work around it.
+ */
+ if (sc->pn_promisc_war && ifp->if_flags & IFF_PROMISC) {
+ if ((rxstat & PN_WHOLEFRAME) != PN_WHOLEFRAME) {
+ if (rxstat & PN_RXSTAT_FIRSTFRAG)
+ sc->pn_promisc_bug_save = cur_rx;
+ if ((rxstat & PN_RXSTAT_LASTFRAG) == 0)
+ continue;
+ pn_promisc_bug_war(sc, cur_rx);
+ rxstat = cur_rx->pn_ptr->pn_status;
+ }
+ }
+#endif
+
/*
* If an error occurs, update stats, clear the
* status word and leave the mbuf cluster in place:
@@ -1118,8 +1275,8 @@ static void pn_rxeof(sc)
if (rxstat & PN_RXSTAT_COLLSEEN)
ifp->if_collisions++;
cur_rx->pn_ptr->pn_status = PN_RXSTAT;
- cur_rx->pn_ptr->pn_ctl =
- PN_RXCTL_RLINK | (MCLBYTES - 1);
+ cur_rx->pn_ptr->pn_ctl = PN_RXCTL_RLINK | PN_RXLEN;
+ bzero((char *)mtod(cur_rx->pn_mbuf, char *), MCLBYTES);
continue;
}
@@ -1127,6 +1284,9 @@ static void pn_rxeof(sc)
m = cur_rx->pn_mbuf;
total_len = PN_RXBYTES(cur_rx->pn_ptr->pn_status);
+ /* Trim off the CRC. */
+ total_len -= ETHER_CRC_LEN;
+
/*
* Try to conjure up a new mbuf cluster. If that
* fails, it means we have an out of memory condition and
@@ -1136,10 +1296,9 @@ static void pn_rxeof(sc)
*/
if (pn_newbuf(sc, cur_rx) == ENOBUFS) {
ifp->if_ierrors++;
- cur_rx->pn_ptr->pn_status =
- PN_RXSTAT_FIRSTFRAG|PN_RXSTAT_LASTFRAG;
- cur_rx->pn_ptr->pn_ctl =
- PN_RXCTL_RLINK | (MCLBYTES - 1);
+ cur_rx->pn_ptr->pn_status = PN_RXSTAT;
+ cur_rx->pn_ptr->pn_ctl = PN_RXCTL_RLINK | PN_RXLEN;
+ bzero((char *)mtod(cur_rx->pn_mbuf, char *), MCLBYTES);
continue;
}
@@ -1300,7 +1459,6 @@ static void pn_intr(arg)
if ((status & PN_INTRS) == 0)
break;
-
if (status & PN_ISR_RX_OK)
pn_rxeof(sc);
@@ -1550,7 +1708,7 @@ static void pn_init(xsc)
CSR_WRITE_4(sc, PN_BUSCTL, PN_BUSCTL_CONFIG);
PN_CLRBIT(sc, PN_NETCFG, PN_NETCFG_TX_IMMEDIATE);
- PN_SETBIT(sc, PN_NETCFG, PN_NETCFG_NO_RXCRC);
+ PN_CLRBIT(sc, PN_NETCFG, PN_NETCFG_NO_RXCRC);
PN_CLRBIT(sc, PN_NETCFG, PN_NETCFG_HEARTBEAT);
PN_CLRBIT(sc, PN_NETCFG, PN_NETCFG_STORENFWD);
PN_CLRBIT(sc, PN_NETCFG, PN_NETCFG_TX_BACKOFF);
diff --git a/sys/pci/if_pnreg.h b/sys/pci/if_pnreg.h
index 37ae2368bddd..b37da2dd692a 100644
--- a/sys/pci/if_pnreg.h
+++ b/sys/pci/if_pnreg.h
@@ -29,7 +29,7 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
- * $Id: if_pnreg.h,v 1.1 1998/12/04 18:01:21 wpaul Exp $
+ * $Id: if_pnreg.h,v 1.15 1998/12/31 16:51:01 wpaul Exp $
*/
/*
@@ -350,6 +350,7 @@ struct pn_desc {
#define PN_TX_LIST_CNT 64
#define PN_MIN_FRAMELEN 60
#define PN_FRAMELEN 1536
+#define PN_RXLEN 1518
/*
* A tx 'super descriptor' is actually 16 regular descriptors
@@ -440,6 +441,12 @@ struct pn_softc {
u_int8_t pn_want_auto;
u_int8_t pn_autoneg;
caddr_t pn_ldata_ptr;
+#ifdef PN_PROMISC_BUG_WAR
+#define PN_169B_REV 33
+ u_int8_t pn_promisc_war;
+ struct pn_chain_onefrag *pn_promisc_bug_save;
+ unsigned char *pn_promisc_buf;
+#endif
struct pn_list_data *pn_ldata;
struct pn_chain_data pn_cdata;
};
@@ -516,6 +523,7 @@ struct pn_softc {
#define PN_PCI_DEVICE_ID 0x02
#define PN_PCI_COMMAND 0x04
#define PN_PCI_STATUS 0x06
+#define PN_PCI_REVISION 0x08
#define PN_PCI_CLASSCODE 0x09
#define PN_PCI_LATENCY_TIMER 0x0D
#define PN_PCI_HEADER_TYPE 0x0E