Module Name:    src
Committed By:   skrll
Date:           Sat Oct  5 07:37:22 UTC 2024

Modified Files:
        src/sys/dev/cadence: if_cemac.c if_cemacvar.h

Log Message:
Make cemac(4) MP safe.

Tested with qemu and

        qemu-system-arm \
            -kernel netbsd.ub \
            -dtb zynq-zc702.dtb \
            -M xilinx-zynq-a9 \
            -sd armv7.img \
            -append "root=ld0a -x -v" \
            -serial null -serial mon:stdio \
            -nographic


To generate a diff of this commit:
cvs rdiff -u -r1.42 -r1.43 src/sys/dev/cadence/if_cemac.c
cvs rdiff -u -r1.5 -r1.6 src/sys/dev/cadence/if_cemacvar.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/cadence/if_cemac.c
diff -u src/sys/dev/cadence/if_cemac.c:1.42 src/sys/dev/cadence/if_cemac.c:1.43
--- src/sys/dev/cadence/if_cemac.c:1.42	Sun Sep 29 09:13:14 2024
+++ src/sys/dev/cadence/if_cemac.c	Sat Oct  5 07:37:22 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_cemac.c,v 1.42 2024/09/29 09:13:14 skrll Exp $	*/
+/*	$NetBSD: if_cemac.c,v 1.43 2024/10/05 07:37:22 skrll Exp $	*/
 
 /*
  * Copyright (c) 2015  Genetec Corporation.  All rights reserved.
@@ -39,8 +39,16 @@
  * used by arm/at91, arm/zynq SoC
  */
 
