Module Name:    src
Committed By:   skrll
Date:           Sun Aug 14 09:01:25 UTC 2022

Modified Files:
        src/sys/dev/pci: if_bge.c if_bgevar.h

Log Message:
Make bge(4) MP safe

This started out as a fix so that LOCKDEBUG wouldn't explode with kernel
lock spinout.  LOCKDEBUG is too aggressive now and really should be
relaxed.


To generate a diff of this commit:
cvs rdiff -u -r1.374 -r1.375 src/sys/dev/pci/if_bge.c
cvs rdiff -u -r1.32 -r1.33 src/sys/dev/pci/if_bgevar.h

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_bge.c
diff -u src/sys/dev/pci/if_bge.c:1.374 src/sys/dev/pci/if_bge.c:1.375
--- src/sys/dev/pci/if_bge.c:1.374	Sun Aug 14 08:42:33 2022
+++ src/sys/dev/pci/if_bge.c	Sun Aug 14 09:01:25 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_bge.c,v 1.374 2022/08/14 08:42:33 skrll Exp $	*/
+/*	$NetBSD: if_bge.c,v 1.375 2022/08/14 09:01:25 skrll Exp $	*/
 
 /*
  * Copyright (c) 2001 Wind River Systems
@@ -79,7 +79,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_bge.c,v 1.374 2022/08/14 08:42:33 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_bge.c,v 1.375 2022/08/14 09:01:25 skrll Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -206,13 +206,17 @@ static int bge_encap(struct bge_softc *,
 
 static int bge_intr(void *);
 static void bge_start(struct ifnet *);
+static void bge_start_locked(struct ifnet *);
 static int bge_ifflags_cb(struct ethercom *);
 static int bge_ioctl(struct ifnet *, u_long, void *);
 static int bge_init(struct ifnet *);
+static int bge_init_locked(struct ifnet *);
 static void bge_stop(struct ifnet *, int);
-static void bge_watchdog(struct ifnet *);
+static void bge_stop_locked(struct ifnet *, int);
+static bool bge_watchdog(struct ifnet *);
 static int bge_ifmedia_upd(struct ifnet *);
 static void bge_ifmedia_sts(struct ifnet *, struct ifmediareq *);
+static void bge_handle_reset_work(struct work *, void *);
 
 static uint8_t bge_nvram_getbyte(struct bge_softc *, int, uint8_t *);
 static int bge_read_nvram(struct bge_softc *, uint8_t *, int, int);
@@ -529,6 +533,12 @@ static const struct bge_revision bge_maj
 
 static int bge_allow_asf = 1;
 
+#ifndef BGE_WATCHDOG_TIMEOUT
+#define BGE_WATCHDOG_TIMEOUT 5
+#endif
+static int bge_watchdog_timeout = BGE_WATCHDOG_TIMEOUT;
+
+
 CFATTACH_DECL3_NEW(bge, sizeof(struct bge_softc),
     bge_probe, bge_attach, bge_detach, NULL, NULL, NULL, DVF_DETACH_SHUTDOWN);
 
@@ -1247,7 +1257,6 @@ static void
 bge_set_thresh(struct ifnet *ifp, int lvl)
 {
 	struct bge_softc * const sc = ifp->if_softc;
-	int s;
 
 	/*
 	 * For now, just save the new Rx-intr thresholds and record
@@ -1256,11 +1265,11 @@ bge_set_thresh(struct ifnet *ifp, int lv
 	 * occasionally cause glitches where Rx-interrupts are not
 	 * honoured for up to 10 seconds. jonat...@netbsd.org, 2003-04-05
 	 */
-	s = splnet();
+	mutex_enter(sc->sc_core_lock);
 	sc->bge_rx_coal_ticks = bge_rx_threshes[lvl].rx_ticks;
 	sc->bge_rx_max_coal_bds = bge_rx_threshes[lvl].rx_max_bds;
 	sc->bge_pending_rxintr_change = 1;
-	splx(s);
+	mutex_exit(sc->sc_core_lock);
 }
 
 
