git: 02880ddf9ed0 - main - thunderbolt: Router suspend routine

From: Aymeric Wibo <obiwac_at_FreeBSD.org>
Date: Fri, 09 Oct 2026 17:33:27 UTC
The branch main has been updated by obiwac:

URL: https://cgit.FreeBSD.org/src/commit/?id=02880ddf9ed01a4b3444239309f522309ef11043

commit 02880ddf9ed01a4b3444239309f522309ef11043
Author:     Aymeric Wibo <obiwac@FreeBSD.org>
AuthorDate: 2025-04-29 20:42:56 +0000
Commit:     Aymeric Wibo <obiwac@FreeBSD.org>
CommitDate: 2026-10-09 17:32:34 +0000

    thunderbolt: Router suspend routine
    
    Suspend routine for USB4 v1.0 NHIs. This will work on v2.0 NHIs too for
    now, but there's ideally a different method we should be using for those
    in fine.
    
    Reviewed by:    imp, ngie, emaste
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D49453
---
 sys/dev/thunderbolt/nhi.c        | 13 +++++++
 sys/dev/thunderbolt/nhi_pci.c    |  6 ++--
 sys/dev/thunderbolt/nhi_reg.h    |  5 +++
 sys/dev/thunderbolt/nhi_var.h    |  2 ++
 sys/dev/thunderbolt/router.c     | 78 ++++++++++++++++++++++++++++++++++++++++
 sys/dev/thunderbolt/router_var.h | 11 ++++++
 6 files changed, 113 insertions(+), 2 deletions(-)

diff --git a/sys/dev/thunderbolt/nhi.c b/sys/dev/thunderbolt/nhi.c
index e8221136a8e5..a01a01cca92c 100644
--- a/sys/dev/thunderbolt/nhi.c
+++ b/sys/dev/thunderbolt/nhi.c
@@ -387,6 +387,19 @@ nhi_detach(struct nhi_softc *sc)
 	return (0);
 }
 
