tech-net archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

if_txtimer API to replace (*if_watchdog)()



Folks --

The legacy (*if_watchdog)() interface has a couple of problems:

1- It does not have any way to represent multiple hardware-managed transmit queues.

2- It's not easy to make MP-safe because it relies on modifying the ifnet structure periodically outside of the normal locking mechanisms.

The wm(4) driver solved the problem in a reasonable way, and to make it easier for the other drivers in the tree to adopt it's strategy, I re-factored it into a new if_txtimer structure and API.

So save typing, I'll paste the relevant section of <net/if.h>:

/*
 * if_txtimer --
 *
 * Transmission timer (to replace the legacy ifnet watchdog timer).
 *
 * The driver should allocate one if_txtimer per hardware-managed
 * transmission queue and initialize it with if_txtimer_init(). The
 * driver should also arrange to have a callout invoked at a regular
 * interval (this may be coindidental with a timer used to check
 * link status).
 *
 * When the driver gives packets to the hardware to transmit, it should
 * arm the timer by calling if_txtimer_arm().  When it is sweeping up
 * completed transmit jobs, it should disarm the timer by calling
 * if_txtimer_disarm() if there are no outstanding jobs remaining.
 *
 * In the periodic callout, the driver should check if the timer has
 * expired by calling if_txtimer_is_expired().  It is not necessary to
 * check if the timer is armed when checking for expiration.  If the
 * timer has is expired, then the transmission has timed out and the
 * driver should take corrective action to recover from the error.
 *
 * If the driver needs to check multiple transmission queues, an
 * optimization is available that avoids repeated calls to fetch
 * the compare time.  In this case, the driver can get the compare
 * time by calling if_txtimer_now() and can check for timer expiration
 * using if_txtimer_is_expired_explicit().
 *
 * The granularity of the if_txtimer is 1 second.
 *
 * Locking: All locking of the if_txtimer is the responsibility of
 * the driver.  The if_txtimer should be protected by the same lock
 * that protects the associated transmission queue.
 */

See the diff for complete details.  Included is a conversion of wm(4) (which uses the if_txtimer_is_expired_explicit() variant), and pcn(4) (which uses the simpler if_txtimer_is_expired() variant).  The diff for pcn(4) looks artificially large because I relocated to pcn_txtimer_check() its sole call site after I renamed it from pcn_watchdog().

My plan is to get the new API in and start updating drivers to use the new mechanism.  The changes will be basically mechanical.  Before I get started on that, I'd like feedback on the proposed API.

Thx.

Index: net/if.c
===================================================================
RCS file: /cvsroot/src/sys/net/if.c,v
retrieving revision 1.468
diff -u -p -r1.468 if.c
--- net/if.c	20 Jan 2020 18:38:18 -0000	1.468
+++ net/if.c	20 Jan 2020 22:39:20 -0000
@@ -3774,6 +3774,91 @@ if_mcast_op(ifnet_t *ifp, const unsigned
 	return rc;
 }
 
