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 *);