@@ -1311,8 +1320,8 @@ static int
 bge_alloc_jumbo_mem(struct bge_softc *sc)
 {
 	char *ptr, *kva;
-	int		i, rseg, state, error;
-	struct bge_jpool_entry	 *entry;
+	int i, rseg, state, error;
+	struct bge_jpool_entry *entry;
 
 	state = error = 0;
 
@@ -1325,8 +1334,7 @@ bge_alloc_jumbo_mem(struct bge_softc *sc
 
 	state = 1;
 	if (bus_dmamem_map(sc->bge_dmatag, &sc->bge_cdata.bge_rx_jumbo_seg,
-	    rseg, BGE_JMEM, (void **)&kva,
-	    BUS_DMA_NOWAIT)) {
+	    rseg, BGE_JMEM, (void **)&kva, BUS_DMA_NOWAIT)) {
 		aprint_error_dev(sc->bge_dev,
 		    "can't map DMA buffers (%d bytes)\n", (int)BGE_JMEM);
 		error = ENOBUFS;
@@ -1443,7 +1451,6 @@ bge_jfree(struct mbuf *m, void *buf, siz
 {
 	struct bge_jpool_entry *entry;
 	struct bge_softc * const sc = arg;
-	int s;
 
 	if (sc == NULL)
 		panic("bge_jfree: can't find softc pointer!");
@@ -1454,17 +1461,17 @@ bge_jfree(struct mbuf *m, void *buf, siz
 	if (i < 0 || i >= BGE_JSLOTS)
 		panic("bge_jfree: asked to free buffer that we don't manage!");
 
-	s = splvm();
+	mutex_enter(sc->sc_core_lock);
 	entry = SLIST_FIRST(&sc->bge_jinuse_listhead);
 	if (entry == NULL)
 		panic("bge_jfree: buffer not in use!");
 	entry->slot = i;
 	SLIST_REMOVE_HEAD(&sc->bge_jinuse_listhead, jpool_entries);
 	SLIST_INSERT_HEAD(&sc->bge_jfree_listhead, entry, jpool_entries);
+	mutex_exit(sc->sc_core_lock);
 
 	if (__predict_true(m != NULL))
 		pool_cache_put(mb_cache, m);
-	splx(s);
 }
 
 
@@ -1576,6 +1583,7 @@ bge_newbuf_jumbo(struct bge_softc *sc, i
 	bus_dmamap_sync(sc->bge_dmatag, sc->bge_cdata.bge_rx_jumbo_map,
 	    mtod(m_new, char *) - (char *)sc->bge_cdata.bge_jumbo_buf,
 	    BGE_JLEN, BUS_DMASYNC_PREREAD);
+
 	/* Set up the descriptor. */
 	r = &sc->bge_rdata->bge_rx_jumbo_ring[i];
 	sc->bge_cdata.bge_rx_jumbo_chain[i] = m_new;
@@ -1810,14 +1818,14 @@ static void
 bge_setmulti(struct bge_softc *sc)
 {
 	struct ethercom * const ec = &sc->ethercom;
-	struct ifnet * const ifp = &ec->ec_if;
 	struct ether_multi	*enm;
 	struct ether_multistep	step;
 	uint32_t		hashes[4] = { 0, 0, 0, 0 };
 	uint32_t		h;
 	int			i;
 
-	if (ifp->if_flags & IFF_PROMISC)
+	KASSERT(mutex_owned(sc->sc_core_lock));
+	if (sc->bge_if_flags & IFF_PROMISC)
 		goto allmulti;
 
 	/* Now program new ones. */
@@ -1845,13 +1853,15 @@ bge_setmulti(struct bge_softc *sc)
 		hashes[(h & 0x60) >> 5] |= 1U << (h & 0x1F);
 		ETHER_NEXT_MULTI(step, enm);
 	}
+	ec->ec_flags &= ~ETHER_F_ALLMULTI;
 	ETHER_UNLOCK(ec);
 
-	ifp->if_flags &= ~IFF_ALLMULTI;
 	goto setit;
 
  allmulti:
-	ifp->if_flags |= IFF_ALLMULTI;
+	ETHER_LOCK(ec);
+	ec->ec_flags |= ETHER_F_ALLMULTI;
+	ETHER_UNLOCK(ec);
 	hashes[0] = hashes[1] = hashes[2] = hashes[3] = 0xffffffff;
 
  setit:
@@ -3631,6 +3641,18 @@ bge_attach(device_t parent, device_t sel
 		break;
 	}
 
+	char wqname[MAXCOMLEN];
+	snprintf(wqname, sizeof(wqname), "%sReset", device_xname(sc->bge_dev));
+	int error = workqueue_create(&sc->sc_reset_wq, wqname,
+	    bge_handle_reset_work, sc, PRI_NONE, IPL_SOFTCLOCK,
+	    WQ_MPSAFE);
+	if (error) {
+		aprint_error_dev(sc->bge_dev,
+		    "unable to create reset workqueue\n");
+		return;
+	}
+
+
 	/*
 	 * All controllers except BCM5700 supports tagged status but
 	 * we use tagged status only for MSI case on BCM5717. Otherwise
@@ -3830,15 +3852,17 @@ bge_attach(device_t parent, device_t sel
 	else
 		sc->bge_return_ring_cnt = BGE_RETURN_RING_CNT;
 
+	sc->sc_core_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_NET);
+
 	/* Set up ifnet structure */
 	ifp = &sc->ethercom.ec_if;
 	ifp->if_softc = sc;
 	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+	ifp->if_extflags = IFEF_MPSAFE;
 	ifp->if_ioctl = bge_ioctl;
 	ifp->if_stop = bge_stop;
 	ifp->if_start = bge_start;
 	ifp->if_init = bge_init;
-	ifp->if_watchdog = bge_watchdog;
 	IFQ_SET_MAXLEN(&ifp->if_snd, uimax(BGE_TX_RING_CNT - 1, IFQ_MAXLEN));
 	IFQ_SET_READY(&ifp->if_snd);
 	DPRINTFN(5, ("strcpy if_xname\n"));
@@ -3986,12 +4010,16 @@ again:
 	/*
 	 * Call MI attach routine.
 	 */
-	DPRINTFN(5, ("if_attach\n"));
-	if_attach(ifp);
+	DPRINTFN(5, ("if_initialize\n"));
+	if_initialize(ifp);
+	ifp->if_percpuq = if_percpuq_create(ifp);
 	if_deferred_start_init(ifp, NULL);
+	if_register(ifp);
+
 	DPRINTFN(5, ("ether_ifattach\n"));
 	ether_ifattach(ifp, eaddr);
 	ether_set_ifflags_cb(&sc->ethercom, bge_ifflags_cb);
+
 	rnd_attach_source(&sc->rnd_source, device_xname(sc->bge_dev),
 		RND_TYPE_NET, RND_FLAG_DEFAULT);
 #ifdef BGE_EVENT_COUNTERS
@@ -4018,7 +4046,7 @@ again:
 	    NULL, device_xname(sc->bge_dev), "xoffentered");
 #endif /* BGE_EVENT_COUNTERS */
 	DPRINTFN(5, ("callout_init\n"));
-	callout_init(&sc->bge_timeout, 0);
+	callout_init(&sc->bge_timeout, CALLOUT_MPSAFE);
 	callout_setfunc(&sc->bge_timeout, bge_tick, sc);
 
 	if (pmf_device_register(self, NULL, NULL))
@@ -4042,12 +4070,9 @@ bge_detach(device_t self, int flags __un
 {
 	struct bge_softc * const sc = device_private(self);
 	struct ifnet * const ifp = &sc->ethercom.ec_if;
-	int s;
 
-	s = splnet();
 	/* Stop the interface. Callouts are stopped in it. */
 	bge_stop(ifp, 1);
-	splx(s);
 
 	mii_detach(&sc->bge_mii, MII_PHY_ANY, MII_OFFSET_ANY);
 
@@ -4654,11 +4679,8 @@ bge_txeof(struct bge_softc *sc)
 		}
 		sc->bge_txcnt--;
 		BGE_INC(sc->bge_tx_saved_considx, BGE_TX_RING_CNT);
-		ifp->if_timer = 0;
+		sc->bge_tx_sending = false;
 	}
-
-	if (cur_tx != NULL)
-		ifp->if_flags &= ~IFF_OACTIVE;
 }
 
 static int
@@ -4669,11 +4691,12 @@ bge_intr(void *xsc)
 	uint32_t pcistate, statusword, statustag;
 	uint32_t intrmask = BGE_PCISTATE_INTR_NOT_ACTIVE;
 
-
 	/* 5717 and newer chips have no BGE_PCISTATE_INTR_NOT_ACTIVE bit */
 	if (BGE_IS_5717_PLUS(sc))
 		intrmask = 0;
 
+	mutex_enter(sc->sc_core_lock);
+
 	/*
 	 * It is possible for the interrupt to arrive before
 	 * the status block is updated prior to the interrupt.
@@ -4694,6 +4717,7 @@ bge_intr(void *xsc)
 		if (sc->bge_lasttag == statustag &&
 		    (~pcistate & intrmask)) {
 			BGE_EVCNT_INCR(sc->bge_ev_intr_spurious);
+			mutex_exit(sc->sc_core_lock);
 			return 0;
 		}
 		sc->bge_lasttag = statustag;
@@ -4701,6 +4725,7 @@ bge_intr(void *xsc)
 		if (!(statusword & BGE_STATFLAG_UPDATED) &&
 		    !(~pcistate & intrmask)) {
 			BGE_EVCNT_INCR(sc->bge_ev_intr_spurious2);
+			mutex_exit(sc->sc_core_lock);
 			return 0;
 		}
 		statustag = 0;
@@ -4722,7 +4747,7 @@ bge_intr(void *xsc)
 	    BGE_STS_BIT(sc, BGE_STS_LINK_EVT))
 		bge_link_upd(sc);
 
-	if (ifp->if_flags & IFF_RUNNING) {
+	if (sc->bge_if_flags & IFF_RUNNING) {
 		/* Check RX return ring producer/consumer */
 		bge_rxeof(sc);
 
@@ -4749,9 +4774,11 @@ bge_intr(void *xsc)
 	/* Re-enable interrupts. */
 	bge_writembx_flush(sc, BGE_MBX_IRQ0_LO, statustag);
 
-	if (ifp->if_flags & IFF_RUNNING)
+	if (sc->bge_if_flags & IFF_RUNNING)
 		if_schedule_deferred_start(ifp);
 
+	mutex_exit(sc->sc_core_lock);
+
 	return 1;
 }
 
@@ -4783,10 +4810,10 @@ static void
 bge_tick(void *xsc)
 {
 	struct bge_softc * const sc = xsc;
+	struct ifnet * const ifp = &sc->ethercom.ec_if;
 	struct mii_data * const mii = &sc->bge_mii;
-	int s;
 
-	s = splnet();
+	mutex_enter(sc->sc_core_lock);
 
 	if (BGE_IS_5705_PLUS(sc))
 		bge_stats_update_regs(sc);
@@ -4813,16 +4840,18 @@ bge_tick(void *xsc)
 
 	bge_asf_driver_up(sc);
 
-	if (!sc->bge_detaching)
+	const bool ok = bge_watchdog(ifp);
+
+	if (ok && !sc->bge_detaching)
 		callout_schedule(&sc->bge_timeout, hz);
 
-	splx(s);
+	mutex_exit(sc->sc_core_lock);
 }
 
 static void
 bge_stats_update_regs(struct bge_softc *sc)
 {
-	struct ifnet *const ifp = &sc->ethercom.ec_if;
+	struct ifnet * const ifp = &sc->ethercom.ec_if;
 
 	net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
 
@@ -5096,7 +5125,6 @@ bge_compact_dma_runt(struct mbuf *pkt)
 static int
 bge_encap(struct bge_softc *sc, struct mbuf *m_head, uint32_t *txidx)
 {
-	struct ifnet * const ifp = &sc->ethercom.ec_if;
 	struct bge_tx_bd	*f, *prev_f;
 	uint32_t		frag, cur;
 	uint16_t		csum_flags = 0;
@@ -5149,7 +5177,6 @@ check_dma_bug:
 doit:
 	dma = SLIST_FIRST(&sc->txdma_list);
 	if (dma == NULL) {
-		ifp->if_flags |= IFF_OACTIVE;
 		return ENOBUFS;
 	}
 	dmamap = dma->dmamap;
@@ -5420,17 +5447,27 @@ load_again:
 
 fail_unload:
 	bus_dmamap_unload(dmatag, dmamap);
-	ifp->if_flags |= IFF_OACTIVE;
 
 	return ENOBUFS;
 }
 
+
+static void
+bge_start(struct ifnet *ifp)
+{
+	struct bge_softc * const sc = ifp->if_softc;
+
+	mutex_enter(sc->sc_core_lock);
+	bge_start_locked(ifp);
+	mutex_exit(sc->sc_core_lock);
+}
+
 /*
  * Main transmit routine. To avoid having to do mbuf copies, we put pointers
  * to the mbuf data regions directly in the transmit descriptors.
  */
 static void
-bge_start(struct ifnet *ifp)
+bge_start_locked(struct ifnet *ifp)
 {
 	struct bge_softc * const sc = ifp->if_softc;
 	struct mbuf *m_head = NULL;
@@ -5439,7 +5476,7 @@ bge_start(struct ifnet *ifp)
 	int pkts = 0;
 	int error;
 
-	if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
+	if ((sc->bge_if_flags & IFF_RUNNING) != IFF_RUNNING)
 		return;
 
 	prodidx = sc->bge_tx_prodidx;
@@ -5475,7 +5512,7 @@ bge_start(struct ifnet *ifp)
 		 */
 		error = bge_encap(sc, m_head, &prodidx);
 		if (__predict_false(error)) {
-			if (ifp->if_flags & IFF_OACTIVE) {
+			if (SLIST_EMPTY(&sc->txdma_list)) {
 				/* just wait for the transmit ring to drain */
 				break;
 			}
@@ -5506,27 +5543,37 @@ bge_start(struct ifnet *ifp)
 		bge_writembx(sc, BGE_MBX_TX_HOST_PROD0_LO, prodidx);
 
 	sc->bge_tx_prodidx = prodidx;
-
-	/*
-	 * Set a timeout in case the chip goes out to lunch.
-	 */
-	ifp->if_timer = 5;
+	sc->bge_tx_lastsent = time_uptime;
+	sc->bge_tx_sending = true;
 }
 
 static int
 bge_init(struct ifnet *ifp)
 {
 	struct bge_softc * const sc = ifp->if_softc;
+
+	mutex_enter(sc->sc_core_lock);
+	int ret = bge_init_locked(ifp);
+	mutex_exit(sc->sc_core_lock);
+
+	return ret;
+}
+
+
+static int
+bge_init_locked(struct ifnet *ifp)
+{
+	struct bge_softc * const sc = ifp->if_softc;
 	const uint16_t *m;
 	uint32_t mode, reg;
-	int s, error = 0;
-
-	s = splnet();
+	int error = 0;
 
+	KASSERT(IFNET_LOCKED(ifp));
+	KASSERT(mutex_owned(sc->sc_core_lock));
 	KASSERT(ifp == &sc->ethercom.ec_if);
 
 	/* Cancel pending I/O and flush buffers. */
-	bge_stop(ifp, 0);
+	bge_stop_locked(ifp, 0);
 
 	bge_stop_fw(sc);
 	bge_sig_pre_reset(sc, BGE_RESET_START);
@@ -5593,7 +5640,6 @@ bge_init(struct ifnet *ifp)
 	if (error != 0) {
 		aprint_error_dev(sc->bge_dev, "initialization error %d\n",
 		    error);
-		splx(s);
 		return error;
 	}
 
@@ -5610,7 +5656,7 @@ bge_init(struct ifnet *ifp)
 	    ((uint32_t)htons(m[1]) << 16) | htons(m[2]));
 
 	/* Enable or disable promiscuous mode as needed. */
-	if (ifp->if_flags & IFF_PROMISC)
+	if (sc->bge_if_flags & IFF_PROMISC)
 		BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
 	else
 		BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
@@ -5700,14 +5746,13 @@ bge_init(struct ifnet *ifp)
 	if ((error = bge_ifmedia_upd(ifp)) != 0)
 		goto out;
 
+	/* IFNET_LOCKED asserted above */
 	ifp->if_flags |= IFF_RUNNING;
-	ifp->if_flags &= ~IFF_OACTIVE;
 
 	callout_schedule(&sc->bge_timeout, hz);
 
 out:
 	sc->bge_if_flags = ifp->if_flags;
-	splx(s);
 
 	return error;
 }
@@ -5846,22 +5891,28 @@ bge_ifflags_cb(struct ethercom *ec)
 {
 	struct ifnet * const ifp = &ec->ec_if;
 	struct bge_softc * const sc = ifp->if_softc;
-	u_short change = ifp->if_flags ^ sc->bge_if_flags;
+	int ret = 0;
 
-	if ((change & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0)
-		return ENETRESET;
-	else if ((change & (IFF_PROMISC | IFF_ALLMULTI)) == 0)
-		return 0;
+	KASSERT(IFNET_LOCKED(ifp));
+	mutex_enter(sc->sc_core_lock);
 
-	if ((ifp->if_flags & IFF_PROMISC) == 0)
-		BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
-	else
-		BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
+	u_short change = ifp->if_flags ^ sc->bge_if_flags;
 
-	bge_setmulti(sc);
+	if ((change & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0) {
+		ret = ENETRESET;
+	} else if ((change & (IFF_PROMISC | IFF_ALLMULTI)) != 0) {
+		if ((ifp->if_flags & IFF_PROMISC) == 0)
+			BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
+		else
+			BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC);
+
+		bge_setmulti(sc);
+	}
 
 	sc->bge_if_flags = ifp->if_flags;
-	return 0;
+	mutex_exit(sc->sc_core_lock);
+
+	return ret;
 }
 
 static int
@@ -5869,13 +5920,21 @@ bge_ioctl(struct ifnet *ifp, u_long comm
 {
 	struct bge_softc * const sc = ifp->if_softc;
 	struct ifreq * const ifr = (struct ifreq *) data;
-	int s, error = 0;
-	struct mii_data *mii;
+	int error = 0;
+
+	switch (command) {
+	case SIOCADDMULTI:
+	case SIOCDELMULTI:
+		break;
+	default:
+		KASSERT(IFNET_LOCKED(ifp));
+	}
 
-	s = splnet();
+	const int s = splnet();
 
 	switch (command) {
 	case SIOCSIFMEDIA:
+		mutex_enter(sc->sc_core_lock);
 		/* XXX Flow control is not supported for 1000BASE-SX */
 		if (sc->bge_flags & BGEF_FIBER_TBI) {
 			ifr->ifr_media &= ~IFM_ETH_FMASK;
@@ -5895,12 +5954,13 @@ bge_ioctl(struct ifnet *ifp, u_long comm
 			}
 			sc->bge_flowflags = ifr->ifr_media & IFM_ETH_FMASK;
 		}
+		mutex_exit(sc->sc_core_lock);
 
 		if (sc->bge_flags & BGEF_FIBER_TBI) {
 			error = ifmedia_ioctl(ifp, ifr, &sc->bge_ifmedia,
 			    command);
 		} else {
-			mii = &sc->bge_mii;
+			struct mii_data * const mii = &sc->bge_mii;
 			error = ifmedia_ioctl(ifp, ifr, &mii->mii_media,
 			    command);
 		}
@@ -5911,10 +5971,13 @@ bge_ioctl(struct ifnet *ifp, u_long comm
 
 		error = 0;
 
-		if (command != SIOCADDMULTI && command != SIOCDELMULTI)
-			;
-		else if (ifp->if_flags & IFF_RUNNING)
-			bge_setmulti(sc);
+		if (command == SIOCADDMULTI || command == SIOCDELMULTI) {
+			mutex_enter(sc->sc_core_lock);
+			if (sc->bge_if_flags & IFF_RUNNING) {
+				bge_setmulti(sc);
+			}
+			mutex_exit(sc->sc_core_lock);
+		}
 		break;
 	}
 
@@ -5923,23 +5986,29 @@ bge_ioctl(struct ifnet *ifp, u_long comm
 	return error;
 }
 
-static void
-bge_watchdog(struct ifnet *ifp)
+static bool
+bge_watchdog_check(struct bge_softc * const sc)
 {
-	struct bge_softc * const sc = ifp->if_softc;
-	uint32_t status;
+
+	KASSERT(mutex_owned(sc->sc_core_lock));
+
+	if (!sc->bge_tx_sending)
+		return true;
+
+	if (time_uptime - sc->bge_tx_lastsent <= bge_watchdog_timeout)
+		return true;
 
 	/* If pause frames are active then don't reset the hardware. */
 	if ((CSR_READ_4(sc, BGE_RX_MODE) & BGE_RXMODE_FLOWCTL_ENABLE) != 0) {
-		status = CSR_READ_4(sc, BGE_RX_STS);
+		const uint32_t status = CSR_READ_4(sc, BGE_RX_STS);
 		if ((status & BGE_RXSTAT_REMOTE_XOFFED) != 0) {
 			/*
 			 * If link partner has us in XOFF state then wait for
 			 * the condition to clear.
 			 */
 			CSR_WRITE_4(sc, BGE_RX_STS, status);
-			ifp->if_timer = 5;
-			return;
+			sc->bge_tx_lastsent = time_uptime;
+			return true;
 		} else if ((status & BGE_RXSTAT_RCVD_XOFF) != 0 &&
 		    (status & BGE_RXSTAT_RCVD_XON) != 0) {
 			/*
@@ -5947,8 +6016,8 @@ bge_watchdog(struct ifnet *ifp)
 			 * the condition to clear.
 			 */
 			CSR_WRITE_4(sc, BGE_RX_STS, status);
-			ifp->if_timer = 5;
-			return;
+			sc->bge_tx_lastsent = time_uptime;
+			return true;
 		}
 		/*
 		 * Any other condition is unexpected and the controller
@@ -5956,12 +6025,52 @@ bge_watchdog(struct ifnet *ifp)
 		 */
 	}
 
+	return false;
+}
+
+static bool
+bge_watchdog(struct ifnet *ifp)
+{
+	struct bge_softc * const sc = ifp->if_softc;
+
+	KASSERT(mutex_owned(sc->sc_core_lock));
+
+	if (!sc->sc_triggerreset && bge_watchdog_check(sc))
+		return true;
+
 	aprint_error_dev(sc->bge_dev, "watchdog timeout -- resetting\n");
 
-	ifp->if_flags &= ~IFF_RUNNING;
+	if (atomic_swap_uint(&sc->sc_reset_pending, 1) == 0)
+		workqueue_enqueue(sc->sc_reset_wq, &sc->sc_reset_work, NULL);
+
+	return false;
+}
+
+/*
+ * Perform an interface watchdog reset.
+ */
+static void
+bge_handle_reset_work(struct work *work, void *arg)
+{
+	struct bge_softc * const sc = arg;
+	struct ifnet * const ifp = &sc->ethercom.ec_if;
+
+	/* Don't want ioctl operations to happen */
+	IFNET_LOCK(ifp);
+
+	/* reset the interface. */
 	bge_init(ifp);
 
-	if_statinc(ifp, if_oerrors);
+	IFNET_UNLOCK(ifp);
+
+	/*
+	 * There are still some upper layer processing which call
+	 * ifp->if_start(). e.g. ALTQ or one CPU system
+	 */
+	/* Try to get more packets going. */
+	ifp->if_start(ifp);
+
+	atomic_store_relaxed(&sc->sc_reset_pending, 0);
 }
 
 static void
@@ -5987,15 +6096,30 @@ bge_stop_block(struct bge_softc *sc, bus
 		    (u_long)reg, bit);
 }
 
+
+static void
+bge_stop(struct ifnet *ifp, int disable)
+{
+	struct bge_softc * const sc = ifp->if_softc;
+
+	ASSERT_SLEEPABLE();
+
+	mutex_enter(sc->sc_core_lock);
+	bge_stop_locked(ifp, disable);
+	mutex_exit(sc->sc_core_lock);
+}
+
 /*
  * Stop the adapter and free any mbufs allocated to the
  * RX and TX lists.
  */
 static void
-bge_stop(struct ifnet *ifp, int disable)
+bge_stop_locked(struct ifnet *ifp, int disable)
 {
 	struct bge_softc * const sc = ifp->if_softc;
 
+	KASSERT(mutex_owned(sc->sc_core_lock));
+
 	if (disable) {
 		sc->bge_detaching = 1;
 		callout_halt(&sc->bge_timeout, NULL);
@@ -6093,7 +6217,7 @@ bge_stop(struct ifnet *ifp, int disable)
 	/* Clear MAC's link state (PHY may still have link UP). */
 	BGE_STS_CLRBIT(sc, BGE_STS_LINK);
 
-	ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+	ifp->if_flags &= ~IFF_RUNNING;
 }
 
 static void
@@ -6277,6 +6401,16 @@ bge_sysctl_init(struct bge_softc *sc)
 
 	bge_rxthresh_nodenum = node->sysctl_num;
 
+#ifdef BGE_DEBUG
+	if ((rc = sysctl_createv(&sc->bge_log, 0, NULL, &node,
+	    CTLFLAG_READWRITE,
+	    CTLTYPE_BOOL, "trigger_reset",
+	    SYSCTL_DESCR("Trigger an interface reset"),
+	    NULL, 0, &sc->sc_triggerreset, 0, CTL_CREATE,
+	    CTL_EOL)) != 0) {
+		goto out;
+	}
+#endif
 	return;
 
 out:

Index: src/sys/dev/pci/if_bgevar.h
diff -u src/sys/dev/pci/if_bgevar.h:1.32 src/sys/dev/pci/if_bgevar.h:1.33
--- src/sys/dev/pci/if_bgevar.h:1.32	Sun Aug 14 08:45:56 2022
+++ src/sys/dev/pci/if_bgevar.h	Sun Aug 14 09:01:25 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_bgevar.h,v 1.32 2022/08/14 08:45:56 skrll Exp $	*/
+/*	$NetBSD: if_bgevar.h,v 1.33 2022/08/14 09:01:25 skrll Exp $	*/
 /*
  * Copyright (c) 2001 Wind River Systems
  * Copyright (c) 1997, 1998, 1999, 2001
@@ -68,6 +68,7 @@
 
 #include <sys/bus.h>
 #include <sys/rndsource.h>
+#include <sys/time.h>
 
 #include <net/if_ether.h>
 
@@ -327,6 +328,9 @@ struct bge_softc {
 	uint32_t		bge_flags;
 	uint32_t		bge_phy_flags;
 	int			bge_flowflags;
+	time_t			bge_tx_lastsent;
+	bool			bge_tx_sending;
+
 #ifdef BGE_EVENT_COUNTERS
 	/*
 	 * Event counters.
@@ -352,6 +356,13 @@ struct bge_softc {
 	struct sysctllog	*bge_log;
 
 	krndsource_t	rnd_source;	/* random source */
+
+	kmutex_t *sc_core_lock;		/* lock for softc operations */
+	struct workqueue *sc_reset_wq;
+	struct work sc_reset_work;
+	volatile unsigned sc_reset_pending;
+
+	bool sc_triggerreset;
 };
 
 #endif /* _DEV_PCI_IF_BGEVAR_H_ */

Reply via email to