+/*
+ * if_txtimer_init --
+ *	Initialize a network interface transmit timer.
+ */
+void
+if_txtimer_init(struct if_txtimer * const txt, unsigned int timeout)
+{
+
+	memset(txt, 0, sizeof(*txt));
+	txt->txt_timeout = timeout;
+}
+
+/*
+ * if_txtimer_arm --
+ *	Arm a network interface transmit timer.
+ */
+void
+if_txtimer_arm(struct if_txtimer * const txt)
+{
+	const time_t current_time = time_uptime;
+
+	txt->txt_armtime = current_time;
+	txt->txt_armed = true;
+}
+
+/*
+ * if_txtimer_disarm --
+ *	Disarm a network interface transmit timer.
+ */
+void
+if_txtimer_disarm(struct if_txtimer * const txt)
+{
+
+	txt->txt_armed = false;
+}
+
+/*
+ * if_txtimer_is_armed --
+ *	Return if a network interface transmit timer is armed.
+ */
+bool
+if_txtimer_is_armed(const struct if_txtimer * const txt)
+{
+
+	return txt->txt_armed;
+}
+
+/*
+ * if_txtimer_now --
+ *	Return the current value of "now" for the purpose of
+ *	checking for transmit timer expiration.
+ */
+time_t
+if_txtimer_now(void)
+{
+
+	return time_uptime;
+}
+
+/*
+ * if_txtimer_is_expired_explicit --
+ *	Return if a network interface transmit timer has expired,
+ *	using an explicit time.
+ */
+bool
+if_txtimer_is_expired_explicit(const struct if_txtimer * const txt,
+			       const time_t current_time)
+{
+
+	return txt->txt_armed &&
+	    (current_time - txt->txt_armtime) > txt->txt_timeout;
+}
+
+/*
+ * if_txtimer_is_expired --
+ *	Return if a network interface transmit timer has expired.
+ */
+bool
+if_txtimer_is_expired(const struct if_txtimer * const txt)
+{
+	const time_t current_time = time_uptime;
+
+	return if_txtimer_is_expired_explicit(txt, current_time);
+}
+
 static void
 sysctl_sndq_setup(struct sysctllog **clog, const char *ifname,
     struct ifaltq *ifq)
Index: net/if.h
===================================================================
RCS file: /cvsroot/src/sys/net/if.h,v
retrieving revision 1.277
diff -u -p -r1.277 if.h
--- net/if.h	19 Sep 2019 06:07:24 -0000	1.277
+++ net/if.h	20 Jan 2020 22:39:20 -0000
@@ -1182,6 +1182,54 @@ bool	ifa_is_destroying(struct ifaddr *);
 void	ifaref(struct ifaddr *);
 void	ifafree(struct ifaddr *);
 
+/*
+ * if_txtimer --
+ *
+ * Transmission timer (to replace the legacy ifnet watchdog timer).
+ *
+ * The driver should allocate one if_txtimer per hardware-managed
+ * transmission queue and initialize it with if_txtimer_init(). The
+ * driver should also arrange to have a callout invoked at a regular
+ * interval (this may be coindidental with a timer used to check
+ * link status).
+ *
+ * When the driver gives packets to the hardware to transmit, it should
+ * arm the timer by calling if_txtimer_arm().  When it is sweeping up
+ * completed transmit jobs, it should disarm the timer by calling
+ * if_txtimer_disarm() if there are no outstanding jobs remaining.
+ *
+ * In the periodic callout, the driver should check if the timer has
+ * expired by calling if_txtimer_is_expired().  It is not necessary to
+ * check if the timer is armed when checking for expiration.  If the
+ * timer has is expired, then the transmission has timed out and the
+ * driver should take corrective action to recover from the error.
+ *
+ * If the driver needs to check multiple transmission queues, an
+ * optimization is available that avoids repeated calls to fetch
+ * the compare time.  In this case, the driver can get the compare
+ * time by calling if_txtimer_now() and can check for timer expiration
+ * using if_txtimer_is_expired_explicit().
+ *
+ * The granularity of the if_txtimer is 1 second.
+ *
+ * Locking: All locking of the if_txtimer is the responsibility of
+ * the driver.  The if_txtimer should be protected by the same lock
+ * that protects the associated transmission queue.
+ */
+struct if_txtimer {
+	time_t		txt_armtime;
+	unsigned int	txt_timeout;
+	bool		txt_armed;
+};
+
+void	if_txtimer_init(struct if_txtimer *, unsigned int);
+void	if_txtimer_arm(struct if_txtimer *);
+void	if_txtimer_disarm(struct if_txtimer *);
+bool	if_txtimer_is_armed(const struct if_txtimer *);
+time_t	if_txtimer_now(void);
+bool	if_txtimer_is_expired_explicit(const struct if_txtimer *, const time_t);
+bool	if_txtimer_is_expired(const struct if_txtimer *);
+
 struct	ifaddr *ifa_ifwithaddr(const struct sockaddr *);
 struct	ifaddr *ifa_ifwithaddr_psref(const struct sockaddr *, struct psref *);
 struct	ifaddr *ifa_ifwithaf(int);