+int
+nhi_suspend(struct nhi_softc *sc)
+{
+	return (tb_router_suspend(sc->root_rsc));
+}
+
+int
+nhi_resume(struct nhi_softc *sc)
+{
+	/* TODO(Bug 299199): fully flesh out the implementation. */
+	return (0);
+}
+
 static void
 nhi_memaddr_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
 {
diff --git a/sys/dev/thunderbolt/nhi_pci.c b/sys/dev/thunderbolt/nhi_pci.c
index 777f8dd5352e..0ba452132523 100644
--- a/sys/dev/thunderbolt/nhi_pci.c
+++ b/sys/dev/thunderbolt/nhi_pci.c
@@ -178,15 +178,17 @@ nhi_pci_detach(device_t dev)
 static int
 nhi_pci_suspend(device_t dev)
 {
+	struct nhi_softc *sc = device_get_softc(dev);
 
-	return (0);
+	return (nhi_suspend(sc));
 }
 
 static int
 nhi_pci_resume(device_t dev)
 {
+	struct nhi_softc *sc = device_get_softc(dev);
 
-	return (0);
+	return (nhi_resume(sc));
 }
 
 static void
diff --git a/sys/dev/thunderbolt/nhi_reg.h b/sys/dev/thunderbolt/nhi_reg.h
index a0c13c741554..0adfeccd76b9 100644
--- a/sys/dev/thunderbolt/nhi_reg.h
+++ b/sys/dev/thunderbolt/nhi_reg.h
@@ -52,6 +52,11 @@
 #define DEVICE_PINK_SARDINE_0	0x1668
 #define DEVICE_PINK_SARDINE_1	0x1669
 
+/* Timings */
+#define NHI_SLPR_WAIT_US	50000 /* 50 ms, tSetSR */
+#define NHI_SLPR_POLL_TRIES	100
+#define NHI_SLPR_POLL_US	(NHI_SLPR_WAIT_US / NHI_SLPR_POLL_TRIES)
+
 /* * * MMIO Registers
  * * Ring buffer registers
  *
diff --git a/sys/dev/thunderbolt/nhi_var.h b/sys/dev/thunderbolt/nhi_var.h
index 6acac4ed0fa6..026bca7c99a0 100644
--- a/sys/dev/thunderbolt/nhi_var.h
+++ b/sys/dev/thunderbolt/nhi_var.h
@@ -210,6 +210,8 @@ int nhi_write_lc_mailbox(struct nhi_softc *, u_int reg, uint32_t val);
 void nhi_get_tunables(struct nhi_softc *);
 int nhi_attach(struct nhi_softc *);
 int nhi_detach(struct nhi_softc *);
+int nhi_suspend(struct nhi_softc *);
+int nhi_resume(struct nhi_softc *);
 
 struct nhi_cmd_frame * nhi_alloc_tx_frame(struct nhi_ring_pair *);
 void nhi_free_tx_frame(struct nhi_ring_pair *, struct nhi_cmd_frame *);
diff --git a/sys/dev/thunderbolt/router.c b/sys/dev/thunderbolt/router.c
index e35c712e4a00..c4141e22ed20 100644
--- a/sys/dev/thunderbolt/router.c
+++ b/sys/dev/thunderbolt/router.c
@@ -252,6 +252,7 @@ tb_router_attach_root(struct nhi_softc *nsc, tb_route_t route)
 	sc->ring0 = nsc->ring0;
 	sc->route = route;
 	sc->nsc = nsc;
+	sc->suspended = false;
 
 	mtx_init(&sc->mtx, "tbcfg", "Thunderbolt Router Config", MTX_DEF);
 	TAILQ_INIT(&sc->cmd_queue);
@@ -345,6 +346,83 @@ tb_router_detach(struct router_softc *sc)
 	return (0);
 }
 
+/*
+ * Suspend a USB4 router and it's downstreams.
+ *
+ * TODO(Bug 299199): fully flesh out the implementation.
+ */
+int
+tb_router_suspend(struct router_softc *sc)
+{
+	int		err;
+	uint32_t	reg;
+
+	tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "%s called\n", __func__);
+	if (sc->suspended) {
+		tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "Already suspended\n");
+		return (0);
+	}
+
+	/*
+	 * TODO Before we do anything, we've first got to make sure that the
+	 * USB3 hub is in the U3 state, and the PCIe endpoint is in D3.
+	 *
+	 * Also check for "USB4 Port is Configured" to know if we support
+	 * sleep state.
+	 */
+
+	/* First, we've got to set ROUTER_CS_5.SLP (enter sleep). */
+	err = tb_config_router_read(sc, ROUTER_CS_5, 1, &reg);
+	if (err != 0) {
+		tb_debug(sc, DBG_ROUTER, "Cannot read ROUTER_CS5\n");
+		return (err);
+	}
+	/*
+	 * We want to set the enter sleep bit, as well as preventing wake
+	 * events from:
+	 * - Wake on PCIe (WoP).
+	 * - Wake on USB3 (WoU).
+	 * - Wake on DisplayPort (WoD).
+	 * TODO Eventually, we will want the user to be able to control this.
+	 */
+	reg |= ROUTER_SLP;
+	reg &= ~(ROUTER_WOP | ROUTER_WOU | ROUTER_WOD);
+	err = tb_config_router_write(sc, ROUTER_CS_5, 1, &reg);
+	if (err != 0) {
+		tb_debug(sc, DBG_ROUTER, "Cannot write to ROUTER_CS5\n");
+		return (err);
+	}
+
+	/*
+	 * The ROUTER_CS_6.SLPR (sleep ready) bit should be set tSetSR after
+	 * we set the SLP bit.  Poll for it to be set.
+	 *
+	 * TODO On a v2 router, we should wait for the ROP_CMPLT notification,
+	 * but in the meantime just polling is also valid.
+	 */
+	for (size_t i = 0; i < NHI_SLPR_POLL_TRIES; i++) {
+		pause_sbt("tbrouter", ustosbt(NHI_SLPR_POLL_US), 0, 0);
+		err = tb_config_router_read(sc, ROUTER_CS_6, 1, &reg);
+		if (err != 0) {
+			tb_debug(sc, DBG_ROUTER, "Cannot read ROUTER_CS6\n");
+			return (err);
+		}
+		if ((reg & ROUTER_SLPR) != 0)
+			goto ready;
+	}
+	tb_printf(sc, "Timed out waiting for the sleep ready bit to be set\n");
+	return (ETIMEDOUT);
+
+ready:
+	tb_printf(sc, "Ready to enter sleep\n");
+	sc->suspended = true;
+	/*
+	 * TODO We must tell the host router to send LT_LRoff on the sideband
+	 * channel of each DFP.
+	 */
+	return (0);
+}
+
 static void
 router_get_config_cb(struct router_softc *sc, struct router_command *cmd,
     void *arg)
diff --git a/sys/dev/thunderbolt/router_var.h b/sys/dev/thunderbolt/router_var.h
index a733c36d401e..7c86086974c2 100644
--- a/sys/dev/thunderbolt/router_var.h
+++ b/sys/dev/thunderbolt/router_var.h
@@ -59,6 +59,7 @@ struct router_softc {
 	tb_route_t		route;
 	device_t		dev;
 	struct nhi_softc	*nsc;
+	bool			suspended;
 
 	struct mtx		mtx;
 	struct nhi_ring_pair	*ring0;
@@ -89,9 +90,19 @@ struct router_cfg_cap {
 #define ROUTER_HRR	0x39898
 #define ROUTER_HIR	0x39858
 
+/* Router config space registers */
+#define ROUTER_CS_5	5
+#define ROUTER_SLP	(1<<0)	/* Enter sleep (CS5). */
+#define ROUTER_WOP	(1<<1)	/* Wake on PCIe (CS5). */
+#define ROUTER_WOU	(1<<2)	/* Wake on USB3 (CS5). */
+#define ROUTER_WOD	(1<<3)	/* Wake on DP (CS5). */
+#define ROUTER_CS_6	6
+#define ROUTER_SLPR	(1<<0)	/* Sleep ready (CS6). */
+
 int tb_router_attach(struct router_softc *, tb_route_t);
 int tb_router_attach_root(struct nhi_softc *, tb_route_t);
 int tb_router_detach(struct router_softc *);
+int tb_router_suspend(struct router_softc *);
 int tb_config_read(struct router_softc *, u_int, u_int, u_int, u_int,
     uint32_t *);
 int tb_config_read_polled(struct router_softc *, u_int, u_int, u_int, u_int,