+/*
+ * Lock order:
+ *
+ *	IFNET_LOCK -> sc_mcast_lock
+ *	IFNET_LOCK -> sc_intr_lock
+ */
+
+
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_cemac.c,v 1.42 2024/09/29 09:13:14 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_cemac.c,v 1.43 2024/10/05 07:37:22 skrll Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -73,6 +81,11 @@ __KERNEL_RCSID(0, "$NetBSD: if_cemac.c,v
 #include <dev/cadence/cemacreg.h>
 #include <dev/cadence/if_cemacvar.h>
 
+#ifndef CEMAC_WATCHDOG_TIMEOUT
+#define CEMAC_WATCHDOG_TIMEOUT 5
+#endif
+static int cemac_watchdog_timeout = CEMAC_WATCHDOG_TIMEOUT;
+
 #define DEFAULT_MDCDIV	32
 
 #define CEMAC_READ(x) \
@@ -97,6 +110,7 @@ static void	cemac_statchg(struct ifnet *
 static void	cemac_tick(void *);
 static int	cemac_ifioctl(struct ifnet *, u_long, void *);
 static void	cemac_ifstart(struct ifnet *);
+static void	cemac_ifstart_locked(struct ifnet *);
 static void	cemac_ifwatchdog(struct ifnet *);
 static int	cemac_ifinit(struct ifnet *);
 static void	cemac_ifstop(struct ifnet *, int);
@@ -109,6 +123,36 @@ int cemac_debug = CEMAC_DEBUG;
 #define	DPRINTFN(n, fmt)
 #endif
 
+/*
+ * Perform an interface watchdog reset.
+ */
+static void
+cemac_handle_reset_work(struct work *work, void *arg)
+{
+	struct cemac_softc * const sc = arg;
+	struct ifnet * const ifp = &sc->sc_ethercom.ec_if;
+
+	printf("%s: watchdog timeout -- resetting\n", ifp->if_xname);
+
+	/* Don't want ioctl operations to happen */
+	IFNET_LOCK(ifp);
+
+	/* reset the interface. */
+	cemac_ifinit(ifp);
+
+	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);
+}
+
+
 void
 cemac_attach_common(struct cemac_softc *sc)
 {
@@ -217,16 +261,29 @@ cemac_intr(void *arg)
 #endif
 	int bi;
 
+	mutex_enter(sc->sc_intr_lock);
+	if (sc->sc_stopping) {
+		mutex_exit(sc->sc_intr_lock);
+		return 0;
+	}
+
 	imr = ~CEMAC_READ(ETH_IMR);
 	if (!(imr & (ETH_ISR_RCOM | ETH_ISR_TBRE | ETH_ISR_TIDLE |
 	    ETH_ISR_RBNA | ETH_ISR_ROVR | ETH_ISR_TCOM))) {
 		// interrupt not enabled, can't be us
+		mutex_exit(sc->sc_intr_lock);
 		return 0;
 	}
 
 	isr = CEMAC_READ(ETH_ISR);
 	CEMAC_WRITE(ETH_ISR, isr);
 	isr &= imr;
+
+	if (isr == 0) {
+		mutex_exit(sc->sc_intr_lock);
+		return 0;
+	}
+
 #ifdef	CEMAC_DEBUG
 	rsr = CEMAC_READ(ETH_RSR);		// get receive status register
 #endif
@@ -338,10 +395,36 @@ cemac_intr(void *arg)
 		goto begin;
 #endif
 
+	mutex_exit(sc->sc_intr_lock);
+
 	return 1;
 }
 
 
+static int
+cemac_ifflags_cb(struct ethercom *ec)
+{
+	struct ifnet * const ifp = &ec->ec_if;
+	struct cemac_softc * const sc = ifp->if_softc;
+	int ret = 0;
+
+	KASSERT(IFNET_LOCKED(ifp));
+	mutex_enter(sc->sc_mcast_lock);
+
+	u_short change = ifp->if_flags ^ sc->sc_if_flags;
+	sc->sc_if_flags = ifp->if_flags;
+
+	if ((change & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0) {
+		ret = ENETRESET;
+	} else if ((change & IFF_PROMISC) != 0) {
+		if ((sc->sc_if_flags & IFF_RUNNING) != 0)
+			cemac_setaddr(ifp);
+	}
+	mutex_exit(sc->sc_mcast_lock);
+
+	return ret;
+}
+
 static void
 cemac_init(struct cemac_softc *sc)
 {
@@ -354,7 +437,8 @@ cemac_init(struct cemac_softc *sc)
 	int mdcdiv = DEFAULT_MDCDIV;
 #endif
 
-	callout_init(&sc->cemac_tick_ch, 0);
+	callout_init(&sc->cemac_tick_ch, CALLOUT_MPSAFE);
+	callout_setfunc(&sc->cemac_tick_ch, cemac_tick, sc);
 
 	// ok...
 	CEMAC_WRITE(ETH_CTL, ETH_CTL_MPE);	// disable everything
@@ -401,6 +485,17 @@ cemac_init(struct cemac_softc *sc)
 	CEMAC_GEM_WRITE(SA4L, 0);
 	CEMAC_GEM_WRITE(SA4H, 0);
 
+	char wqname[MAXCOMLEN];
+	snprintf(wqname, sizeof(wqname), "%sReset", device_xname(sc->sc_dev));
+	int error = workqueue_create(&sc->sc_reset_wq, wqname,
+	    cemac_handle_reset_work, sc, PRI_NONE, IPL_SOFTCLOCK,
+	    WQ_MPSAFE);
+	if (error) {
+		aprint_error_dev(sc->sc_dev,
+		    "unable to create reset workqueue\n");
+		return;
+	}
+
 	/* Allocate memory for receive queue descriptors */
 	sc->rbqlen = roundup(ETH_DSC_SIZE * (RX_QLEN + 1) * 2, PAGE_SIZE);
 	DPRINTFN(1,("%s: rbqlen=%i\n", __FUNCTION__, sc->rbqlen));
@@ -518,6 +613,9 @@ cemac_init(struct cemac_softc *sc)
 	CEMAC_WRITE(ETH_RBQP, (uint32_t)sc->rbqpage_dsaddr);
 	CEMAC_WRITE(ETH_TBQP, (uint32_t)sc->tbqpage_dsaddr);
 
+	sc->sc_mcast_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_SOFTNET);
+	sc->sc_intr_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_NET);
+
 	/* Divide HCLK by 32 for MDC clock */
 	sc->sc_ethercom.ec_mii = mii;
 	mii->mii_ifp = ifp;
@@ -557,17 +655,18 @@ cemac_init(struct cemac_softc *sc)
 
 	strcpy(ifp->if_xname, device_xname(sc->sc_dev));
 	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+	ifp->if_extflags = IFEF_MPSAFE;
 	ifp->if_ioctl = cemac_ifioctl;
 	ifp->if_start = cemac_ifstart;
 	ifp->if_watchdog = cemac_ifwatchdog;
 	ifp->if_init = cemac_ifinit;
 	ifp->if_stop = cemac_ifstop;
-	ifp->if_timer = 0;
 	ifp->if_softc = sc;
 	IFQ_SET_READY(&ifp->if_snd);
 	if_attach(ifp);
 	if_deferred_start_init(ifp, NULL);
 	ether_ifattach(ifp, (sc)->sc_enaddr);
+	ether_set_ifflags_cb(&sc->sc_ethercom, cemac_ifflags_cb);
 }
 
 static int