Index: dev/pci/if_pcn.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/if_pcn.c,v
retrieving revision 1.72
diff -u -p -r1.72 if_pcn.c
--- dev/pci/if_pcn.c	11 Oct 2019 14:22:46 -0000	1.72
+++ dev/pci/if_pcn.c	20 Jan 2020 22:39:21 -0000
@@ -296,6 +296,8 @@ struct pcn_softc {
 	int sc_flags;			/* misc. flags; see below */
 	int sc_swstyle;			/* the software style in use */
 
+	struct if_txtimer sc_txtimer;	/* transmit watchdog timer */
+
 	int sc_txfree;			/* number of free Tx descriptors */
 	int sc_txnext;			/* next ready Tx descriptor */
 
@@ -381,7 +383,6 @@ do {									\
 } while(/*CONSTCOND*/0)
 
 static void	pcn_start(struct ifnet *);
-static void	pcn_watchdog(struct ifnet *);
 static int	pcn_ioctl(struct ifnet *, u_long, void *);
 static int	pcn_init(struct ifnet *);
 static void	pcn_stop(struct ifnet *, int);
@@ -806,9 +807,9 @@ pcn_attach(device_t parent, device_t sel
 	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
 	ifp->if_ioctl = pcn_ioctl;
 	ifp->if_start = pcn_start;
-	ifp->if_watchdog = pcn_watchdog;
 	ifp->if_init = pcn_init;
 	ifp->if_stop = pcn_stop;
+	if_txtimer_init(&sc->sc_txtimer, 5);
 	IFQ_SET_READY(&ifp->if_snd);
 
 	/* Attach the interface. */
@@ -1140,37 +1141,8 @@ pcn_start(struct ifnet *ifp)
 
 	if (sc->sc_txfree != ofree) {
 		/* Set a watchdog timer in case the chip flakes out. */
-		ifp->if_timer = 5;
-	}
-}
-
-/*
- * pcn_watchdog:	[ifnet interface function]
- *
- *	Watchdog timer handler.
- */
-static void
-pcn_watchdog(struct ifnet *ifp)
-{
-	struct pcn_softc *sc = ifp->if_softc;
-
-	/*
-	 * Since we're not interrupting every packet, sweep
-	 * up before we report an error.
-	 */
-	pcn_txintr(sc);
-
-	if (sc->sc_txfree != PCN_NTXDESC) {
-		printf("%s: device timeout (txfree %d txsfree %d)\n",
-		    device_xname(sc->sc_dev), sc->sc_txfree, sc->sc_txsfree);
-		ifp->if_oerrors++;
-
-		/* Reset the interface. */
-		(void) pcn_init(ifp);
+		if_txtimer_arm(&sc->sc_txtimer);
 	}
-
-	/* Try to get more packets going. */
-	pcn_start(ifp);
 }
 
 /*
@@ -1412,7 +1384,7 @@ pcn_txintr(struct pcn_softc *sc)
 	 * timer.
 	 */
 	if (sc->sc_txsfree == PCN_TXQUEUELEN)
-		ifp->if_timer = 0;
+		if_txtimer_disarm(&sc->sc_txtimer);
 }
 
 /*
@@ -1553,6 +1525,39 @@ pcn_rxintr(struct pcn_softc *sc)
 }
 
 /*
+ * pcn_txtimer_check:
+ *
+ *	Tx timer to check for stalled transmitter.
+ */
+static void
+pcn_txtimer_check(struct pcn_softc * const sc)
+{
+	struct ifnet * const ifp = &sc->sc_ethercom.ec_if;
+
+	if (__predict_true(!if_txtimer_is_expired(&sc->sc_txtimer))) {
+		return;
+	}
+
+	/*
+	 * Since we're not interrupting every packet, sweep
+	 * up before we report an error.
+	 */
+	pcn_txintr(sc);
+
+	if (__predict_false(sc->sc_txfree != PCN_NTXDESC)) {
+		printf("%s: device timeout (txfree %d txsfree %d)\n",
+		    device_xname(sc->sc_dev), sc->sc_txfree, sc->sc_txsfree);
+		ifp->if_oerrors++;
+
+		/* Reset the interface. */
+		(void) pcn_init(ifp);
+	}
+
+	/* Try to get more packets going. */
+	pcn_start(ifp);
+}
+
+/*
  * pcn_tick:
  *
  *	One second timer, used to tick the MII.
@@ -1564,7 +1569,13 @@ pcn_tick(void *arg)
 	int s;
 
 	s = splnet();
-	mii_tick(&sc->sc_mii);
+
+	if (__predict_true(sc->sc_flags & PCN_F_HAS_MII)) {
+		mii_tick(&sc->sc_mii);
+	}
+
+	pcn_txtimer_check(sc);
+
 	splx(s);
 
 	callout_reset(&sc->sc_tick_ch, hz, pcn_tick, sc);
@@ -1809,10 +1820,8 @@ pcn_init(struct ifnet *ifp)
 	/* Enable interrupts and external activity (and ACK IDON). */
 	pcn_csr_write(sc, LE_CSR0, LE_C0_INEA | LE_C0_STRT | LE_C0_IDON);
 
