Module Name: src
Committed By: martin
Date: Sun Oct 22 06:25:32 UTC 2023
Modified Files:
src/sys/dev/pci [netbsd-10]: if_rge.c
Log Message:
Pull up following revision(s) (requested by mrg in ticket #434):
sys/dev/pci/if_rge.c: revision 1.26
sys/dev/pci/if_rge.c: revision 1.28
rge(4): check for all errors in rx buffer allocation
should fix a crash seen by by Chavdar Ivanov reported on current-users.
move the rx and tx list clean up into their own functions, and call the
rx clean up function from the init function if something fails. this
should fix a potential leak in this case, and generally frees up memory
that won't be used without a successful init phase again.
slight application of 'static', much more could be done.
rge: properly handle mbuf allocation failures in rx interrupts
several changes that should fix crashes seen after an mbuf
allocation failure:
- create RX ring dma maps with BUS_DMA_ALLOCNOW, so that any
future bus_dmamap_load*() call will succeed. this avoids one
error case in rge_newbuf(), that similar cases in eg wm(4)
actually call panic for now (i think this idea can be copied
into wm(4) as well.)
- extract the RX descriptor set into a common function that
both rge_newbuf() and rge_rxeof() can both use. it's almost
identical to the old rge_discard_rxbuf() except it also sets
the rge_addr (this is needed for the newbuf case.)
- move the bus_dmamap_unload() into rge_newbuf(), so that the
existing mbuf will remain mapped until a new mbuf is allocated.
(this part is what should fix crashes seen by wiz and Chavdar,
as the unload follow by sync is what triggers the assert in
x86 bus_dma. without the assert, it will would have shortly
triggered a page fault.) remove the assignment to NULL for
the rxq mbuf pointer, it is required for reload.
- add a couple of missing if_statinc() calls.
tested on amd64 and arm64.
To generate a diff of this commit:
cvs rdiff -u -r1.24.4.2 -r1.24.4.3 src/sys/dev/pci/if_rge.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/dev/pci/if_rge.c
diff -u src/sys/dev/pci/if_rge.c:1.24.4.2 src/sys/dev/pci/if_rge.c:1.24.4.3
--- src/sys/dev/pci/if_rge.c:1.24.4.2 Sat Oct 14 06:59:43 2023
+++ src/sys/dev/pci/if_rge.c Sun Oct 22 06:25:32 2023
@@ -1,4 +1,4 @@
-/* $NetBSD: if_rge.c,v 1.24.4.2 2023/10/14 06:59:43 martin Exp $ */
+/* $NetBSD: if_rge.c,v 1.24.4.3 2023/10/22 06:25:32 martin Exp $ */
/* $OpenBSD: if_rge.c,v 1.9 2020/12/12 11:48:53 jan Exp $ */
/*
@@ -18,7 +18,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_rge.c,v 1.24.4.2 2023/10/14 06:59:43 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_rge.c,v 1.24.4.3 2023/10/22 06:25:32 martin Exp $");
#include <sys/types.h>
@@ -106,9 +106,10 @@ int rge_ifmedia_upd(struct ifnet *);
void rge_ifmedia_sts(struct ifnet *, struct ifmediareq *);
int rge_allocmem(struct rge_softc *);
int rge_newbuf(struct rge_softc *, int);
-void rge_discard_rxbuf(struct rge_softc *, int);
-int rge_rx_list_init(struct rge_softc *);
-void rge_tx_list_init(struct rge_softc *);
+static int rge_rx_list_init(struct rge_softc *);
+static void rge_rx_list_fini(struct rge_softc *);
+static void rge_tx_list_init(struct rge_softc *);
+static void rge_tx_list_fini(struct rge_softc *);
int rge_rxeof(struct rge_softc *);
int rge_txeof(struct rge_softc *);
void rge_reset(struct rge_softc *);
@@ -650,7 +651,7 @@ rge_init(struct ifnet *ifp)
{
struct rge_softc *sc = ifp->if_softc;
uint32_t val;
- int i;
+ unsigned i;
rge_stop(ifp, 0);
@@ -661,11 +662,12 @@ rge_init(struct ifnet *ifp)
RGE_WRITE_2(sc, RGE_RXMAXSIZE, RGE_JUMBO_FRAMELEN);
/* Initialize RX descriptors list. */
- if (rge_rx_list_init(sc) == ENOBUFS) {
+ int error = rge_rx_list_init(sc);
+ if (error != 0) {
device_printf(sc->sc_dev,
"init failed: no memory for RX buffers\n");
rge_stop(ifp, 1);
- return (ENOBUFS);
+ return error;
}
/* Initialize TX descriptors. */
@@ -836,7 +838,6 @@ void
rge_stop(struct ifnet *ifp, int disable)
{
struct rge_softc *sc = ifp->if_softc;
- int i;
callout_halt(&sc->sc_timeout, NULL);
@@ -867,25 +868,8 @@ rge_stop(struct ifnet *ifp, int disable)
sc->rge_head = sc->rge_tail = NULL;
}
- /* Free the TX list buffers. */
- for (i = 0; i < RGE_TX_LIST_CNT; i++) {
- if (sc->rge_ldata.rge_txq[i].txq_mbuf != NULL) {
- bus_dmamap_unload(sc->sc_dmat,
- sc->rge_ldata.rge_txq[i].txq_dmamap);
- m_freem(sc->rge_ldata.rge_txq[i].txq_mbuf);
- sc->rge_ldata.rge_txq[i].txq_mbuf = NULL;
- }
- }
-
- /* Free the RX list buffers. */
- for (i = 0; i < RGE_RX_LIST_CNT; i++) {
- if (sc->rge_ldata.rge_rxq[i].rxq_mbuf != NULL) {
- bus_dmamap_unload(sc->sc_dmat,
- sc->rge_ldata.rge_rxq[i].rxq_dmamap);
- m_freem(sc->rge_ldata.rge_rxq[i].rxq_mbuf);
- sc->rge_ldata.rge_rxq[i].rxq_mbuf = NULL;
- }
- }
+ rge_tx_list_fini(sc);
+ rge_rx_list_fini(sc);
}
/*
@@ -991,6 +975,9 @@ rge_ifmedia_sts(struct ifnet *ifp, struc
/*
* Allocate memory for RX/TX rings.
+ *
+ * XXX There is no tear-down for this if it any part fails, so everything
+ * remains allocated.
*/
int
rge_allocmem(struct rge_softc *sc)
@@ -1086,10 +1073,13 @@ rge_allocmem(struct rge_softc *sc)
return (error);
}
- /* Create DMA maps for RX buffers. */
+ /*
+ * Create DMA maps for RX buffers. Use BUS_DMA_ALLOCNOW to avoid any
+ * potential failure in bus_dmamap_load_mbuf() in the RX path.
+ */
for (i = 0; i < RGE_RX_LIST_CNT; i++) {
error = bus_dmamap_create(sc->sc_dmat, RGE_JUMBO_FRAMELEN, 1,
- RGE_JUMBO_FRAMELEN, 0, 0,
+ RGE_JUMBO_FRAMELEN, 0, BUS_DMA_ALLOCNOW,
&sc->rge_ldata.rge_rxq[i].rxq_dmamap);
if (error) {
aprint_error_dev(sc->sc_dev, "can't create DMA map for RX\n");
@@ -1101,15 +1091,39 @@ rge_allocmem(struct rge_softc *sc)
}
/*
+ * Set an RX descriptor and sync it.
+ */
+static void
+rge_load_rxbuf(struct rge_softc *sc, int idx)
+{
+ struct rge_rx_desc *r = &sc->rge_ldata.rge_rx_list[idx];
+ struct rge_rxq *rxq = &sc->rge_ldata.rge_rxq[idx];
+ bus_dmamap_t rxmap = rxq->rxq_dmamap;
+ uint32_t cmdsts;
+
+ cmdsts = rxmap->dm_segs[0].ds_len | RGE_RDCMDSTS_OWN;
+ if (idx == RGE_RX_LIST_CNT - 1)
+ cmdsts |= RGE_RDCMDSTS_EOR;
+
+ r->hi_qword0.rge_addr = htole64(rxmap->dm_segs[0].ds_addr);
+ r->hi_qword1.rx_qword4.rge_extsts = 0;
+ r->hi_qword1.rx_qword4.rge_cmdsts = htole32(cmdsts);
+
+ bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map,
+ idx * sizeof(struct rge_rx_desc), sizeof(struct rge_rx_desc),
+ BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
+}
+
+/*
* Initialize the RX descriptor and attach an mbuf cluster.
*/
int
rge_newbuf(struct rge_softc *sc, int idx)
{
struct mbuf *m;
- struct rge_rx_desc *r;
struct rge_rxq *rxq;
bus_dmamap_t rxmap;
+ int error __diagused;
m = MCLGETL(NULL, M_DONTWAIT, RGE_JUMBO_FRAMELEN);
if (m == NULL)
@@ -1120,66 +1134,37 @@ rge_newbuf(struct rge_softc *sc, int idx
rxq = &sc->rge_ldata.rge_rxq[idx];
rxmap = rxq->rxq_dmamap;
- if (bus_dmamap_load_mbuf(sc->sc_dmat, rxmap, m, BUS_DMA_NOWAIT))
- goto out;
+ if (rxq->rxq_mbuf != NULL)
+ bus_dmamap_unload(sc->sc_dmat, rxq->rxq_dmamap);
+
+ /* This map was created with BUS_DMA_ALLOCNOW so should never fail. */
+ error = bus_dmamap_load_mbuf(sc->sc_dmat, rxmap, m, BUS_DMA_NOWAIT);
+ KASSERTMSG(error == 0, "error=%d", error);
bus_dmamap_sync(sc->sc_dmat, rxmap, 0, rxmap->dm_mapsize,
BUS_DMASYNC_PREREAD);
/* Map the segments into RX descriptors. */
- r = &sc->rge_ldata.rge_rx_list[idx];
rxq->rxq_mbuf = m;
+ rge_load_rxbuf(sc, idx);
- r->hi_qword1.rx_qword4.rge_extsts = 0;
- r->hi_qword0.rge_addr = htole64(rxmap->dm_segs[0].ds_addr);
-
- r->hi_qword1.rx_qword4.rge_cmdsts = htole32(rxmap->dm_segs[0].ds_len);
- if (idx == RGE_RX_LIST_CNT - 1)
- r->hi_qword1.rx_qword4.rge_cmdsts |= htole32(RGE_RDCMDSTS_EOR);
-
- r->hi_qword1.rx_qword4.rge_cmdsts |= htole32(RGE_RDCMDSTS_OWN);
-
- bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map,
- idx * sizeof(struct rge_rx_desc), sizeof(struct rge_rx_desc),
- BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
-
- return (0);
-out:
- if (m != NULL)
- m_freem(m);
- return (ENOMEM);
+ return 0;
}
-void
-rge_discard_rxbuf(struct rge_softc *sc, int idx)
-{
- struct rge_rx_desc *r;
-
- r = &sc->rge_ldata.rge_rx_list[idx];
-
- r->hi_qword1.rx_qword4.rge_cmdsts = htole32(RGE_JUMBO_FRAMELEN);
- r->hi_qword1.rx_qword4.rge_extsts = 0;
- if (idx == RGE_RX_LIST_CNT - 1)
- r->hi_qword1.rx_qword4.rge_cmdsts |= htole32(RGE_RDCMDSTS_EOR);
- r->hi_qword1.rx_qword4.rge_cmdsts |= htole32(RGE_RDCMDSTS_OWN);
-
- bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map,
- idx * sizeof(struct rge_rx_desc), sizeof(struct rge_rx_desc),
- BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
-}
-
-int
+static int
rge_rx_list_init(struct rge_softc *sc)
{
- int i;
+ unsigned i;
memset(sc->rge_ldata.rge_rx_list, 0, RGE_RX_LIST_SZ);
for (i = 0; i < RGE_RX_LIST_CNT; i++) {
sc->rge_ldata.rge_rxq[i].rxq_mbuf = NULL;
- if (rge_newbuf(sc, i) == ENOBUFS)
+ if (rge_newbuf(sc, i) != 0) {
+ rge_rx_list_fini(sc);
return (ENOBUFS);
+ }
}
sc->rge_ldata.rge_rxq_prodidx = sc->rge_ldata.rge_rxq_considx = 0;
@@ -1188,10 +1173,26 @@ rge_rx_list_init(struct rge_softc *sc)
return (0);
}
-void
+static void
+rge_rx_list_fini(struct rge_softc *sc)
+{
+ unsigned i;
+
+ /* Free the RX list buffers. */
+ for (i = 0; i < RGE_RX_LIST_CNT; i++) {
+ if (sc->rge_ldata.rge_rxq[i].rxq_mbuf != NULL) {
+ bus_dmamap_unload(sc->sc_dmat,
+ sc->rge_ldata.rge_rxq[i].rxq_dmamap);
+ m_freem(sc->rge_ldata.rge_rxq[i].rxq_mbuf);
+ sc->rge_ldata.rge_rxq[i].rxq_mbuf = NULL;
+ }
+ }
+}
+
+static void
rge_tx_list_init(struct rge_softc *sc)
{
- int i;
+ unsigned i;
memset(sc->rge_ldata.rge_tx_list, 0, RGE_TX_LIST_SZ);
@@ -1205,6 +1206,22 @@ rge_tx_list_init(struct rge_softc *sc)
sc->rge_ldata.rge_txq_prodidx = sc->rge_ldata.rge_txq_considx = 0;
}
+static void
+rge_tx_list_fini(struct rge_softc *sc)
+{
+ unsigned i;
+
+ /* Free the TX list buffers. */
+ for (i = 0; i < RGE_TX_LIST_CNT; i++) {
+ if (sc->rge_ldata.rge_txq[i].txq_mbuf != NULL) {
+ bus_dmamap_unload(sc->sc_dmat,
+ sc->rge_ldata.rge_txq[i].txq_dmamap);
+ m_freem(sc->rge_ldata.rge_txq[i].txq_mbuf);
+ sc->rge_ldata.rge_txq[i].txq_mbuf = NULL;
+ }
+ }
+}
+
int
rge_rxeof(struct rge_softc *sc)
{
@@ -1232,17 +1249,16 @@ rge_rxeof(struct rge_softc *sc)
total_len = RGE_RXBYTES(cur_rx);
rxq = &sc->rge_ldata.rge_rxq[i];
m = rxq->rxq_mbuf;
- rxq->rxq_mbuf = NULL;
rx = 1;
- /* Invalidate the RX mbuf and unload its map. */
+ /* Invalidate the RX mbuf. */
bus_dmamap_sync(sc->sc_dmat, rxq->rxq_dmamap, 0,
rxq->rxq_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD);
- bus_dmamap_unload(sc->sc_dmat, rxq->rxq_dmamap);
if ((rxstat & (RGE_RDCMDSTS_SOF | RGE_RDCMDSTS_EOF)) !=
(RGE_RDCMDSTS_SOF | RGE_RDCMDSTS_EOF)) {
- rge_discard_rxbuf(sc, i);
+ if_statinc(ifp, if_ierrors);
+ rge_load_rxbuf(sc, i);
continue;
}
@@ -1252,11 +1268,11 @@ rge_rxeof(struct rge_softc *sc)
* If this is part of a multi-fragment packet,
* discard all the pieces.
*/
- if (sc->rge_head != NULL) {
+ if (sc->rge_head != NULL) {
m_freem(sc->rge_head);
sc->rge_head = sc->rge_tail = NULL;
}
- rge_discard_rxbuf(sc, i);
+ rge_load_rxbuf(sc, i);
continue;
}
@@ -1264,13 +1280,13 @@ rge_rxeof(struct rge_softc *sc)
* If allocating a replacement mbuf fails,
* reload the current one.
*/
-
- if (rge_newbuf(sc, i) == ENOBUFS) {
+ if (rge_newbuf(sc, i) != 0) {
+ if_statinc(ifp, if_iqdrops);
if (sc->rge_head != NULL) {
m_freem(sc->rge_head);
sc->rge_head = sc->rge_tail = NULL;
}
- rge_discard_rxbuf(sc, i);
+ rge_load_rxbuf(sc, i);
continue;
}