@@ -656,12 +755,49 @@ cemac_statchg(struct ifnet *ifp)
 	CEMAC_WRITE(ETH_CFG, reg);
 }
 
+static bool
+cemac_watchdog_check(struct cemac_softc * const sc)
+{
+
+	KASSERT(mutex_owned(sc->sc_intr_lock));
+
+	if (!sc->sc_tx_sending)
+		return true;
+
+	if (time_uptime - sc->sc_tx_lastsent <= cemac_watchdog_timeout)
+		return true;
+
+	return false;
+}
+
+static bool
+cemac_watchdog_tick(struct ifnet *ifp)
+{
+	struct cemac_softc * const sc = ifp->if_softc;
+
+	KASSERT(mutex_owned(sc->sc_intr_lock));
+
+	if (!sc->sc_trigger_reset && cemac_watchdog_check(sc))
+		return true;
+
+	if (atomic_swap_uint(&sc->sc_reset_pending, 1) == 0)
+		workqueue_enqueue(sc->sc_reset_wq, &sc->sc_reset_work, NULL);
+
+	return false;
+}
+
+
 static void
 cemac_tick(void *arg)
 {
 	struct cemac_softc * const sc = arg;
 	struct ifnet * const ifp = &sc->sc_ethercom.ec_if;
-	int s;
+
+	mutex_enter(sc->sc_intr_lock);
+	if (sc->sc_stopping) {
+		mutex_exit(sc->sc_intr_lock);
+		return;
+	}
 
 	if (ISSET(sc->cemac_flags, CEMAC_FLAG_GEM))
 		if_statadd(ifp, if_collisions,
@@ -677,65 +813,84 @@ cemac_tick(void *arg)
 			aprint_normal_ifnet(ifp, "%d rx misses\n", misses);
 	}
 
-	s = splnet();
-	if (cemac_gctx(sc) > 0 && IFQ_IS_EMPTY(&ifp->if_snd) == 0)
-		cemac_ifstart(ifp);
-	splx(s);
-
 	mii_tick(&sc->sc_mii);
-	callout_reset(&sc->cemac_tick_ch, hz, cemac_tick, sc);
+
+	const bool ok = cemac_watchdog_tick(ifp);
+	if (ok)
+		callout_schedule(&sc->cemac_tick_ch, hz);
+
+	mutex_exit(sc->sc_intr_lock);
 }
 
 
 static int
 cemac_ifioctl(struct ifnet *ifp, u_long cmd, void *data)
 {
-	int s, error;
-
-	s = splnet();
-	switch (cmd) {
-	default:
-		error = ether_ioctl(ifp, cmd, data);
-		if (error != ENETRESET)
-			break;
-		error = 0;
+	struct cemac_softc * const sc = ifp->if_softc;
+	int error;
 
-		if (cmd == SIOCSIFCAP) {
-			error = if_init(ifp);
-		} else if (cmd != SIOCADDMULTI && cmd != SIOCDELMULTI)
-			;
-		else if (ifp->if_flags & IFF_RUNNING) {
-			cemac_setaddr(ifp);
-		}
+ 	switch (cmd) {
+	case SIOCADDMULTI:
+	case SIOCDELMULTI:
+		break;
+ 	default:
+		KASSERT(IFNET_LOCKED(ifp));
 	}
+
+	const int s = splnet();
+	error = ether_ioctl(ifp, cmd, data);
 	splx(s);
+
+	if (error == ENETRESET) {
+ 		error = 0;
+
+		if (cmd == SIOCADDMULTI || cmd == SIOCDELMULTI) {
+			mutex_enter(sc->sc_mcast_lock);
+			if ((sc->sc_if_flags & IFF_RUNNING) != 0)
+				cemac_setaddr(ifp);
+
+			mutex_exit(sc->sc_mcast_lock);
+ 		}
+ 	}
+
 	return error;
 }
 
+
+
 static void
 cemac_ifstart(struct ifnet *ifp)
 {
 	struct cemac_softc * const sc = ifp->if_softc;
+	KASSERT(if_is_mpsafe(ifp));
+
+	mutex_enter(sc->sc_intr_lock);
+	if (!sc->sc_stopping) {
+		cemac_ifstart_locked(ifp);
+	}
+	mutex_exit(sc->sc_intr_lock);
+}
+
+static void
+cemac_ifstart_locked(struct ifnet *ifp)
+{
+	struct cemac_softc * const sc = ifp->if_softc;
 	struct mbuf *m;
 	bus_dma_segment_t *segs;
-	int s, bi, err, nsegs;
+	int bi, err, nsegs;
+
+	KASSERT(mutex_owned(sc->sc_intr_lock));
 
-	s = splnet();
 start:
 	if (cemac_gctx(sc) == 0) {
 		/* Enable transmit-buffer-free interrupt */
 		CEMAC_WRITE(ETH_IER, ETH_ISR_TBRE);
 		sc->sc_txbusy = true;
-		ifp->if_timer = 10;
-		splx(s);
 		return;
 	}
 
-	ifp->if_timer = 0;
-
 	IFQ_POLL(&ifp->if_snd, m);
 	if (m == NULL) {
-		splx(s);
 		return;
 	}
 
@@ -752,12 +907,12 @@ start:
 
 		MGETHDR(mn, M_DONTWAIT, MT_DATA);
 		if (mn == NULL)
-			goto stop;
+			return;
 		if (m->m_pkthdr.len > MHLEN) {
 			MCLGET(mn, M_DONTWAIT);
 			if ((mn->m_flags & M_EXT) == 0) {
 				m_freem(mn);
-				goto stop;
+				return;
 			}
 		}
 		m_copydata(m, 0, m->m_pkthdr.len, mtod(mn, void *));
@@ -812,11 +967,11 @@ start:
 		CEMAC_WRITE(ETH_TAR, segs->ds_addr);
 		CEMAC_WRITE(ETH_TCR, m->m_pkthdr.len);
 	}
+	sc->sc_tx_lastsent = time_uptime;
+
 	if (IFQ_IS_EMPTY(&ifp->if_snd) == 0)
 		goto start;
-stop:
 
-	splx(s);
 	return;
 }
 