-	if (sc->sc_flags & PCN_F_HAS_MII) {
-		/* Start the one second MII clock. */
-		callout_reset(&sc->sc_tick_ch, hz, pcn_tick, sc);
-	}
+	/* Start the one second MII clock. */
+	callout_reset(&sc->sc_tick_ch, hz, pcn_tick, sc);
 
 	/* ...all done! */
 	ifp->if_flags |= IFF_RUNNING;
@@ -1880,7 +1889,7 @@ pcn_stop(struct ifnet *ifp, int disable)
 
 	/* Mark the interface as down and cancel the watchdog timer. */
 	ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
-	ifp->if_timer = 0;
+	if_txtimer_disarm(&sc->sc_txtimer);
 
 	if (disable)
 		pcn_rxdrain(sc);
Index: dev/pci/if_wm.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/if_wm.c,v
retrieving revision 1.659
diff -u -p -r1.659 if_wm.c
--- dev/pci/if_wm.c	20 Jan 2020 19:45:27 -0000	1.659
+++ dev/pci/if_wm.c	20 Jan 2020 22:39:21 -0000
@@ -370,8 +370,7 @@ struct wm_txqueue {
 
 	bool txq_stopping;
 
-	bool txq_sending;
-	time_t txq_lastsent;
+	struct if_txtimer txq_txtimer;
 
 	uint32_t txq_packets;		/* for AIM */
 	uint32_t txq_bytes;		/* for AIM */
@@ -704,9 +703,9 @@ static bool	wm_suspend(device_t, const p
 static bool	wm_resume(device_t, const pmf_qual_t *);
 static void	wm_watchdog(struct ifnet *);
 static void	wm_watchdog_txq(struct ifnet *, struct wm_txqueue *,
-    uint16_t *);
+				const time_t, uint16_t *);
 static void	wm_watchdog_txq_locked(struct ifnet *, struct wm_txqueue *,
-    uint16_t *);
+				       uint16_t *);
 static void	wm_tick(void *);
 static int	wm_ifflags_cb(struct ethercom *);
 static int	wm_ioctl(struct ifnet *, u_long, void *);
@@ -3148,10 +3147,12 @@ wm_watchdog(struct ifnet *ifp)
 	struct wm_softc *sc = ifp->if_softc;
 	uint16_t hang_queue = 0; /* Max queue number of wm(4) is 82576's 16. */
 
