From e0b35c1fa14e0f1ba0d18b346ebd12f26aeec3a8 Mon Sep 17 00:00:00 2001 From: Sepherosa Ziehau Date: Mon, 2 Jul 2012 15:45:30 +0800 Subject: [PATCH] bge: Workaround "short DMA" bug on certain chips If controllers receive two back-to-back send BDs with less than or equal to 8 total bytes then the device may hang. The two back-to-back send BDs must be in the same frame for this failure to occur. Obtained-from: FreeBSD 214087 --- sys/dev/netif/bge/if_bge.c | 52 +++++++++++++++++++++++++++++++++-- sys/dev/netif/bge/if_bgereg.h | 1 + 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/sys/dev/netif/bge/if_bge.c b/sys/dev/netif/bge/if_bge.c index 21ea0f2f30..8bc47ccc10 100644 --- a/sys/dev/netif/bge/if_bge.c +++ b/sys/dev/netif/bge/if_bge.c @@ -290,6 +290,8 @@ static void bge_rxeof(struct bge_softc *); static void bge_tick(void *); static void bge_stats_update(struct bge_softc *); static void bge_stats_update_regs(struct bge_softc *); +static struct mbuf * + bge_defrag_shortdma(struct mbuf *); static int bge_encap(struct bge_softc *, struct mbuf **, uint32_t *); #ifdef DEVICE_POLLING @@ -2028,6 +2030,10 @@ bge_attach(device_t dev) misccfg == BGE_MISCCFG_BOARD_ID_5788M)) sc->bge_flags |= BGE_FLAG_5788; + /* BCM5755 or higher and BCM5906 have short DMA bug. */ + if (BGE_IS_5755_PLUS(sc) || sc->bge_asicrev == BGE_ASICREV_BCM5906) + sc->bge_flags |= BGE_FLAG_SHORTDMA; + /* * Set various quirk flags. */ @@ -3030,7 +3036,7 @@ bge_encap(struct bge_softc *sc, struct mbuf **m_head0, uint32_t *txidx) bus_dma_segment_t segs[BGE_NSEG_NEW]; bus_dmamap_t map; int error, maxsegs, nsegs, idx, i; - struct mbuf *m_head = *m_head0; + struct mbuf *m_head = *m_head0, *m_new; if (m_head->m_pkthdr.csum_flags) { if (m_head->m_pkthdr.csum_flags & CSUM_IP) @@ -3069,10 +3075,16 @@ bge_encap(struct bge_softc *sc, struct mbuf **m_head0, uint32_t *txidx) goto back; } + if ((sc->bge_flags & BGE_FLAG_SHORTDMA) && m_head->m_next != NULL) { + m_new = bge_defrag_shortdma(m_head); + if (m_new == NULL) { + error = ENOBUFS; + goto back; + } + *m_head0 = m_head = m_new; + } if (sc->bge_force_defrag && (sc->bge_flags & BGE_FLAG_PCIE) && m_head->m_next != NULL) { - struct mbuf *m_new; - /* * Forcefully defragment mbuf chain to overcome hardware * limitation which only support a single outstanding @@ -4334,3 +4346,37 @@ bge_get_eaddr(struct bge_softc *sc, uint8_t eaddr[]) } return (*func == NULL ? ENXIO : 0); } + +/* + * NOTE: 'm' is not freed upon failure + */ +struct mbuf * +bge_defrag_shortdma(struct mbuf *m) +{ + struct mbuf *n; + int found; + + /* + * If device receive two back-to-back send BDs with less than + * or equal to 8 total bytes then the device may hang. The two + * back-to-back send BDs must in the same frame for this failure + * to occur. Scan mbuf chains and see whether two back-to-back + * send BDs are there. If this is the case, allocate new mbuf + * and copy the frame to workaround the silicon bug. + */ + for (n = m, found = 0; n != NULL; n = n->m_next) { + if (n->m_len < 8) { + found++; + if (found > 1) + break; + continue; + } + found = 0; + } + + if (found > 1) + n = m_defrag(m, MB_DONTWAIT); + else + n = m; + return n; +} diff --git a/sys/dev/netif/bge/if_bgereg.h b/sys/dev/netif/bge/if_bgereg.h index cd103cf9bb..c18d5b8b0c 100644 --- a/sys/dev/netif/bge/if_bgereg.h +++ b/sys/dev/netif/bge/if_bgereg.h @@ -2526,6 +2526,7 @@ struct bge_softc { #define BGE_FLAG_RX_ALIGNBUG 0x00100000 #define BGE_FLAG_NO_EEPROM 0x10000000 #define BGE_FLAG_5788 0x20000000 +#define BGE_FLAG_SHORTDMA 0x40000000 uint32_t bge_chipid; uint32_t bge_asicrev; -- 2.41.0