@@ -836,9 +991,12 @@ cemac_ifinit(struct ifnet *ifp)
 {
 	struct cemac_softc * const sc = ifp->if_softc;
 	uint32_t dma, cfg;
-	int s = splnet();
 
-	callout_stop(&sc->cemac_tick_ch);
+	ASSERT_SLEEPABLE();
+	KASSERT(IFNET_LOCKED(ifp));
+
+	/* Cancel pending I/O and flush buffers. */
+	cemac_ifstop(ifp, 0);
 
 	if (ISSET(sc->cemac_flags, CEMAC_FLAG_GEM)) {
 
@@ -872,7 +1030,11 @@ cemac_ifinit(struct ifnet *ifp)
 	mii_mediachg(&sc->sc_mii);
 	callout_reset(&sc->cemac_tick_ch, hz, cemac_tick, sc);
 	ifp->if_flags |= IFF_RUNNING;
-	splx(s);
+
+	mutex_enter(sc->sc_intr_lock);
+	sc->sc_stopping = false;
+	mutex_exit(sc->sc_intr_lock);
+
 	return 0;
 }
 
@@ -882,6 +1044,19 @@ cemac_ifstop(struct ifnet *ifp, int disa
 //	uint32_t u;
 	struct cemac_softc * const sc = ifp->if_softc;
 
+	ASSERT_SLEEPABLE();
+	KASSERT(IFNET_LOCKED(ifp));
+
+	ifp->if_flags &= ~IFF_RUNNING;
+
+	mutex_enter(sc->sc_mcast_lock);
+	sc->sc_if_flags = ifp->if_flags;
+	mutex_exit(sc->sc_mcast_lock);
+
+	mutex_enter(sc->sc_intr_lock);
+	sc->sc_stopping = true;
+	mutex_exit(sc->sc_intr_lock);
+
 #if 0
 	CEMAC_WRITE(ETH_CTL, ETH_CTL_MPE);	// disable everything
 	CEMAC_WRITE(ETH_IDR, -1);		// disable interrupts
@@ -901,15 +1076,14 @@ cemac_ifstop(struct ifnet *ifp, int disa
 	u = CEMAC_READ(ETH_RSR);
 	CEMAC_WRITE(ETH_RSR, (u & (ETH_RSR_OVR | ETH_RSR_REC | ETH_RSR_BNA)));
 #endif
-	callout_stop(&sc->cemac_tick_ch);
+	callout_halt(&sc->cemac_tick_ch, NULL);
 
 	/* Down the MII. */
 	mii_down(&sc->sc_mii);
 
 	ifp->if_flags &= ~IFF_RUNNING;
-	ifp->if_timer = 0;
 	sc->sc_txbusy = false;
-	sc->sc_mii.mii_media_status &= ~IFM_ACTIVE;
+	    sc->sc_mii.mii_media_status &= ~IFM_ACTIVE;
 }
 
 static void
@@ -924,12 +1098,14 @@ cemac_setaddr(struct ifnet *ifp)
 	uint32_t ctl = CEMAC_READ(ETH_CTL);
 	uint32_t cfg = CEMAC_READ(ETH_CFG);
 
+	KASSERT(mutex_owned(sc->sc_mcast_lock));
+
 	/* disable receiver temporarily */
 	CEMAC_WRITE(ETH_CTL, ctl & ~ETH_CTL_RE);
 
 	cfg &= ~(ETH_CFG_MTI | ETH_CFG_UNI | ETH_CFG_CAF | ETH_CFG_UNI);
 
-	if (ifp->if_flags & IFF_PROMISC) {
+	if (sc->sc_if_flags & IFF_PROMISC) {
 		cfg |=	ETH_CFG_CAF;
 	} else {
 		cfg &= ~ETH_CFG_CAF;
@@ -937,9 +1113,9 @@ cemac_setaddr(struct ifnet *ifp)
 
 	// ETH_CFG_BIG?
 
-	ifp->if_flags &= ~IFF_ALLMULTI;
-
 	ETHER_LOCK(ec);
+	ec->ec_flags &= ~ETHER_F_ALLMULTI;
+
 	ETHER_FIRST_MULTI(step, ec, enm);
 	while (enm != NULL) {
 		if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
@@ -954,8 +1130,8 @@ cemac_setaddr(struct ifnet *ifp)
 			cfg |= ETH_CFG_MTI;
 			hashes[0] = 0xffffffffUL;
 			hashes[1] = 0xffffffffUL;
-			ifp->if_flags |= IFF_ALLMULTI;
 			nma = 0;
+			ec->ec_flags |= ETHER_F_ALLMULTI;
 			break;
 		}
 

Index: src/sys/dev/cadence/if_cemacvar.h
diff -u src/sys/dev/cadence/if_cemacvar.h:1.5 src/sys/dev/cadence/if_cemacvar.h:1.6
--- src/sys/dev/cadence/if_cemacvar.h:1.5	Sun Sep 29 09:13:14 2024
+++ src/sys/dev/cadence/if_cemacvar.h	Sat Oct  5 07:37:22 2024
@@ -1,4 +1,4 @@
-/*      $NetBSD: if_cemacvar.h,v 1.5 2024/09/29 09:13:14 skrll Exp $	*/
+/*      $NetBSD: if_cemacvar.h,v 1.6 2024/10/05 07:37:22 skrll Exp $	*/
 /*-
  * Copyright (c) 2015  Genetec Corporation.  All rights reserved.
  * Written by Hashimoto Kenichi for Genetec Corporation.
@@ -72,7 +72,19 @@ struct cemac_softc {
 	unsigned		cemac_flags;
 #define CEMAC_FLAG_GEM	__BIT(0)
 
-	bool			sc_txbusy;
+	kmutex_t *sc_mcast_lock;	/* m: lock for SIOCADD/DELMULTI */
+	kmutex_t *sc_intr_lock;		/* i: lock for interrupt operations */
+
+	u_short sc_if_flags;		/* m: if_flags cache */
+	time_t sc_tx_lastsent;		/* i: time of last tx */
+	bool sc_txbusy;			/* i: no Tx because down or busy */
+	bool sc_stopping;		/* i: ignore intr because down */
+	bool sc_tx_sending;		/* i: expecting tx complete irq */
+	bool sc_trigger_reset;		/* i */
+
+	struct workqueue *sc_reset_wq;
+	struct work sc_reset_work;	/* i */
+	volatile unsigned sc_reset_pending;
 };
 
 int cemac_intr(void *);

Reply via email to