+	const time_t txq_now = if_txtimer_now();
+
 	for (qid = 0; qid < sc->sc_nqueues; qid++) {
 		struct wm_txqueue *txq = &sc->sc_queue[qid].wmq_txq;
 
-		wm_watchdog_txq(ifp, txq, &hang_queue);
+		wm_watchdog_txq(ifp, txq, txq_now, &hang_queue);
 	}
 
 	/* IF any of queues hanged up, reset the interface. */
@@ -3169,14 +3170,13 @@ wm_watchdog(struct ifnet *ifp)
 
 
 static void
-wm_watchdog_txq(struct ifnet *ifp, struct wm_txqueue *txq, uint16_t *hang)
+wm_watchdog_txq(struct ifnet *ifp, struct wm_txqueue *txq,
+		const time_t txq_now, uint16_t *hang)
 {
 
 	mutex_enter(txq->txq_lock);
-	if (txq->txq_sending &&
-	    time_uptime - txq->txq_lastsent > wm_watchdog_timeout)
+	if (if_txtimer_is_expired_explicit(&txq->txq_txtimer, txq_now))
 		wm_watchdog_txq_locked(ifp, txq, hang);
-
 	mutex_exit(txq->txq_lock);
 }
 
@@ -3195,7 +3195,7 @@ wm_watchdog_txq_locked(struct ifnet *ifp
 	 */
 	wm_txeof(txq, UINT_MAX);
 
-	if (txq->txq_sending)
+	if (if_txtimer_is_armed(&txq->txq_txtimer))
 		*hang |= __BIT(wmq->wmq_id);
 
 	if (txq->txq_free == WM_NTXDESC(txq)) {
@@ -6356,7 +6356,7 @@ wm_stop_locked(struct ifnet *ifp, int di
 		struct wm_queue *wmq = &sc->sc_queue[qidx];
 		struct wm_txqueue *txq = &wmq->wmq_txq;
 		mutex_enter(txq->txq_lock);
-		txq->txq_sending = false; /* Ensure watchdog disabled */
+		if_txtimer_disarm(&txq->txq_txtimer);
 		for (i = 0; i < WM_TXQUEUELEN(txq); i++) {
 			txs = &txq->txq_soft[i];
 			if (txs->txs_mbuf != NULL) {
@@ -6769,6 +6769,7 @@ wm_alloc_txrx_queues(struct wm_softc *sc
 		struct wm_txqueue *txq = &sc->sc_queue[i].wmq_txq;
 		txq->txq_sc = sc;
 		txq->txq_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_NET);
+		if_txtimer_init(&txq->txq_txtimer, wm_watchdog_timeout);
 
 		error = wm_alloc_tx_descs(sc, txq);
 		if (error)
@@ -7058,7 +7059,7 @@ wm_init_tx_queue(struct wm_softc *sc, st
 	wm_init_tx_buffer(sc, txq);
 
 	txq->txq_flags = 0; /* Clear WM_TXQ_NO_SPACE */
-	txq->txq_sending = false;
+	if_txtimer_disarm(&txq->txq_txtimer);
 }
 
 static void
@@ -7833,8 +7834,7 @@ retry:
 
 	if (txq->txq_free != ofree) {
 		/* Set a watchdog timer in case the chip flakes out. */
-		txq->txq_lastsent = time_uptime;
-		txq->txq_sending = true;
+		if_txtimer_arm(&txq->txq_txtimer);
 	}
 }
 
@@ -8417,8 +8417,7 @@ retry:
 
 	if (sent) {
 		/* Set a watchdog timer in case the chip flakes out. */
-		txq->txq_lastsent = time_uptime;
-		txq->txq_sending = true;
+		if_txtimer_arm(&txq->txq_txtimer);
 	}
 }
 
@@ -8574,7 +8573,7 @@ wm_txeof(struct wm_txqueue *txq, u_int l
 	 * timer.
 	 */
 	if (txq->txq_sfree == WM_TXQUEUELEN(txq))
-		txq->txq_sending = false;
+		if_txtimer_disarm(&txq->txq_txtimer);
 
 	return more;
 }
-- thorpej



Home | Main Index | Thread Index | Old Index