aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/thunderbolt
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/thunderbolt')
-rw-r--r--sys/dev/thunderbolt/hcm.c223
-rw-r--r--sys/dev/thunderbolt/hcm_var.h47
-rw-r--r--sys/dev/thunderbolt/nhi.c1170
-rw-r--r--sys/dev/thunderbolt/nhi_pci.c529
-rw-r--r--sys/dev/thunderbolt/nhi_reg.h332
-rw-r--r--sys/dev/thunderbolt/nhi_var.h277
-rw-r--r--sys/dev/thunderbolt/nhi_wmi.c198
-rw-r--r--sys/dev/thunderbolt/router.c939
-rw-r--r--sys/dev/thunderbolt/router_var.h242
-rw-r--r--sys/dev/thunderbolt/tb_acpi_pcib.c181
-rw-r--r--sys/dev/thunderbolt/tb_debug.c334
-rw-r--r--sys/dev/thunderbolt/tb_debug.h93
-rw-r--r--sys/dev/thunderbolt/tb_dev.c331
-rw-r--r--sys/dev/thunderbolt/tb_dev.h41
-rw-r--r--sys/dev/thunderbolt/tb_if.m121
-rw-r--r--sys/dev/thunderbolt/tb_ioctl.h52
-rw-r--r--sys/dev/thunderbolt/tb_pcib.c614
-rw-r--r--sys/dev/thunderbolt/tb_pcib.h93
-rw-r--r--sys/dev/thunderbolt/tb_reg.h52
-rw-r--r--sys/dev/thunderbolt/tb_var.h54
-rw-r--r--sys/dev/thunderbolt/tbcfg_reg.h363
21 files changed, 6286 insertions, 0 deletions
diff --git a/sys/dev/thunderbolt/hcm.c b/sys/dev/thunderbolt/hcm.c
new file mode 100644
index 000000000000..b8f703fc3b52
--- /dev/null
+++ b/sys/dev/thunderbolt/hcm.c
@@ -0,0 +1,223 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_thunderbolt.h"
+
+/* Host Configuration Manager (HCM) for USB4 and later TB3 */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/taskqueue.h>
+#include <sys/gsb_crc32.h>
+#include <sys/endian.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/bus.h>
+#include <machine/stdarg.h>
+
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_var.h>
+#include <dev/thunderbolt/tb_debug.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/router_var.h>
+#include <dev/thunderbolt/hcm_var.h>
+
+static void hcm_cfg_task(void *, int);
+
+int
+hcm_attach(struct nhi_softc *nsc)
+{
+ struct hcm_softc *hcm;
+
+ tb_debug(nsc, DBG_HCM|DBG_EXTRA, "hcm_attach called\n");
+
+ hcm = malloc(sizeof(struct hcm_softc), M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+ if (hcm == NULL) {
+ tb_debug(nsc, DBG_HCM, "Cannot allocate hcm object\n");
+ return (ENOMEM);
+ }
+
+ hcm->dev = nsc->dev;
+ hcm->nsc = nsc;
+ nsc->hcm = hcm;
+
+ hcm->taskqueue = taskqueue_create("hcm_event", M_NOWAIT,
+ taskqueue_thread_enqueue, &hcm->taskqueue);
+ if (hcm->taskqueue == NULL)
+ return (ENOMEM);
+ taskqueue_start_threads(&hcm->taskqueue, 1, PI_DISK, "tbhcm%d_tq",
+ device_get_unit(nsc->dev));
+ TASK_INIT(&hcm->cfg_task, 0, hcm_cfg_task, hcm);
+
+ return (0);
+}
+
+int
+hcm_detach(struct nhi_softc *nsc)
+{
+ struct hcm_softc *hcm;
+
+ hcm = nsc->hcm;
+ if (hcm->taskqueue)
+ taskqueue_free(hcm->taskqueue);
+
+ return (0);
+}
+
+int
+hcm_router_discover(struct hcm_softc *hcm)
+{
+
+ taskqueue_enqueue(hcm->taskqueue, &hcm->cfg_task);
+
+ return (0);
+}
+
+static void
+hcm_cfg_task(void *arg, int pending)
+{
+ struct hcm_softc *hcm;
+ struct router_softc *rsc;
+ struct router_cfg_cap cap;
+ struct tb_cfg_router *cfg;
+ struct tb_cfg_adapter *adp;
+ struct tb_cfg_cap_lane *lane;
+ uint32_t *buf;
+ uint8_t *u;
+ u_int error, i, offset;
+
+ hcm = (struct hcm_softc *)arg;
+
+ tb_debug(hcm, DBG_HCM|DBG_EXTRA, "hcm_cfg_task called\n");
+
+ buf = malloc(8 * 4, M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+ if (buf == NULL) {
+ tb_debug(hcm, DBG_HCM, "Cannot alloc memory for discovery\n");
+ return;
+ }
+
+ rsc = hcm->nsc->root_rsc;
+ error = tb_config_router_read(rsc, 0, 5, buf);
+ if (error != 0) {
+ free(buf, M_NHI);
+ return;
+ }
+
+ cfg = (struct tb_cfg_router *)buf;
+
+ cap.space = TB_CFG_CS_ROUTER;
+ cap.adap = 0;
+ cap.next_cap = GET_ROUTER_CS_NEXT_CAP(cfg);
+ while (cap.next_cap != 0) {
+ error = tb_config_next_cap(rsc, &cap);
+ if (error != 0)
+ break;
+
+ if ((cap.cap_id == TB_CFG_CAP_VSEC) && (cap.vsc_len == 0)) {
+ tb_debug(hcm, DBG_HCM, "Router Cap= %d, vsec= %d, "
+ "len= %d, next_cap= %d\n", cap.cap_id,
+ cap.vsc_id, cap.vsec_len, cap.next_cap);
+ } else if (cap.cap_id == TB_CFG_CAP_VSC) {
+ tb_debug(hcm, DBG_HCM, "Router cap= %d, vsc= %d, "
+ "len= %d, next_cap= %d\n", cap.cap_id,
+ cap.vsc_id, cap.vsc_len, cap.next_cap);
+ } else
+ tb_debug(hcm, DBG_HCM, "Router cap= %d, "
+ "next_cap= %d\n", cap.cap_id, cap.next_cap);
+ if (cap.next_cap > TB_CFG_CAP_OFFSET_MAX)
+ cap.next_cap = 0;
+ }
+
+ u = (uint8_t *)buf;
+ error = tb_config_get_lc_uuid(rsc, u);
+ if (error == 0) {
+ tb_debug(hcm, DBG_HCM, "Router LC UUID: %02x%02x%02x%02x-"
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+ u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8],
+ u[9], u[10], u[11], u[12], u[13], u[14], u[15]);
+ } else
+ tb_printf(hcm, "Error finding LC registers: %d\n", error);
+
+ for (i = 1; i <= rsc->max_adap; i++) {
+ error = tb_config_adapter_read(rsc, i, 0, 8, buf);
+ if (error != 0) {
+ tb_debug(hcm, DBG_HCM, "Adapter %d: no adapter\n", i);
+ continue;
+ }
+ adp = (struct tb_cfg_adapter *)buf;
+ tb_debug(hcm, DBG_HCM, "Adapter %d: %s, max_counters= 0x%08x,"
+ " adapter_num= %d\n", i,
+ tb_get_string(GET_ADP_CS_TYPE(adp), tb_adapter_type),
+ GET_ADP_CS_MAX_COUNTERS(adp), GET_ADP_CS_ADP_NUM(adp));
+
+ if (GET_ADP_CS_TYPE(adp) != ADP_CS2_LANE)
+ continue;
+
+ error = tb_config_find_adapter_cap(rsc, i, TB_CFG_CAP_LANE,
+ &offset);
+ if (error)
+ continue;
+
+ error = tb_config_adapter_read(rsc, i, offset, 3, buf);
+ if (error)
+ continue;
+
+ lane = (struct tb_cfg_cap_lane *)buf;
+ tb_debug(hcm, DBG_HCM, "Lane Adapter State= %s %s\n",
+ tb_get_string((lane->current_lws & CAP_LANE_STATE_MASK),
+ tb_adapter_state), (lane->targ_lwp & CAP_LANE_DISABLE) ?
+ "disabled" : "enabled");
+
+ if ((lane->current_lws & CAP_LANE_STATE_MASK) ==
+ CAP_LANE_STATE_CL0) {
+ tb_route_t newr;
+
+ newr.hi = rsc->route.hi;
+ newr.lo = rsc->route.lo | (i << rsc->depth * 8);
+
+ tb_printf(hcm, "want to add router at 0x%08x%08x\n",
+ newr.hi, newr.lo);
+ error = tb_router_attach(rsc, newr);
+ tb_printf(rsc, "tb_router_attach returned %d\n", error);
+ }
+ }
+
+ free(buf, M_THUNDERBOLT);
+}
diff --git a/sys/dev/thunderbolt/hcm_var.h b/sys/dev/thunderbolt/hcm_var.h
new file mode 100644
index 000000000000..a11c8e9b6a92
--- /dev/null
+++ b/sys/dev/thunderbolt/hcm_var.h
@@ -0,0 +1,47 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HCM_VAR_H
+#define _HCM_VAR_H
+
+struct hcm_softc {
+ u_int debug;
+ device_t dev;
+ struct nhi_softc *nsc;
+
+ struct task cfg_task;
+ struct taskqueue *taskqueue;
+};
+
+int hcm_attach(struct nhi_softc *);
+int hcm_detach(struct nhi_softc *);
+int hcm_router_discover(struct hcm_softc *);
+
+#endif /* _HCM_VAR_H */
diff --git a/sys/dev/thunderbolt/nhi.c b/sys/dev/thunderbolt/nhi.c
new file mode 100644
index 000000000000..205e69c16253
--- /dev/null
+++ b/sys/dev/thunderbolt/nhi.c
@@ -0,0 +1,1170 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_thunderbolt.h"
+
+/* PCIe interface for Thunderbolt Native Host Interface (nhi) */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/taskqueue.h>
+#include <sys/gsb_crc32.h>
+#include <sys/endian.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/bus.h>
+#include <machine/stdarg.h>
+
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_var.h>
+#include <dev/thunderbolt/tb_debug.h>
+#include <dev/thunderbolt/hcm_var.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/router_var.h>
+#include <dev/thunderbolt/tb_dev.h>
+#include "tb_if.h"
+
+static int nhi_alloc_ring(struct nhi_softc *, int, int, int,
+ struct nhi_ring_pair **);
+static void nhi_free_ring(struct nhi_ring_pair *);
+static void nhi_free_rings(struct nhi_softc *);
+static int nhi_configure_ring(struct nhi_softc *, struct nhi_ring_pair *);
+static int nhi_activate_ring(struct nhi_ring_pair *);
+static int nhi_deactivate_ring(struct nhi_ring_pair *);
+static int nhi_alloc_ring0(struct nhi_softc *);
+static void nhi_free_ring0(struct nhi_softc *);
+static void nhi_fill_rx_ring(struct nhi_softc *, struct nhi_ring_pair *);
+static int nhi_init(struct nhi_softc *);
+static void nhi_post_init(void *);
+static int nhi_tx_enqueue(struct nhi_ring_pair *, struct nhi_cmd_frame *);
+static int nhi_setup_sysctl(struct nhi_softc *);
+
+SYSCTL_NODE(_hw, OID_AUTO, nhi, CTLFLAG_RD, 0, "NHI Driver Parameters");
+
+MALLOC_DEFINE(M_NHI, "nhi", "nhi driver memory");
+
+#ifndef NHI_DEBUG_LEVEL
+#define NHI_DEBUG_LEVEL 0
+#endif
+
+/* 0 = default, 1 = force-on, 2 = force-off */
+#ifndef NHI_FORCE_HCM
+#define NHI_FORCE_HCM 0
+#endif
+
+void
+nhi_get_tunables(struct nhi_softc *sc)
+{
+ devclass_t dc;
+ device_t ufp;
+ char tmpstr[80], oid[80];
+ u_int val;
+
+ /* Set local defaults */
+ sc->debug = NHI_DEBUG_LEVEL;
+ sc->max_ring_count = NHI_DEFAULT_NUM_RINGS;
+ sc->force_hcm = NHI_FORCE_HCM;
+
+ /* Inherit setting from the upstream thunderbolt switch node */
+ val = TB_GET_DEBUG(sc->dev, &sc->debug);
+ if (val != 0) {
+ dc = devclass_find("tbolt");
+ if (dc != NULL) {
+ ufp = devclass_get_device(dc, device_get_unit(sc->dev));
+ if (ufp != NULL)
+ TB_GET_DEBUG(ufp, &sc->debug);
+ } else {
+ if (TUNABLE_STR_FETCH("hw.tbolt.debug_level", oid,
+ 80) != 0)
+ tb_parse_debug(&sc->debug, oid);
+ }
+ }
+
+ /*
+ * Grab global variables. Allow nhi debug flags to override
+ * thunderbolt debug flags, if present.
+ */
+ bzero(oid, 80);
+ if (TUNABLE_STR_FETCH("hw.nhi.debug_level", oid, 80) != 0)
+ tb_parse_debug(&sc->debug, oid);
+ if (TUNABLE_INT_FETCH("hw.nhi.max_rings", &val) != 0) {
+ val = min(val, NHI_MAX_NUM_RINGS);
+ sc->max_ring_count = max(val, 1);
+ }
+ if (TUNABLE_INT_FETCH("hw.nhi.force_hcm", &val) != 0)
+ sc->force_hcm = val;
+
+ /* Grab instance variables */
+ bzero(oid, 80);
+ snprintf(tmpstr, sizeof(tmpstr), "dev.nhi.%d.debug_level",
+ device_get_unit(sc->dev));
+ if (TUNABLE_STR_FETCH(tmpstr, oid, 80) != 0)
+ tb_parse_debug(&sc->debug, oid);
+ snprintf(tmpstr, sizeof(tmpstr), "dev.nhi.%d.max_rings",
+ device_get_unit(sc->dev));
+ if (TUNABLE_INT_FETCH(tmpstr, &val) != 0) {
+ val = min(val, NHI_MAX_NUM_RINGS);
+ sc->max_ring_count = max(val, 1);
+ }
+ snprintf(tmpstr, sizeof(tmpstr), "dev, nhi.%d.force_hcm",
+ device_get_unit(sc->dev));
+ if (TUNABLE_INT_FETCH(tmpstr, &val) != 0)
+ sc->force_hcm = val;
+
+ return;
+}
+
+static void
+nhi_configure_caps(struct nhi_softc *sc)
+{
+
+ if (NHI_IS_USB4(sc) || (sc->force_hcm == NHI_FORCE_HCM_ON))
+ sc->caps |= NHI_CAP_HCM;
+ if (sc->force_hcm == NHI_FORCE_HCM_OFF)
+ sc->caps &= ~NHI_CAP_HCM;
+}
+
+struct nhi_cmd_frame *
+nhi_alloc_tx_frame(struct nhi_ring_pair *r)
+{
+ struct nhi_cmd_frame *cmd;
+
+ mtx_lock(&r->mtx);
+ cmd = nhi_alloc_tx_frame_locked(r);
+ mtx_unlock(&r->mtx);
+
+ return (cmd);
+}
+
+void
+nhi_free_tx_frame(struct nhi_ring_pair *r, struct nhi_cmd_frame *cmd)
+{
+ mtx_lock(&r->mtx);
+ nhi_free_tx_frame_locked(r, cmd);
+ mtx_unlock(&r->mtx);
+}
+
+/*
+ * Push a command and data dword through the mailbox to the firmware.
+ * Response is either good, error, or timeout. Commands that return data
+ * do so by reading OUTMAILDATA.
+ */
+int
+nhi_inmail_cmd(struct nhi_softc *sc, uint32_t cmd, uint32_t data)
+{
+ uint32_t val;
+ u_int error, timeout;
+
+ mtx_lock(&sc->nhi_mtx);
+ /*
+ * XXX Should a defer/reschedule happen here, or is it not worth
+ * worrying about?
+ */
+ if (sc->hwflags & NHI_MBOX_BUSY) {
+ mtx_unlock(&sc->nhi_mtx);
+ tb_debug(sc, DBG_MBOX, "Driver busy with mailbox\n");
+ return (EBUSY);
+ }
+ sc->hwflags |= NHI_MBOX_BUSY;
+
+ val = nhi_read_reg(sc, TBT_INMAILCMD);
+ tb_debug(sc, DBG_MBOX|DBG_FULL, "Reading INMAILCMD= 0x%08x\n", val);
+ if (val & INMAILCMD_ERROR)
+ tb_debug(sc, DBG_MBOX, "Error already set in INMAILCMD\n");
+ if (val & INMAILCMD_OPREQ) {
+ mtx_unlock(&sc->nhi_mtx);
+ tb_debug(sc, DBG_MBOX,
+ "INMAILCMD request already in progress\n");
+ return (EBUSY);
+ }
+
+ nhi_write_reg(sc, TBT_INMAILDATA, data);
+ nhi_write_reg(sc, TBT_INMAILCMD, cmd | INMAILCMD_OPREQ);
+
+ /* Poll at 1s intervals */
+ timeout = NHI_MAILBOX_TIMEOUT;
+ while (timeout--) {
+ DELAY(1000000);
+ val = nhi_read_reg(sc, TBT_INMAILCMD);
+ tb_debug(sc, DBG_MBOX|DBG_EXTRA,
+ "Polling INMAILCMD= 0x%08x\n", val);
+ if ((val & INMAILCMD_OPREQ) == 0)
+ break;
+ }
+ sc->hwflags &= ~NHI_MBOX_BUSY;
+ mtx_unlock(&sc->nhi_mtx);
+
+ error = 0;
+ if (val & INMAILCMD_OPREQ) {
+ tb_printf(sc, "Timeout waiting for mailbox\n");
+ error = ETIMEDOUT;
+ }
+ if (val & INMAILCMD_ERROR) {
+ tb_printf(sc, "Firmware reports error in mailbox\n");
+ error = EINVAL;
+ }
+
+ return (error);
+}
+
+/*
+ * Pull command status and data from the firmware mailbox.
+ */
+int
+nhi_outmail_cmd(struct nhi_softc *sc, uint32_t *val)
+{
+
+ if (val == NULL)
+ return (EINVAL);
+ *val = nhi_read_reg(sc, TBT_OUTMAILCMD);
+ return (0);
+}
+
+int
+nhi_attach(struct nhi_softc *sc)
+{
+ uint32_t val;
+ int error = 0;
+
+ if ((error = nhi_setup_sysctl(sc)) != 0)
+ return (error);
+
+ mtx_init(&sc->nhi_mtx, "nhimtx", "NHI Control Mutex", MTX_DEF);
+
+ nhi_configure_caps(sc);
+
+ /*
+ * Get the number of TX/RX paths. This sizes some of the register
+ * arrays during allocation and initialization. USB4 spec says that
+ * the max is 21. Alpine Ridge appears to default to 12.
+ */
+ val = GET_HOST_CAPS_PATHS(nhi_read_reg(sc, NHI_HOST_CAPS));
+ tb_debug(sc, DBG_INIT|DBG_NOISY, "Total Paths= %d\n", val);
+ if ((val == 0) || (val > 21) || ((NHI_IS_AR(sc) && val != 12))) {
+ tb_printf(sc, "WARN: unexpected number of paths: %d\n", val);
+ /* return (ENXIO); */
+ }
+ sc->path_count = val;
+
+ SLIST_INIT(&sc->ring_list);
+
+ error = nhi_pci_configure_interrupts(sc);
+ if (error == 0)
+ error = nhi_alloc_ring0(sc);
+ if (error == 0) {
+ nhi_configure_ring(sc, sc->ring0);
+ nhi_activate_ring(sc->ring0);
+ nhi_fill_rx_ring(sc, sc->ring0);
+ }
+
+ if (error == 0)
+ error = tbdev_add_interface(sc);
+
+ if ((error == 0) && (NHI_USE_ICM(sc)))
+ tb_printf(sc, "WARN: device uses an internal connection manager\n");
+ if ((error == 0) && (NHI_USE_HCM(sc)))
+ ;
+ error = hcm_attach(sc);
+
+ if (error == 0)
+ error = nhi_init(sc);
+
+ return (error);
+}
+
+int
+nhi_detach(struct nhi_softc *sc)
+{
+
+ if (NHI_USE_HCM(sc))
+ hcm_detach(sc);
+
+ if (sc->root_rsc != NULL)
+ tb_router_detach(sc->root_rsc);
+
+ tbdev_remove_interface(sc);
+
+ nhi_pci_disable_interrupts(sc);
+
+ nhi_free_ring0(sc);
+
+ /* XXX Should the rings be marked as !VALID in the descriptors? */
+ nhi_free_rings(sc);
+
+ mtx_destroy(&sc->nhi_mtx);
+
+ return (0);
+}
+
+static void
+nhi_memaddr_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
+{
+ bus_addr_t *addr;
+
+ addr = arg;
+ if (error == 0 && nsegs == 1) {
+ *addr = segs[0].ds_addr;
+ } else
+ *addr = 0;
+}
+
+static int
+nhi_alloc_ring(struct nhi_softc *sc, int ringnum, int tx_depth, int rx_depth,
+ struct nhi_ring_pair **rp)
+{
+ bus_dma_template_t t;
+ bus_addr_t ring_busaddr;
+ struct nhi_ring_pair *r;
+ int ring_size, error;
+ u_int rxring_len, txring_len;
+ char *ring;
+
+ if (ringnum >= sc->max_ring_count) {
+ tb_debug(sc, DBG_INIT, "Tried to allocate ring number %d\n",
+ ringnum);
+ return (EINVAL);
+ }
+
+ /* Allocate the ring structure and the RX ring tacker together. */
+ rxring_len = rx_depth * sizeof(void *);
+ txring_len = tx_depth * sizeof(void *);
+ r = malloc(sizeof(struct nhi_ring_pair) + rxring_len + txring_len,
+ M_NHI, M_NOWAIT|M_ZERO);
+ if (r == NULL) {
+ tb_printf(sc, "ERROR: Cannot allocate ring memory\n");
+ return (ENOMEM);
+ }
+
+ r->sc = sc;
+ TAILQ_INIT(&r->tx_head);
+ TAILQ_INIT(&r->rx_head);
+ r->ring_num = ringnum;
+ r->tx_ring_depth = tx_depth;
+ r->tx_ring_mask = tx_depth - 1;
+ r->rx_ring_depth = rx_depth;
+ r->rx_ring_mask = rx_depth - 1;
+ r->rx_pici_reg = NHI_RX_RING_PICI + ringnum * 16;
+ r->tx_pici_reg = NHI_TX_RING_PICI + ringnum * 16;
+ r->rx_cmd_ring = (struct nhi_cmd_frame **)((uint8_t *)r + sizeof (*r));
+ r->tx_cmd_ring = (struct nhi_cmd_frame **)((uint8_t *)r->rx_cmd_ring +
+ rxring_len);
+
+ snprintf(r->name, NHI_RING_NAMELEN, "nhiring%d\n", ringnum);
+ mtx_init(&r->mtx, r->name, "NHI Ring Lock", MTX_DEF);
+ tb_debug(sc, DBG_INIT | DBG_FULL, "Allocated ring context at %p, "
+ "mutex %p\n", r, &r->mtx);
+
+ /* Allocate the RX and TX buffer descriptor rings */
+ ring_size = sizeof(struct nhi_tx_buffer_desc) * r->tx_ring_depth;
+ ring_size += sizeof(struct nhi_rx_buffer_desc) * r->rx_ring_depth;
+ tb_debug(sc, DBG_INIT | DBG_FULL, "Ring %d ring_size= %d\n",
+ ringnum, ring_size);
+
+ bus_dma_template_init(&t, sc->parent_dmat);
+ t.alignment = 4;
+ t.maxsize = t.maxsegsize = ring_size;
+ t.nsegments = 1;
+ if ((error = bus_dma_template_tag(&t, &r->ring_dmat)) != 0) {
+ tb_printf(sc, "Cannot allocate ring %d DMA tag: %d\n",
+ ringnum, error);
+ return (ENOMEM);
+ }
+ if (bus_dmamem_alloc(r->ring_dmat, (void **)&ring, BUS_DMA_NOWAIT,
+ &r->ring_map)) {
+ tb_printf(sc, "Cannot allocate ring memory\n");
+ return (ENOMEM);
+ }
+ bzero(ring, ring_size);
+ bus_dmamap_load(r->ring_dmat, r->ring_map, ring, ring_size,
+ nhi_memaddr_cb, &ring_busaddr, 0);
+
+ r->ring = ring;
+
+ r->tx_ring = (union nhi_ring_desc *)(ring);
+ r->tx_ring_busaddr = ring_busaddr;
+ ring += sizeof(struct nhi_tx_buffer_desc) * r->tx_ring_depth;
+ ring_busaddr += sizeof(struct nhi_tx_buffer_desc) * r->tx_ring_depth;
+
+ r->rx_ring = (union nhi_ring_desc *)(ring);
+ r->rx_ring_busaddr = ring_busaddr;
+
+ tb_debug(sc, DBG_INIT | DBG_EXTRA, "Ring %d: RX %p [0x%jx] "
+ "TX %p [0x%jx]\n", ringnum, r->tx_ring, r->tx_ring_busaddr,
+ r->rx_ring, r->rx_ring_busaddr);
+
+ *rp = r;
+ return (0);
+}
+
+static void
+nhi_free_ring(struct nhi_ring_pair *r)
+{
+
+ tb_debug(r->sc, DBG_INIT, "Freeing ring %d resources\n", r->ring_num);
+ nhi_deactivate_ring(r);
+
+ if (r->tx_ring_busaddr != 0) {
+ bus_dmamap_unload(r->ring_dmat, r->ring_map);
+ r->tx_ring_busaddr = 0;
+ }
+ if (r->ring != NULL) {
+ bus_dmamem_free(r->ring_dmat, r->ring, r->ring_map);
+ r->ring = NULL;
+ }
+ if (r->ring_dmat != NULL) {
+ bus_dma_tag_destroy(r->ring_dmat);
+ r->ring_dmat = NULL;
+ }
+ mtx_destroy(&r->mtx);
+}
+
+static void
+nhi_free_rings(struct nhi_softc *sc)
+{
+ struct nhi_ring_pair *r;
+
+ while ((r = SLIST_FIRST(&sc->ring_list)) != NULL) {
+ nhi_free_ring(r);
+ mtx_lock(&sc->nhi_mtx);
+ SLIST_REMOVE_HEAD(&sc->ring_list, ring_link);
+ mtx_unlock(&sc->nhi_mtx);
+ free(r, M_NHI);
+ }
+
+ return;
+}
+
+static int
+nhi_configure_ring(struct nhi_softc *sc, struct nhi_ring_pair *ring)
+{
+ bus_addr_t busaddr;
+ uint32_t val;
+ int idx;
+
+ idx = ring->ring_num * 16;
+
+ /* Program the TX ring address and size */
+ busaddr = ring->tx_ring_busaddr;
+ nhi_write_reg(sc, NHI_TX_RING_ADDR_LO + idx, busaddr & 0xffffffff);
+ nhi_write_reg(sc, NHI_TX_RING_ADDR_HI + idx, busaddr >> 32);
+ nhi_write_reg(sc, NHI_TX_RING_SIZE + idx, ring->tx_ring_depth);
+ nhi_write_reg(sc, NHI_TX_RING_TABLE_TIMESTAMP + idx, 0x0);
+ tb_debug(sc, DBG_INIT, "TX Ring %d TX_RING_SIZE= 0x%x\n",
+ ring->ring_num, ring->tx_ring_depth);
+
+ /* Program the RX ring address and size */
+ busaddr = ring->rx_ring_busaddr;
+ val = (ring->rx_buffer_size << 16) | ring->rx_ring_depth;
+ nhi_write_reg(sc, NHI_RX_RING_ADDR_LO + idx, busaddr & 0xffffffff);
+ nhi_write_reg(sc, NHI_RX_RING_ADDR_HI + idx, busaddr >> 32);
+ nhi_write_reg(sc, NHI_RX_RING_SIZE + idx, val);
+ nhi_write_reg(sc, NHI_RX_RING_TABLE_BASE1 + idx, 0xffffffff);
+ tb_debug(sc, DBG_INIT, "RX Ring %d RX_RING_SIZE= 0x%x\n",
+ ring->ring_num, val);
+
+ return (0);
+}
+
+static int
+nhi_activate_ring(struct nhi_ring_pair *ring)
+{
+ struct nhi_softc *sc = ring->sc;
+ int idx;
+
+ nhi_pci_enable_interrupt(ring);
+
+ idx = ring->ring_num * 32;
+ tb_debug(sc, DBG_INIT, "Activating ring %d at idx %d\n",
+ ring->ring_num, idx);
+ nhi_write_reg(sc, NHI_TX_RING_TABLE_BASE0 + idx,
+ TX_TABLE_RAW | TX_TABLE_VALID);
+ nhi_write_reg(sc, NHI_RX_RING_TABLE_BASE0 + idx,
+ RX_TABLE_RAW | RX_TABLE_VALID);
+
+ return (0);
+}
+
+static int
+nhi_deactivate_ring(struct nhi_ring_pair *r)
+{
+ struct nhi_softc *sc = r->sc;
+ int idx;
+
+ idx = r->ring_num * 32;
+ tb_debug(sc, DBG_INIT, "Deactiving ring %d at idx %d\n",
+ r->ring_num, idx);
+ nhi_write_reg(sc, NHI_TX_RING_TABLE_BASE0 + idx, 0);
+ nhi_write_reg(sc, NHI_RX_RING_TABLE_BASE0 + idx, 0);
+
+ idx = r->ring_num * 16;
+ tb_debug(sc, DBG_INIT, "Setting ring %d sizes to 0\n", r->ring_num);
+ nhi_write_reg(sc, NHI_TX_RING_SIZE + idx, 0);
+ nhi_write_reg(sc, NHI_RX_RING_SIZE + idx, 0);
+
+ return (0);
+}
+
+static int
+nhi_alloc_ring0(struct nhi_softc *sc)
+{
+ bus_addr_t frames_busaddr;
+ bus_dma_template_t t;
+ struct nhi_intr_tracker *trkr;
+ struct nhi_ring_pair *r;
+ struct nhi_cmd_frame *cmd;
+ char *frames;
+ int error, size, i;
+
+ if ((error = nhi_alloc_ring(sc, 0, NHI_RING0_TX_DEPTH,
+ NHI_RING0_RX_DEPTH, &r)) != 0) {
+ tb_printf(sc, "Error allocating control ring\n");
+ return (error);
+ }
+
+ r->rx_buffer_size = NHI_RING0_FRAME_SIZE;/* Control packets are small */
+
+ /* Allocate the RX and TX buffers that are used for Ring0 comms */
+ size = r->tx_ring_depth * NHI_RING0_FRAME_SIZE;
+ size += r->rx_ring_depth * NHI_RING0_FRAME_SIZE;
+
+ bus_dma_template_init(&t, sc->parent_dmat);
+ t.maxsize = t.maxsegsize = size;
+ t.nsegments = 1;
+ if (bus_dma_template_tag(&t, &sc->ring0_dmat)) {
+ tb_printf(sc, "Error allocating control ring buffer tag\n");
+ return (ENOMEM);
+ }
+
+ if (bus_dmamem_alloc(sc->ring0_dmat, (void **)&frames, BUS_DMA_NOWAIT,
+ &sc->ring0_map) != 0) {
+ tb_printf(sc, "Error allocating control ring memory\n");
+ return (ENOMEM);
+ }
+ bzero(frames, size);
+ bus_dmamap_load(sc->ring0_dmat, sc->ring0_map, frames, size,
+ nhi_memaddr_cb, &frames_busaddr, 0);
+ sc->ring0_frames_busaddr = frames_busaddr;
+ sc->ring0_frames = frames;
+
+ /* Allocate the driver command trackers */
+ sc->ring0_cmds = malloc(sizeof(struct nhi_cmd_frame) *
+ (r->tx_ring_depth + r->rx_ring_depth), M_NHI, M_NOWAIT | M_ZERO);
+ if (sc->ring0_cmds == NULL)
+ return (ENOMEM);
+
+ /* Initialize the RX frames so they can be used */
+ mtx_lock(&r->mtx);
+ for (i = 0; i < r->rx_ring_depth; i++) {
+ cmd = &sc->ring0_cmds[i];
+ cmd->data = (uint32_t *)(frames + NHI_RING0_FRAME_SIZE * i);
+ cmd->data_busaddr = frames_busaddr + NHI_RING0_FRAME_SIZE * i;
+ cmd->flags = CMD_MAPPED;
+ cmd->idx = i;
+ TAILQ_INSERT_TAIL(&r->rx_head, cmd, cm_link);
+ }
+
+ /* Inititalize the TX frames */
+ for ( ; i < r->tx_ring_depth + r->rx_ring_depth - 1; i++) {
+ cmd = &sc->ring0_cmds[i];
+ cmd->data = (uint32_t *)(frames + NHI_RING0_FRAME_SIZE * i);
+ cmd->data_busaddr = frames_busaddr + NHI_RING0_FRAME_SIZE * i;
+ cmd->flags = CMD_MAPPED;
+ cmd->idx = i;
+ nhi_free_tx_frame_locked(r, cmd);
+ }
+ mtx_unlock(&r->mtx);
+
+ /* Do a 1:1 mapping of rings to interrupt vectors. */
+ /* XXX Should be abstracted */
+ trkr = &sc->intr_trackers[0];
+ trkr->ring = r;
+ r->tracker = trkr;
+
+ /* XXX Should be an array */
+ sc->ring0 = r;
+ SLIST_INSERT_HEAD(&sc->ring_list, r, ring_link);
+
+ return (0);
+}
+
+static void
+nhi_free_ring0(struct nhi_softc *sc)
+{
+ if (sc->ring0_cmds != NULL) {
+ free(sc->ring0_cmds, M_NHI);
+ sc->ring0_cmds = NULL;
+ }
+
+ if (sc->ring0_frames_busaddr != 0) {
+ bus_dmamap_unload(sc->ring0_dmat, sc->ring0_map);
+ sc->ring0_frames_busaddr = 0;
+ }
+
+ if (sc->ring0_frames != NULL) {
+ bus_dmamem_free(sc->ring0_dmat, sc->ring0_frames,
+ sc->ring0_map);
+ sc->ring0_frames = NULL;
+ }
+
+ if (sc->ring0_dmat != NULL)
+ bus_dma_tag_destroy(sc->ring0_dmat);
+
+ return;
+}
+
+static void
+nhi_fill_rx_ring(struct nhi_softc *sc, struct nhi_ring_pair *rp)
+{
+ struct nhi_cmd_frame *cmd;
+ struct nhi_rx_buffer_desc *desc;
+ u_int ci;
+
+ /* Assume that we never grow or shrink the ring population */
+ rp->rx_ci = ci = 0;
+ rp->rx_pi = 0;
+
+ do {
+ cmd = TAILQ_FIRST(&rp->rx_head);
+ if (cmd == NULL)
+ break;
+ TAILQ_REMOVE(&rp->rx_head, cmd, cm_link);
+ desc = &rp->rx_ring[ci].rx;
+ if ((cmd->flags & CMD_MAPPED) == 0)
+ panic("Need rx buffer mapping code");
+
+ desc->addr_lo = cmd->data_busaddr & 0xffffffff;
+ desc->addr_hi = (cmd->data_busaddr >> 32) & 0xffffffff;
+ desc->offset = 0;
+ desc->flags = RX_BUFFER_DESC_RS | RX_BUFFER_DESC_IE;
+ rp->rx_ci = ci;
+ rp->rx_cmd_ring[ci] = cmd;
+ tb_debug(sc, DBG_RXQ | DBG_FULL,
+ "Updating ring%d ci= %d cmd= %p, busaddr= 0x%jx\n",
+ rp->ring_num, ci, cmd, cmd->data_busaddr);
+
+ ci = (rp->rx_ci + 1) & rp->rx_ring_mask;
+ } while (ci != rp->rx_pi);
+
+ /* Update the CI in one shot */
+ tb_debug(sc, DBG_RXQ, "Writing RX CI= %d\n", rp->rx_ci);
+ nhi_write_reg(sc, rp->rx_pici_reg, rp->rx_ci);
+
+ return;
+}
+
+static int
+nhi_init(struct nhi_softc *sc)
+{
+ tb_route_t root_route = {0x0, 0x0};
+ uint32_t val;
+ int error;
+
+ tb_debug(sc, DBG_INIT, "Initializing NHI\n");
+
+ /* Set interrupt Auto-ACK */
+ val = nhi_read_reg(sc, NHI_DMA_MISC);
+ tb_debug(sc, DBG_INIT|DBG_FULL, "Read NHI_DMA_MISC= 0x%08x\n", val);
+ val |= DMA_MISC_INT_AUTOCLEAR;
+ tb_debug(sc, DBG_INIT, "Setting interrupt auto-ACK, 0x%08x\n", val);
+ nhi_write_reg(sc, NHI_DMA_MISC, val);
+
+ if (NHI_IS_AR(sc) || NHI_IS_TR(sc) || NHI_IS_ICL(sc))
+ tb_printf(sc, "WARN: device uses an internal connection manager\n");
+
+ /*
+ * Populate the controller (local) UUID, necessary for cross-domain
+ * communications.
+ if (NHI_IS_ICL(sc))
+ nhi_pci_get_uuid(sc);
+ */
+
+ /*
+ * Attach the router to the root thunderbolt bridge now that the DMA
+ * channel is configured and ready.
+ * The root router always has a route of 0x0...0, so set it statically
+ * here.
+ */
+ if ((error = tb_router_attach_root(sc, root_route)) != 0)
+ tb_printf(sc, "tb_router_attach_root() error."
+ " The driver should be loaded at boot\n");
+
+ if (error == 0) {
+ sc->ich.ich_func = nhi_post_init;
+ sc->ich.ich_arg = sc;
+ error = config_intrhook_establish(&sc->ich);
+ if (error)
+ tb_printf(sc, "Failed to establish config hook\n");
+ }
+
+ return (error);
+}
+
+static void
+nhi_post_init(void *arg)
+{
+ struct nhi_softc *sc;
+ uint8_t *u;
+ int error;
+
+ sc = (struct nhi_softc *)arg;
+ tb_debug(sc, DBG_INIT | DBG_EXTRA, "nhi_post_init\n");
+
+ bzero(sc->lc_uuid, 16);
+ error = tb_config_get_lc_uuid(sc->root_rsc, sc->lc_uuid);
+ if (error == 0) {
+ u = sc->lc_uuid;
+ tb_printf(sc, "Root Router LC UUID: %02x%02x%02x%02x-"
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+ u[15], u[14], u[13], u[12], u[11], u[10], u[9], u[8], u[7],
+ u[6], u[5], u[4], u[3], u[2], u[1], u[0]);
+ } else
+ tb_printf(sc, "Error finding LC registers: %d\n", error);
+
+ u = sc->uuid;
+ tb_printf(sc, "Root Router UUID: %02x%02x%02x%02x-"
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+ u[15], u[14], u[13], u[12], u[11], u[10], u[9], u[8], u[7],
+ u[6], u[5], u[4], u[3], u[2], u[1], u[0]);
+
+ config_intrhook_disestablish(&sc->ich);
+}
+
+static int
+nhi_tx_enqueue(struct nhi_ring_pair *r, struct nhi_cmd_frame *cmd)
+{
+ struct nhi_softc *sc;
+ struct nhi_tx_buffer_desc *desc;
+ uint16_t pi;
+
+ sc = r->sc;
+
+ /* A length of 0 means 4096. Can't have longer lengths */
+ if (cmd->req_len > TX_BUFFER_DESC_LEN_MASK + 1) {
+ tb_debug(sc, DBG_TXQ, "Error: TX frame too big\n");
+ return (EINVAL);
+ }
+ cmd->req_len &= TX_BUFFER_DESC_LEN_MASK;
+
+ mtx_lock(&r->mtx);
+ desc = &r->tx_ring[r->tx_pi].tx;
+ pi = (r->tx_pi + 1) & r->tx_ring_mask;
+ if (pi == r->tx_ci) {
+ mtx_unlock(&r->mtx);
+ return (EBUSY);
+ }
+ r->tx_cmd_ring[r->tx_pi] = cmd;
+ r->tx_pi = pi;
+
+ desc->addr_lo = htole32(cmd->data_busaddr & 0xffffffff);
+ desc->addr_hi = htole32(cmd->data_busaddr >> 32);
+ desc->eof_len = htole16((cmd->pdf << TX_BUFFER_DESC_EOF_SHIFT) |
+ cmd->req_len);
+ desc->flags_sof = cmd->pdf | TX_BUFFER_DESC_IE | TX_BUFFER_DESC_RS;
+ desc->offset = 0;
+ desc->payload_time = 0;
+
+ tb_debug(sc, DBG_TXQ, "enqueue TXdescIdx= %d cmdidx= %d len= %d, "
+ "busaddr= 0x%jx\n", r->tx_pi, cmd->idx, cmd->req_len,
+ cmd->data_busaddr);
+
+ nhi_write_reg(sc, r->tx_pici_reg, pi << TX_RING_PI_SHIFT | r->tx_ci);
+ mtx_unlock(&r->mtx);
+ return (0);
+}
+
+/*
+ * No scheduling happens for now. Ring0 scheduling is done in the TB
+ * layer.
+ */
+int
+nhi_tx_schedule(struct nhi_ring_pair *r, struct nhi_cmd_frame *cmd)
+{
+ int error;
+
+ error = nhi_tx_enqueue(r, cmd);
+ if (error == EBUSY)
+ nhi_write_reg(r->sc, r->tx_pici_reg, r->tx_pi << TX_RING_PI_SHIFT | r->tx_ci);
+ return (error);
+}
+
+int
+nhi_tx_synchronous(struct nhi_ring_pair *r, struct nhi_cmd_frame *cmd)
+{
+ int error, count;
+
+ if ((error = nhi_tx_schedule(r, cmd)) != 0)
+ return (error);
+
+ if (cmd->flags & CMD_POLLED) {
+ error = 0;
+ count = cmd->timeout * 100;
+
+ /* Enter the loop at least once */
+ while ((count-- > 0) && (cmd->flags & CMD_REQ_COMPLETE) == 0) {
+ DELAY(10000);
+ rmb();
+ nhi_intr(r->tracker);
+ }
+ } else {
+ error = msleep(cmd, &r->mtx, PCATCH, "nhi_tx", cmd->timeout);
+ if ((error == 0) && (cmd->flags & CMD_REQ_COMPLETE) != 0)
+ error = EWOULDBLOCK;
+ }
+
+ if ((cmd->flags & CMD_REQ_COMPLETE) == 0)
+ error = ETIMEDOUT;
+
+ tb_debug(r->sc, DBG_TXQ|DBG_FULL, "tx_synchronous done waiting, "
+ "err= %d, TX_COMPLETE= %d\n", error,
+ !!(cmd->flags & CMD_REQ_COMPLETE));
+
+ if (error == ERESTART) {
+ tb_printf(r->sc, "TX command interrupted\n");
+ } else if ((error == EWOULDBLOCK) || (error == ETIMEDOUT)) {
+ tb_printf(r->sc, "TX command timed out\n");
+ } else if (error != 0) {
+ tb_printf(r->sc, "TX command failed error= %d\n", error);
+ }
+
+ return (error);
+}
+
+static int
+nhi_tx_complete(struct nhi_ring_pair *r, struct nhi_tx_buffer_desc *desc,
+ struct nhi_cmd_frame *cmd)
+{
+ struct nhi_softc *sc;
+ struct nhi_pdf_dispatch *txpdf;
+ u_int sof;
+
+ sc = r->sc;
+ sof = desc->flags_sof & TX_BUFFER_DESC_SOF_MASK;
+ tb_debug(sc, DBG_TXQ, "Recovered TX pdf= %s cmdidx= %d flags= 0x%x\n",
+ tb_get_string(sof, nhi_frame_pdf), cmd->idx, desc->flags_sof);
+
+ if ((desc->flags_sof & TX_BUFFER_DESC_DONE) == 0)
+ tb_debug(sc, DBG_TXQ,
+ "warning, TX descriptor DONE flag not set\n");
+
+ /* XXX Atomics */
+ cmd->flags |= CMD_REQ_COMPLETE;
+
+ txpdf = &r->tracker->txpdf[sof];
+ if (txpdf->cb != NULL) {
+ tb_debug(sc, DBG_INTR|DBG_TXQ, "Calling PDF TX callback\n");
+ txpdf->cb(txpdf->context, (union nhi_ring_desc *)desc, cmd);
+ return (0);
+ }
+
+ tb_debug(sc, DBG_TXQ, "Unhandled TX complete %s\n",
+ tb_get_string(sof, nhi_frame_pdf));
+ nhi_free_tx_frame(r, cmd);
+
+ return (0);
+}
+
+static int
+nhi_rx_complete(struct nhi_ring_pair *r, struct nhi_rx_post_desc *desc,
+ struct nhi_cmd_frame *cmd)
+{
+ struct nhi_softc *sc;
+ struct nhi_pdf_dispatch *rxpdf;
+ u_int eof, len;
+
+ sc = r->sc;
+ eof = desc->eof_len >> RX_BUFFER_DESC_EOF_SHIFT;
+ len = desc->eof_len & RX_BUFFER_DESC_LEN_MASK;
+ tb_debug(sc, DBG_INTR|DBG_RXQ,
+ "Recovered RX pdf= %s len= %d cmdidx= %d, busaddr= 0x%jx\n",
+ tb_get_string(eof, nhi_frame_pdf), len, cmd->idx,
+ cmd->data_busaddr);
+
+ rxpdf = &r->tracker->rxpdf[eof];
+ if (rxpdf->cb != NULL) {
+ tb_debug(sc, DBG_INTR|DBG_RXQ, "Calling PDF RX callback\n");
+ rxpdf->cb(rxpdf->context, (union nhi_ring_desc *)desc, cmd);
+ return (0);
+ }
+
+ tb_debug(sc, DBG_INTR, "Unhandled RX frame %s\n",
+ tb_get_string(eof, nhi_frame_pdf));
+
+ return (0);
+}
+
+int
+nhi_register_pdf(struct nhi_ring_pair *rp, struct nhi_dispatch *tx,
+ struct nhi_dispatch *rx)
+{
+ struct nhi_intr_tracker *trkr;
+ struct nhi_pdf_dispatch *slot;
+
+ KASSERT(rp != NULL, ("ring_pair is null\n"));
+ tb_debug(rp->sc, DBG_INTR|DBG_EXTRA, "nhi_register_pdf called\n");
+
+ trkr = rp->tracker;
+ if (trkr == NULL) {
+ tb_debug(rp->sc, DBG_INTR, "Invalid tracker\n");
+ return (EINVAL);
+ }
+
+ tb_debug(rp->sc, DBG_INTR|DBG_EXTRA, "Registering TX interrupts\n");
+ if (tx != NULL) {
+ while (tx->cb != NULL) {
+ if ((tx->pdf < 0) || (tx->pdf > 15))
+ return (EINVAL);
+ slot = &trkr->txpdf[tx->pdf];
+ if (slot->cb != NULL) {
+ tb_debug(rp->sc, DBG_INTR,
+ "Attempted to register busy callback\n");
+ return (EBUSY);
+ }
+ slot->cb = tx->cb;
+ slot->context = tx->context;
+ tb_debug(rp->sc, DBG_INTR,
+ "Registered TX callback for PDF %d\n", tx->pdf);
+ tx++;
+ }
+ }
+
+ tb_debug(rp->sc, DBG_INTR|DBG_EXTRA, "Registering RX interrupts\n");
+ if (rx != NULL) {
+ while (rx->cb != NULL) {
+ if ((rx->pdf < 0) || (rx->pdf > 15))
+ return (EINVAL);
+ slot = &trkr->rxpdf[rx->pdf];
+ if (slot->cb != NULL) {
+ tb_debug(rp->sc, DBG_INTR,
+ "Attempted to register busy callback\n");
+ return (EBUSY);
+ }
+ slot->cb = rx->cb;
+ slot->context = rx->context;
+ tb_debug(rp->sc, DBG_INTR,
+ "Registered RX callback for PDF %d\n", rx->pdf);
+ rx++;
+ }
+ }
+
+ return (0);
+}
+
+int
+nhi_deregister_pdf(struct nhi_ring_pair *rp, struct nhi_dispatch *tx,
+ struct nhi_dispatch *rx)
+{
+ struct nhi_intr_tracker *trkr;
+ struct nhi_pdf_dispatch *slot;
+
+ tb_debug(rp->sc, DBG_INTR|DBG_EXTRA, "nhi_register_pdf called\n");
+
+ trkr = rp->tracker;
+
+ if (tx != NULL) {
+ while (tx->cb != NULL) {
+ if ((tx->pdf < 0) || (tx->pdf > 15))
+ return (EINVAL);
+ slot = &trkr->txpdf[tx->pdf];
+ slot->cb = NULL;
+ slot->context = NULL;
+ tx++;
+ }
+ }
+
+ if (rx != NULL) {
+ while (rx->cb != NULL) {
+ if ((rx->pdf < 0) || (rx->pdf > 15))
+ return (EINVAL);
+ slot = &trkr->rxpdf[rx->pdf];
+ slot->cb = NULL;
+ slot->context = NULL;
+ rx++;
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * The CI and PI indexes are not read from the hardware. We track them in
+ * software, so we know where in the ring to start a scan on an interrupt.
+ * All we have to do is check for the appropriate Done bit in the next
+ * descriptor, and we know if we have reached the last descriptor that the
+ * hardware touched. This technique saves at least 2 MEMIO reads per
+ * interrupt.
+ */
+void
+nhi_intr(void *data)
+{
+ union nhi_ring_desc *rxd;
+ struct nhi_cmd_frame *cmd;
+ struct nhi_intr_tracker *trkr = data;
+ struct nhi_softc *sc;
+ struct nhi_ring_pair *r;
+ struct nhi_tx_buffer_desc *txd;
+ uint32_t val, old_ci;
+ u_int count;
+
+ sc = trkr->sc;
+
+ tb_debug(sc, DBG_INTR|DBG_FULL, "Interrupt @ vector %d\n",
+ trkr->vector);
+ if ((r = trkr->ring) == NULL)
+ return;
+
+ /*
+ * Process TX completions from the adapter. Only go through
+ * the ring once to prevent unbounded looping.
+ */
+ count = r->tx_ring_depth;
+ while (count-- > 0) {
+ txd = &r->tx_ring[r->tx_ci].tx;
+ if ((txd->flags_sof & TX_BUFFER_DESC_DONE) == 0)
+ break;
+ cmd = r->tx_cmd_ring[r->tx_ci];
+ tb_debug(sc, DBG_INTR|DBG_TXQ|DBG_FULL,
+ "Found tx cmdidx= %d cmd= %p\n", r->tx_ci, cmd);
+
+ /* Pass the completion up the stack */
+ nhi_tx_complete(r, txd, cmd);
+
+ /*
+ * Advance to the next item in the ring via the cached
+ * copy of the CI. Clear the flags so we can detect
+ * a new done condition the next time the ring wraps
+ * around. Anything higher up the stack that needs this
+ * field should have already copied it.
+ *
+ * XXX is a memory barrier needed?
+ */
+ txd->flags_sof = 0;
+ r->tx_ci = (r->tx_ci + 1) & r->tx_ring_mask;
+ }
+
+ /* Process RX packets from the adapter */
+ count = r->rx_ring_depth;
+ old_ci = r->rx_ci;
+
+ while (count-- > 0) {
+ tb_debug(sc, DBG_INTR|DBG_RXQ|DBG_FULL,
+ "Checking RX descriptor at %d\n", r->rx_pi);
+
+ /* Look up RX descriptor and cmd */
+ rxd = &r->rx_ring[r->rx_pi];
+ tb_debug(sc, DBG_INTR|DBG_RXQ|DBG_FULL,
+ "rx desc len= 0x%04x flags= 0x%04x\n", rxd->rxpost.eof_len,
+ rxd->rxpost.flags_sof);
+ if ((rxd->rxpost.flags_sof & RX_BUFFER_DESC_DONE) == 0)
+ break;
+ cmd = r->rx_cmd_ring[r->rx_pi];
+ tb_debug(sc, DBG_INTR|DBG_RXQ|DBG_FULL,
+ "Found rx cmdidx= %d cmd= %p\n", r->rx_pi, cmd);
+
+ /*
+ * Pass the RX frame up the stack. RX frames are re-used
+ * in-place, so their contents must be copied before this
+ * function returns.
+ *
+ * XXX Rings other than Ring0 might want to have a different
+ * re-use and re-populate policy
+ */
+ nhi_rx_complete(r, &rxd->rxpost, cmd);
+
+ /*
+ * Advance the CI and move forward to the next item in the
+ * ring via our cached copy of the PI. Clear out the
+ * length field so we can detect a new RX frame when the
+ * ring wraps around. Reset the flags of the descriptor.
+ */
+ rxd->rxpost.eof_len = 0;
+ rxd->rx.flags = RX_BUFFER_DESC_RS | RX_BUFFER_DESC_IE;
+ r->rx_ci = (r->rx_ci + 1) & r->rx_ring_mask;
+ r->rx_pi = (r->rx_pi + 1) & r->rx_ring_mask;
+ }
+
+ /*
+ * Tell the firmware about the new RX CI
+ *
+ * XXX There's a chance this will overwrite an update to the PI.
+ * Is that OK? We keep our own copy of the PI and never read it from
+ * hardware. However, will overwriting it result in a missed
+ * interrupt?
+ */
+ if (r->rx_ci != old_ci) {
+ val = r->rx_pi << RX_RING_PI_SHIFT | r->rx_ci;
+ tb_debug(sc, DBG_INTR | DBG_RXQ,
+ "Writing new RX PICI= 0x%08x\n", val);
+ nhi_write_reg(sc, r->rx_pici_reg, val);
+ }
+}
+
+static int
+nhi_setup_sysctl(struct nhi_softc *sc)
+{
+ struct sysctl_ctx_list *ctx = NULL;
+ struct sysctl_oid *tree = NULL;
+
+ ctx = device_get_sysctl_ctx(sc->dev);
+ if (ctx != NULL)
+ tree = device_get_sysctl_tree(sc->dev);
+
+ /*
+ * Not being able to create sysctls is going to hamper other
+ * parts of the driver.
+ */
+ if (tree == NULL) {
+ tb_printf(sc, "Error: cannot create sysctl nodes\n");
+ return (EINVAL);
+ }
+ sc->sysctl_tree = tree;
+ sc->sysctl_ctx = ctx;
+
+ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree),
+ OID_AUTO, "debug_level", CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
+ &sc->debug, 0, tb_debug_sysctl, "A", "Thunderbolt debug level");
+ SYSCTL_ADD_U16(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "max_rings", CTLFLAG_RD, &sc->max_ring_count, 0,
+ "Max number of rings available");
+ SYSCTL_ADD_U8(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ "force_hcm", CTLFLAG_RD, &sc->force_hcm, 0,
+ "Force on/off the function of the host connection manager");
+
+ return (0);
+}
diff --git a/sys/dev/thunderbolt/nhi_pci.c b/sys/dev/thunderbolt/nhi_pci.c
new file mode 100644
index 000000000000..7dacff523cef
--- /dev/null
+++ b/sys/dev/thunderbolt/nhi_pci.c
@@ -0,0 +1,529 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_thunderbolt.h"
+
+/* PCIe interface for Thunderbolt Native Host Interface */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/taskqueue.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <machine/stdarg.h>
+#include <sys/rman.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pci_private.h>
+
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/router_var.h>
+#include <dev/thunderbolt/tb_debug.h>
+#include "tb_if.h"
+
+static int nhi_pci_probe(device_t);
+static int nhi_pci_attach(device_t);
+static int nhi_pci_detach(device_t);
+static int nhi_pci_suspend(device_t);
+static int nhi_pci_resume(device_t);
+static void nhi_pci_free(struct nhi_softc *);
+static int nhi_pci_allocate_interrupts(struct nhi_softc *);
+static void nhi_pci_free_interrupts(struct nhi_softc *);
+static int nhi_pci_icl_poweron(struct nhi_softc *);
+
+static device_method_t nhi_methods[] = {
+ DEVMETHOD(device_probe, nhi_pci_probe),
+ DEVMETHOD(device_attach, nhi_pci_attach),
+ DEVMETHOD(device_detach, nhi_pci_detach),
+ DEVMETHOD(device_suspend, nhi_pci_suspend),
+ DEVMETHOD(device_resume, nhi_pci_resume),
+
+ DEVMETHOD(tb_find_ufp, tb_generic_find_ufp),
+ DEVMETHOD(tb_get_debug, tb_generic_get_debug),
+
+ DEVMETHOD_END
+};
+
+static driver_t nhi_pci_driver = {
+ "nhi",
+ nhi_methods,
+ sizeof(struct nhi_softc)
+};
+
+struct nhi_ident {
+ uint16_t vendor;
+ uint16_t device;
+ uint16_t subvendor;
+ uint16_t subdevice;
+ uint32_t flags;
+ const char *desc;
+} nhi_identifiers[] = {
+ { VENDOR_INTEL, DEVICE_AR_2C_NHI, 0xffff, 0xffff, NHI_TYPE_AR,
+ "Thunderbolt 3 NHI (Alpine Ridge 2C)" },
+ { VENDOR_INTEL, DEVICE_AR_DP_B_NHI, 0xffff, 0xffff, NHI_TYPE_AR,
+ "Thunderbolt 3 NHI (Alpine Ridge 4C Rev B)" },
+ { VENDOR_INTEL, DEVICE_AR_DP_C_NHI, 0xffff, 0xffff, NHI_TYPE_AR,
+ "Thunderbolt 3 NHI (Alpine Ridge 4C Rev C)" },
+ { VENDOR_INTEL, DEVICE_AR_LP_NHI, 0xffff, 0xffff, NHI_TYPE_AR,
+ "Thunderbolt 3 NHI (Alpine Ridge LP 2C)" },
+ { VENDOR_INTEL, DEVICE_ICL_NHI_0, 0xffff, 0xffff, NHI_TYPE_ICL,
+ "Thunderbolt 3 NHI Port 0 (IceLake)" },
+ { VENDOR_INTEL, DEVICE_ICL_NHI_1, 0xffff, 0xffff, NHI_TYPE_ICL,
+ "Thunderbolt 3 NHI Port 1 (IceLake)" },
+ { VENDOR_AMD, DEVICE_PINK_SARDINE_0, 0xffff, 0xffff, NHI_TYPE_USB4,
+ "USB4 NHI Port 0 (Pink Sardine)" },
+ { VENDOR_AMD, DEVICE_PINK_SARDINE_1, 0xffff, 0xffff, NHI_TYPE_USB4,
+ "USB4 NHI Port 1 (Pink Sardine)" },
+ { 0, 0, 0, 0, 0, NULL }
+};
+
+DRIVER_MODULE_ORDERED(nhi, pci, nhi_pci_driver, NULL, NULL,
+ SI_ORDER_ANY);
+MODULE_PNP_INFO("U16:vendor;U16:device;V16:subvendor;V16:subdevice;U32:#;D:#",
+ pci, nhi, nhi_identifiers, nitems(nhi_identifiers) - 1);
+
+static struct nhi_ident *
+nhi_find_ident(device_t dev)
+{
+ struct nhi_ident *n;
+
+ for (n = nhi_identifiers; n->vendor != 0; n++) {
+ if (n->vendor != pci_get_vendor(dev))
+ continue;
+ if (n->device != pci_get_device(dev))
+ continue;
+ if ((n->subvendor != 0xffff) &&
+ (n->subvendor != pci_get_subvendor(dev)))
+ continue;
+ if ((n->subdevice != 0xffff) &&
+ (n->subdevice != pci_get_subdevice(dev)))
+ continue;
+ return (n);
+ }
+
+ return (NULL);
+}
+
+static int
+nhi_pci_probe(device_t dev)
+{
+ struct nhi_ident *n;
+
+ if (resource_disabled("tb", 0))
+ return (ENXIO);
+ if ((n = nhi_find_ident(dev)) != NULL) {
+ device_set_desc(dev, n->desc);
+ return (BUS_PROBE_DEFAULT);
+ }
+ return (ENXIO);
+}
+
+static int
+nhi_pci_attach(device_t dev)
+{
+ devclass_t dc;
+ bus_dma_template_t t;
+ struct nhi_softc *sc;
+ struct nhi_ident *n;
+ int error = 0;
+
+ sc = device_get_softc(dev);
+ bzero(sc, sizeof(*sc));
+ sc->dev = dev;
+ n = nhi_find_ident(dev);
+ sc->hwflags = n->flags;
+ nhi_get_tunables(sc);
+
+ tb_debug(sc, DBG_INIT|DBG_FULL, "busmaster status was %s\n",
+ (pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_BUSMASTEREN)
+ ? "enabled" : "disabled");
+ pci_enable_busmaster(dev);
+
+ sc->ufp = NULL;
+ if ((TB_FIND_UFP(dev, &sc->ufp) != 0) || (sc->ufp == NULL)) {
+ dc = devclass_find("tbolt");
+ if (dc != NULL)
+ sc->ufp = devclass_get_device(dc, device_get_unit(dev));
+ }
+ if (sc->ufp == NULL)
+ tb_printf(sc, "Cannot find Upstream Facing Port\n");
+ else
+ tb_printf(sc, "Upstream Facing Port is %s\n",
+ device_get_nameunit(sc->ufp));
+
+ if (NHI_IS_ICL(sc)) {
+ if ((error = nhi_pci_icl_poweron(sc)) != 0)
+ return (error);
+ }
+
+
+ /* Allocate BAR0 DMA registers */
+ sc->regs_rid = PCIR_BAR(0);
+ if ((sc->regs_resource = bus_alloc_resource_any(dev,
+ SYS_RES_MEMORY, &sc->regs_rid, RF_ACTIVE)) == NULL) {
+ tb_printf(sc, "Cannot allocate PCI registers\n");
+ return (ENXIO);
+ }
+ sc->regs_btag = rman_get_bustag(sc->regs_resource);
+ sc->regs_bhandle = rman_get_bushandle(sc->regs_resource);
+
+ /* Allocate parent DMA tag */
+ bus_dma_template_init(&t, bus_get_dma_tag(dev));
+ if (bus_dma_template_tag(&t, &sc->parent_dmat) != 0) {
+ tb_printf(sc, "Cannot allocate parent DMA tag\n");
+ nhi_pci_free(sc);
+ return (ENOMEM);
+ }
+
+ error = nhi_pci_allocate_interrupts(sc);
+ if (error == 0)
+ error = nhi_attach(sc);
+ if (error != 0)
+ nhi_pci_detach(sc->dev);
+ return (error);
+}
+
+static int
+nhi_pci_detach(device_t dev)
+{
+ struct nhi_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ nhi_detach(sc);
+ nhi_pci_free(sc);
+
+ return (0);
+}
+
+static int
+nhi_pci_suspend(device_t dev)
+{
+
+ return (0);
+}
+
+static int
+nhi_pci_resume(device_t dev)
+{
+
+ return (0);
+}
+
+static void
+nhi_pci_free(struct nhi_softc *sc)
+{
+
+ nhi_pci_free_interrupts(sc);
+
+ if (sc->parent_dmat != NULL) {
+ bus_dma_tag_destroy(sc->parent_dmat);
+ sc->parent_dmat = NULL;
+ }
+
+ if (sc->regs_resource != NULL) {
+ bus_release_resource(sc->dev, SYS_RES_MEMORY,
+ sc->regs_rid, sc->regs_resource);
+ sc->regs_resource = NULL;
+ }
+
+ return;
+}
+
+static int
+nhi_pci_allocate_interrupts(struct nhi_softc *sc)
+{
+ int msgs, error = 0;
+
+ /* Map the Pending Bit Array and Vector Table BARs for MSI-X */
+ sc->irq_pba_rid = pci_msix_pba_bar(sc->dev);
+ sc->irq_table_rid = pci_msix_table_bar(sc->dev);
+
+ if (sc->irq_pba_rid != -1)
+ sc->irq_pba = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY,
+ &sc->irq_pba_rid, RF_ACTIVE);
+ if (sc->irq_table_rid != -1)
+ sc->irq_table = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY,
+ &sc->irq_table_rid, RF_ACTIVE);
+
+ msgs = pci_msix_count(sc->dev);
+ tb_debug(sc, DBG_INIT|DBG_INTR|DBG_FULL,
+ "Counted %d MSI-X messages\n", msgs);
+ msgs = min(msgs, NHI_MSIX_MAX);
+ msgs = max(msgs, 1);
+ if (msgs != 0) {
+ tb_debug(sc, DBG_INIT|DBG_INTR, "Attempting to allocate %d "
+ "MSI-X interrupts\n", msgs);
+ error = pci_alloc_msix(sc->dev, &msgs);
+ tb_debug(sc, DBG_INIT|DBG_INTR|DBG_FULL,
+ "pci_alloc_msix return msgs= %d, error= %d\n", msgs, error);
+ }
+
+ if ((error != 0) || (msgs <= 0)) {
+ tb_printf(sc, "Failed to allocate any interrupts\n");
+ msgs = 0;
+ }
+
+ sc->msix_count = msgs;
+ return (error);
+}
+
+static void
+nhi_pci_free_interrupts(struct nhi_softc *sc)
+{
+ int i;
+
+ for (i = 0; i < sc->msix_count; i++) {
+ bus_teardown_intr(sc->dev, sc->irqs[i], sc->intrhand[i]);
+ bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irq_rid[i],
+ sc->irqs[i]);
+ }
+
+ pci_release_msi(sc->dev);
+
+ if (sc->irq_table != NULL) {
+ bus_release_resource(sc->dev, SYS_RES_MEMORY,
+ sc->irq_table_rid, sc->irq_table);
+ sc->irq_table = NULL;
+ }
+
+ if (sc->irq_pba != NULL) {
+ bus_release_resource(sc->dev, SYS_RES_MEMORY,
+ sc->irq_pba_rid, sc->irq_pba);
+ sc->irq_pba = NULL;
+ }
+
+ if (sc->intr_trackers != NULL)
+ free(sc->intr_trackers, M_NHI);
+ return;
+}
+
+int
+nhi_pci_configure_interrupts(struct nhi_softc *sc)
+{
+ struct nhi_intr_tracker *trkr;
+ int rid, i, error;
+
+ nhi_pci_disable_interrupts(sc);
+
+ sc->intr_trackers = malloc(sizeof(struct nhi_intr_tracker) *
+ sc->msix_count, M_NHI, M_ZERO | M_NOWAIT);
+ if (sc->intr_trackers == NULL) {
+ tb_debug(sc, DBG_INIT, "Cannot allocate intr trackers\n");
+ return (ENOMEM);
+ }
+
+ for (i = 0; i < sc->msix_count; i++) {
+ rid = i + 1;
+ trkr = &sc->intr_trackers[i];
+ trkr->sc = sc;
+ trkr->ring = NULL;
+ trkr->vector = i;
+
+ sc->irq_rid[i] = rid;
+ sc->irqs[i] = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ,
+ &sc->irq_rid[i], RF_ACTIVE);
+ if (sc->irqs[i] == NULL) {
+ tb_debug(sc, DBG_INIT,
+ "Cannot allocate interrupt RID %d\n",
+ sc->irq_rid[i]);
+ break;
+ }
+ error = bus_setup_intr(sc->dev, sc->irqs[i], INTR_TYPE_BIO |
+ INTR_MPSAFE, NULL, nhi_intr, trkr, &sc->intrhand[i]);
+ if (error) {
+ tb_debug(sc, DBG_INIT,
+ "cannot setup interrupt RID %d\n", sc->irq_rid[i]);
+ break;
+ }
+ }
+
+ tb_debug(sc, DBG_INIT, "Set up %d interrupts\n", sc->msix_count);
+
+ /* Set the interrupt throttle rate to 128us */
+ for (i = 0; i < 16; i ++)
+ nhi_write_reg(sc, NHI_ITR0 + i * 4, 0x1f4);
+
+ return (error);
+}
+
+#define NHI_SET_INTERRUPT(offset, mask, val) \
+do { \
+ reg = offset / 32; \
+ offset %= 32; \
+ ivr[reg] &= ~(mask << offset); \
+ ivr[reg] |= (val << offset); \
+} while (0)
+
+void
+nhi_pci_enable_interrupt(struct nhi_ring_pair *r)
+{
+ struct nhi_softc *sc = r->sc;
+ uint32_t ivr[5];
+ u_int offset, reg;
+
+ tb_debug(sc, DBG_INIT|DBG_INTR, "Enabling interrupts for ring %d\n",
+ r->ring_num);
+ /*
+ * Compute the routing between event type and MSI-X vector.
+ * 4 bits per descriptor.
+ */
+ ivr[0] = nhi_read_reg(sc, NHI_IVR0);
+ ivr[1] = nhi_read_reg(sc, NHI_IVR1);
+ ivr[2] = nhi_read_reg(sc, NHI_IVR2);
+ ivr[3] = nhi_read_reg(sc, NHI_IVR3);
+ ivr[4] = nhi_read_reg(sc, NHI_IVR4);
+
+ /* Program TX */
+ offset = (r->ring_num + IVR_TX_OFFSET) * 4;
+ NHI_SET_INTERRUPT(offset, 0x0f, r->ring_num);
+
+ /* Now program RX */
+ offset = (r->ring_num + IVR_RX_OFFSET) * 4;
+ NHI_SET_INTERRUPT(offset, 0x0f, r->ring_num);
+
+ /* Last, program Nearly Empty. This one always going to vector 15 */
+ offset = (r->ring_num + IVR_NE_OFFSET) * 4;
+ NHI_SET_INTERRUPT(offset, 0x0f, 0x0f);
+
+ nhi_write_reg(sc, NHI_IVR0, ivr[0]);
+ nhi_write_reg(sc, NHI_IVR1, ivr[1]);
+ nhi_write_reg(sc, NHI_IVR2, ivr[2]);
+ nhi_write_reg(sc, NHI_IVR3, ivr[3]);
+ nhi_write_reg(sc, NHI_IVR4, ivr[4]);
+
+ tb_debug(sc, DBG_INIT|DBG_INTR|DBG_FULL,
+ "Wrote IVR 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ ivr[0], ivr[1], ivr[2], ivr[3], ivr[4]);
+
+ /* Now do the Interrupt Mask Register, 1 bit per descriptor */
+ ivr[0] = nhi_read_reg(sc, NHI_IMR0);
+ ivr[1] = nhi_read_reg(sc, NHI_IMR1);
+
+ /* Tx */
+ offset = r->ring_num + IMR_TX_OFFSET;
+ NHI_SET_INTERRUPT(offset, 0x01, 1);
+
+ /* Rx */
+ offset = r->ring_num + IMR_RX_OFFSET;
+ NHI_SET_INTERRUPT(offset, 0x01, 1);
+
+ /* NE */
+ offset = r->ring_num + IMR_NE_OFFSET;
+ NHI_SET_INTERRUPT(offset, 0x01, 1);
+
+ nhi_write_reg(sc, NHI_IMR0, ivr[0]);
+ nhi_write_reg(sc, NHI_IMR1, ivr[1]);
+ tb_debug(sc, DBG_INIT|DBG_FULL,
+ "Wrote IMR 0x%08x 0x%08x\n", ivr[0], ivr[1]);
+}
+
+void
+nhi_pci_disable_interrupts(struct nhi_softc *sc)
+{
+
+ tb_debug(sc, DBG_INIT, "Disabling interrupts\n");
+ nhi_write_reg(sc, NHI_IMR0, 0);
+ nhi_write_reg(sc, NHI_IMR1, 0);
+ nhi_write_reg(sc, NHI_IVR0, 0);
+ nhi_write_reg(sc, NHI_IVR1, 0);
+ nhi_write_reg(sc, NHI_IVR2, 0);
+ nhi_write_reg(sc, NHI_IVR3, 0);
+ nhi_write_reg(sc, NHI_IVR4, 0);
+
+ /* Dummy reads to clear pending bits */
+ nhi_read_reg(sc, NHI_ISR0);
+ nhi_read_reg(sc, NHI_ISR1);
+}
+
+/*
+ * Icelake controllers need to be notified of power-on
+ */
+static int
+nhi_pci_icl_poweron(struct nhi_softc *sc)
+{
+ device_t dev;
+ uint32_t val;
+ int i, error = 0;
+
+ dev = sc->dev;
+ val = pci_read_config(dev, ICL_VSCAP_9, 4);
+ tb_debug(sc, DBG_INIT, "icl_poweron val= 0x%x\n", val);
+ if (val & ICL_VSCAP9_FWREADY)
+ return (0);
+
+ val = pci_read_config(dev, ICL_VSCAP_22, 4);
+ val |= ICL_VSCAP22_FORCEPWR;
+ tb_debug(sc, DBG_INIT|DBG_FULL, "icl_poweron writing 0x%x\n", val);
+ pci_write_config(dev, ICL_VSCAP_22, val, 4);
+
+ error = ETIMEDOUT;
+ for (i = 0; i < 15; i++) {
+ DELAY(1000000);
+ val = pci_read_config(dev, ICL_VSCAP_9, 4);
+ if (val & ICL_VSCAP9_FWREADY) {
+ error = 0;
+ break;
+ }
+ }
+
+ return (error);
+}
+
+/*
+ * Icelake and Alderlake controllers store their UUID in PCI config space
+ */
+int
+nhi_pci_get_uuid(struct nhi_softc *sc)
+{
+ device_t dev;
+ uint32_t val[4];
+
+ dev = sc->dev;
+ val[0] = pci_read_config(dev, ICL_VSCAP_10, 4);
+ val[1] = pci_read_config(dev, ICL_VSCAP_11, 4);
+ val[2] = 0xffffffff;
+ val[3] = 0xffffffff;
+
+ bcopy(val, &sc->uuid, 16);
+ return (0);
+}
diff --git a/sys/dev/thunderbolt/nhi_reg.h b/sys/dev/thunderbolt/nhi_reg.h
new file mode 100644
index 000000000000..6e71f4c9646b
--- /dev/null
+++ b/sys/dev/thunderbolt/nhi_reg.h
@@ -0,0 +1,332 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt 3 register definitions
+ */
+
+/* $FreeBSD$ */
+
+#ifndef _NHI_REG_H
+#define _NHI_REG_H
+
+/* Some common definitions */
+#define TBT_SEC_NONE 0x00
+#define TBT_SEC_USER 0x01
+#define TBT_SEC_SECURE 0x02
+#define TBT_SEC_DP 0x03
+
+#define GENMASK(h, l) (((~0U) >> (31 - (h))) ^ ((~0U) >> (31 - (l)) >> 1))
+
+/* PCI Vendor and Device ID's */
+#define VENDOR_INTEL 0x8086
+#define DEVICE_AR_2C_NHI 0x1575
+#define DEVICE_AR_DP_B_NHI 0x1577
+#define DEVICE_AR_DP_C_NHI 0x15d2
+#define DEVICE_AR_LP_NHI 0x15bf
+#define DEVICE_ICL_NHI_0 0x8a17
+#define DEVICE_ICL_NHI_1 0x8a0d
+
+#define VENDOR_AMD 0x1022
+#define DEVICE_PINK_SARDINE_0 0x1668
+#define DEVICE_PINK_SARDINE_1 0x1669
+
+/* * * MMIO Registers
+ * * Ring buffer registers
+ *
+ * 32 transmit and receive rings are available, with Ring 0 being the most
+ * important one. The ring descriptors are 16 bytes each, and each set of
+ * TX and RX descriptors are packed together. There are only definitions
+ * for the Ring 0 addresses, others can be directly computed.
+ */
+#define NHI_TX_RING_ADDR_LO 0x00000
+#define NHI_TX_RING_ADDR_HI 0x00004
+#define NHI_TX_RING_PICI 0x00008
+#define TX_RING_CI_MASK GENMASK(15, 0)
+#define TX_RING_PI_SHIFT 16
+#define NHI_TX_RING_SIZE 0x0000c
+
+#define NHI_RX_RING_ADDR_LO 0x08000
+#define NHI_RX_RING_ADDR_HI 0x08004
+#define NHI_RX_RING_PICI 0x08008
+#define RX_RING_CI_MASK GENMASK(15, 0)
+#define RX_RING_PI_SHIFT 16
+#define NHI_RX_RING_SIZE 0x0800c
+#define RX_RING_BUF_SIZE_SHIFT 16
+
+/*
+ * One 32-bit status register encodes one status bit per ring indicates that
+ * the watermark from the control descriptor has been reached.
+ */
+#define NHI_RX_RING_STATUS 0x19400
+
+/*
+ * TX and RX Tables. These are 32 byte control fields for each ring.
+ * Only 8 bytes are controllable by the host software, the rest are a
+ * shadow copy by the controller of the current packet that's being
+ * processed.
+ */
+#define NHI_TX_RING_TABLE_BASE0 0x19800
+#define TX_TABLE_INTERVAL_MASK GENMASK(23,0) /* Isoch interval 256ns */
+#define TX_TABLE_ITE (1 << 27) /* Isoch tx enable */
+#define TX_TABLE_E2E (1 << 28) /* End-to-end flow control */
+#define TX_TABLE_NS (1 << 29) /* PCIe No Snoop */
+#define TX_TABLE_RAW (1 << 30) /* Raw (1)/frame(0) mode */
+#define TX_TABLE_VALID (1 << 31) /* Table entry is valid */
+#define NHI_TX_RING_TABLE_TIMESTAMP 0x19804
+
+#define NHI_RX_RING_TABLE_BASE0 0x29800
+#define RX_TABLE_TX_E2E_HOPID_SHIFT (1 << 12)
+#define RX_TABLE_E2E (1 << 28) /* End-to-end flow control */
+#define RX_TABLE_NS (1 << 29) /* PCIe No Snoop */
+#define RX_TABLE_RAW (1 << 30) /* Raw (1)/frame(0) mode */
+#define RX_TABLE_VALID (1 << 31) /* Table entry is valid */
+#define NHI_RX_RING_TABLE_BASE1 0x29804
+#define RX_TABLE_EOF_MASK (1 << 0)
+#define RX_TABLE_SOF_MASK (1 << 16)
+
+/* * Interrupt Control/Status Registers
+ * Interrupt Status Register (ISR)
+ * Interrupt status for RX, TX, and Nearly Empty events, one bit per
+ * MSI-X vector. Clear on read.
+ * Only 12 bits per operation, instead of 16? I guess it relates to the
+ * number paths, advertised in the HOST_CAPS register, which is wired to
+ * 0x0c for Alpine Ridge.
+ */
+#define NHI_ISR0 0x37800
+#define ISR0_TX_DESC_SHIFT 0
+#define ISR0_RX_DESC_SHIFT 12
+#define ISR0_RX_EMPTY_SHIFT 24
+#define NHI_ISR1 0x37804
+#define ISR1_RX_EMPTY_SHIFT 0
+
+/* * Interrupt Status Clear, corresponds to ISR0/ISR1. Write Only */
+#define NHI_ISC0 0x37808
+#define NHI_ISC1 0x3780c
+
+/* * Interrupt Status Set, corresponds to ISR0/ISR1. Write Only */
+#define NHI_ISS0 0x37810
+#define NHI_ISS1 0x37814
+
+/* * Interrupt Mask, corresponds to ISR0/ISR1. Read-Write */
+#define NHI_IMR0 0x38200
+#define NHI_IMR1 0x38204
+#define IMR_TX_OFFSET 0
+#define IMR_RX_OFFSET 12
+#define IMR_NE_OFFSET 24
+
+/* * Interrupt Mask Clear, corresponds to ISR0/ISR1. Write-only */
+#define NHI_IMC0 0x38208
+#define NHI_IMC1 0x3820c
+
+/* * Interrupt Mask Set, corresponds to ISR0/ISR1. Write-only */
+#define NHI_IMS0 0x38210
+#define NHI_IMS1 0x38214
+
+/*
+ * Interrupt Throttle Rate. One 32 bit register per interrupt,
+ * 16 registers for the 16 MSI-X interrupts. Interval is in 256ns
+ * increments.
+ */
+#define NHI_ITR0 0x38c00
+#define ITR_INTERVAL_SHIFT 0
+#define ITR_COUNTER_SHIFT 16
+
+/*
+ * Interrupt Vector Allocation.
+ * There are 12 4-bit descriptors for TX, 12 4-bit descriptors for RX,
+ * and 12 4-bit descriptors for Nearly Empty. Each descriptor holds
+ * the numerical value of the MSI-X vector that will receive the
+ * corresponding interrupt.
+ * Bits 0-31 of IVR0 and 0-15 of IVR1 are for TX
+ * Bits 16-31 of IVR1 and 0-31 of IVR2 are for RX
+ * Bits 0-31 of IVR3 and 0-15 of IVR4 are for Nearly Empty
+ */
+#define NHI_IVR0 0x38c40
+#define NHI_IVR1 0x38c44
+#define NHI_IVR2 0x38c48
+#define NHI_IVR3 0x38c4c
+#define NHI_IVR4 0x38c50
+#define IVR_TX_OFFSET 0
+#define IVR_RX_OFFSET 12
+#define IVR_NE_OFFSET 24
+
+/* Native Host Interface Control registers */
+#define NHI_HOST_CAPS 0x39640
+#define GET_HOST_CAPS_PATHS(val) ((val) & 0x3f)
+
+/*
+ * This definition comes from the Linux driver. In the USB4 spec, this
+ * register is named Host Interface Control, and the Interrupt Autoclear bit
+ * is at bit17, not bit2. The Linux driver doesn't seem to acknowledge this.
+ */
+#define NHI_DMA_MISC 0x39864
+#define DMA_MISC_INT_AUTOCLEAR (1 << 2)
+
+/* Thunderbolt firmware mailbox registers */
+#define TBT_INMAILDATA 0x39900
+
+#define TBT_INMAILCMD 0x39904
+#define INMAILCMD_CMD_MASK 0xff
+#define INMAILCMD_SAVE_CONNECTED 0x05
+#define INMAILCMD_DISCONNECT_PCIE 0x06
+#define INMAILCMD_DRIVER_UNLOAD_DISCONNECT 0x07
+#define INMAILCMD_DISCONNECT_PORTA 0x10
+#define INMAILCMD_DISCONNECT_PORTB 0x11
+#define INMAILCMD_SETMODE_CERT_TB_1ST_DEPTH 0x20
+#define INMAILCMD_SETMODE_ANY_TB_1ST_DEPTH 0x21
+#define INMAILCMD_SETMODE_CERT_TB_ANY_DEPTH 0x22
+#define INMAILCMD_SETMODE_ANY_TB_ANY_DEPTH 0x23
+#define INMAILCMD_CIO_RESET 0xf0
+#define INMAILCMD_ERROR (1 << 30)
+#define INMAILCMD_OPREQ (1 << 31)
+
+#define TBT_OUTMAILCMD 0x3990c
+#define OUTMAILCMD_STATUS_BUSY (1 << 12)
+#define OUTMAILCMD_OPMODE_MASK 0xf00
+#define OUTMAILCMD_OPMODE_SAFE 0x000
+#define OUTMAILCMD_OPMODE_AUTH 0x100
+#define OUTMAILCMD_OPMODE_ENDPOINT 0x200
+#define OUTMAILCMD_OPMODE_CM_FULL 0x300
+
+#define TBT_FW_STATUS 0x39944
+#define FWSTATUS_ENABLE (1 << 0)
+#define FWSTATUS_INVERT (1 << 1)
+#define FWSTATUS_START (1 << 2)
+#define FWSTATUS_CIO_RESET (1 << 30)
+#define FWSTATUS_CM_READY (1 << 31)
+
+/*
+ * Link Controller (LC) registers. These are in the Vendor Specific
+ * Extended Capability registers in PCICFG.
+ */
+#define AR_LC_MBOX_OUT 0x4c
+#define ICL_LC_MBOX_OUT 0xf0
+#define LC_MBOXOUT_VALID (1 << 0)
+#define LC_MBOXOUT_CMD_SHIFT 1
+#define LC_MBOXOUT_CMD_MASK (0x7f << LC_MBOXOUT_CMD_SHIFT)
+#define LC_MBOXOUT_CMD_GO2SX (0x02 << LC_MBOXOUT_CMD_SHIFT)
+#define LC_MBOXOUT_CMD_GO2SX_NOWAKE (0x03 << LC_MBOXOUT_CMD_SHIFT)
+#define LC_MBOXOUT_CMD_SXEXIT_TBT (0x04 << LC_MBOXOUT_CMD_SHIFT)
+#define LC_MBOXOUT_CMD_SXEXIT_NOTBT (0x05 << LC_MBOXOUT_CMD_SHIFT)
+#define LC_MBOXOUT_CMD_OS_UP (0x06 << LC_MBOXOUT_CMD_SHIFT)
+#define LC_MBOXOUT_DATA_SHIFT 8
+#define SET_LC_MBOXOUT_DATA(val) ((val) << LC_MBOXOUT_DATA_SHIFT)
+
+#define AR_LC_MBOX_IN 0x48
+#define ICL_LC_MBOX_IN 0xec
+#define LC_MBOXIN_DONE (1 << 0)
+#define LC_MBOXIN_CMD_SHIFT 1
+#define LC_MBOXIN_CMD_MASK (0x7f << LC_MBOXIN_CMD_SHIFT)
+#define LC_MBOXIN_DATA_SHIFT 8
+#define GET_LC_MBOXIN_DATA(val) ((val) >> LC_MBOXIN_DATA_SHIFT)
+
+/* Other Vendor Specific registers */
+#define AR_VSCAP_1C 0x1c
+#define AR_VSCAP_B0 0xb0
+
+#define ICL_VSCAP_9 0xc8
+#define ICL_VSCAP9_FWREADY (1 << 31)
+#define ICL_VSCAP_10 0xcc
+#define ICL_VSCAP_11 0xd0
+#define ICL_VSCAP_22 0xfc
+#define ICL_VSCAP22_FORCEPWR (1 << 1)
+
+/* * Data structures
+ * Transmit buffer descriptor, 12.3.1. Must be aligned on a 4byte boundary.
+ */
+struct nhi_tx_buffer_desc {
+ uint32_t addr_lo;
+ uint32_t addr_hi;
+ uint16_t eof_len;
+#define TX_BUFFER_DESC_LEN_MASK 0xfff
+#define TX_BUFFER_DESC_EOF_SHIFT 12
+ uint8_t flags_sof;
+#define TX_BUFFER_DESC_SOF_MASK 0xf
+#define TX_BUFFER_DESC_IDE (1 << 4) /* Isoch DMA enable */
+#define TX_BUFFER_DESC_DONE (1 << 5) /* Descriptor Done */
+#define TX_BUFFER_DESC_RS (1 << 6) /* Request Status/Done */
+#define TX_BUFFER_DESC_IE (1 << 7) /* Interrupt Enable */
+ uint8_t offset;
+ uint32_t payload_time;
+} __packed;
+
+/*
+ * Receive buffer descriptor, 12.4.1. 4 byte aligned. This goes into
+ * the descriptor ring, but changes into the _post form when the
+ * controller uses it.
+ */
+struct nhi_rx_buffer_desc {
+ uint32_t addr_lo;
+ uint32_t addr_hi;
+ uint16_t reserved0;
+ uint8_t flags;
+#define RX_BUFFER_DESC_RS (1 << 6) /* Request Status/Done */
+#define RX_BUFFER_DESC_IE (1 << 7) /* Interrupt Enable */
+ uint8_t offset;
+ uint32_t reserved1;
+} __packed;
+
+/*
+ * Receive buffer descriptor, after the controller fills it in
+ */
+struct nhi_rx_post_desc {
+ uint32_t addr_lo;
+ uint32_t addr_hi;
+ uint16_t eof_len;
+#define RX_BUFFER_DESC_LEN_MASK 0xfff
+#define RX_BUFFER_DESC_EOF_SHIFT 12
+ uint8_t flags_sof;
+#define RX_BUFFER_DESC_SOF_MASK 0xf
+#define RX_BUFFER_DESC_CRC_ERROR (1 << 4) /* CRC error (frame mode) */
+#define RX_BUFFER_DESC_DONE (1 << 5) /* Descriptor Done */
+#define RX_BUFFER_DESC_OVERRUN (1 << 6) /* Buffer overrun */
+#define RX_BUFFER_DESC_IE (1 << 7) /* Interrupt Enable */
+ uint8_t offset;
+ uint32_t payload_time;
+} __packed;
+
+union nhi_ring_desc {
+ struct nhi_tx_buffer_desc tx;
+ struct nhi_rx_buffer_desc rx;
+ struct nhi_rx_post_desc rxpost;
+ uint32_t dword[4];
+};
+
+/* Protocol Defined Field (PDF) */
+#define PDF_READ 0x01
+#define PDF_WRITE 0x02
+#define PDF_NOTIFY 0x03
+#define PDF_NOTIFY_ACK 0x04
+#define PDF_HOTPLUG 0x05
+#define PDF_XDOMAIN_REQ 0x06
+#define PDF_XDOMAIN_RESP 0x07
+/* Thunderbolt-only */
+#define PDF_CM_EVENT 0x0a
+#define PDF_CM_REQ 0x0b
+#define PDF_CM_RESP 0x0c
+
+#endif /* _NHI_REG_H */
diff --git a/sys/dev/thunderbolt/nhi_var.h b/sys/dev/thunderbolt/nhi_var.h
new file mode 100644
index 000000000000..2b9e878af47d
--- /dev/null
+++ b/sys/dev/thunderbolt/nhi_var.h
@@ -0,0 +1,277 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt 3 / Native Host Interface driver variables
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _NHI_VAR
+#define _NHI_VAR
+
+MALLOC_DECLARE(M_NHI);
+
+#define NHI_MSIX_MAX 32
+#define NHI_RING0_TX_DEPTH 16
+#define NHI_RING0_RX_DEPTH 16
+#define NHI_DEFAULT_NUM_RINGS 1
+#define NHI_MAX_NUM_RINGS 32 /* XXX 2? */
+#define NHI_RING0_FRAME_SIZE 256
+#define NHI_MAILBOX_TIMEOUT 15
+
+#define NHI_CMD_TIMEOUT 3 /* 3 seconds */
+
+struct nhi_softc;
+struct nhi_ring_pair;
+struct nhi_intr_tracker;
+struct nhi_cmd_frame;
+struct hcm_softc;
+struct router_softc;
+
+struct nhi_cmd_frame {
+ TAILQ_ENTRY(nhi_cmd_frame) cm_link;
+ uint32_t *data;
+ bus_addr_t data_busaddr;
+ u_int req_len;
+ uint16_t flags;
+#define CMD_MAPPED (1 << 0)
+#define CMD_POLLED (1 << 1)
+#define CMD_REQ_COMPLETE (1 << 2)
+#define CMD_RESP_COMPLETE (1 << 3)
+#define CMD_RESP_OVERRUN (1 << 4)
+ uint16_t retries;
+ uint16_t pdf;
+ uint16_t idx;
+
+ void *context;
+ u_int timeout;
+
+ uint32_t *resp_buffer;
+ u_int resp_len;
+};
+
+#define NHI_RING_NAMELEN 16
+struct nhi_ring_pair {
+ struct nhi_softc *sc;
+
+ union nhi_ring_desc *tx_ring;
+ union nhi_ring_desc *rx_ring;
+
+ uint16_t tx_pi;
+ uint16_t tx_ci;
+ uint16_t rx_pi;
+ uint16_t rx_ci;
+
+ uint16_t rx_pici_reg;
+ uint16_t tx_pici_reg;
+
+ struct nhi_cmd_frame **rx_cmd_ring;
+ struct nhi_cmd_frame **tx_cmd_ring;
+
+ struct mtx mtx;
+ char name[NHI_RING_NAMELEN];
+ struct nhi_intr_tracker *tracker;
+ SLIST_ENTRY(nhi_ring_pair) ring_link;
+
+ TAILQ_HEAD(, nhi_cmd_frame) tx_head;
+ TAILQ_HEAD(, nhi_cmd_frame) rx_head;
+
+ uint16_t tx_ring_depth;
+ uint16_t tx_ring_mask;
+ uint16_t rx_ring_depth;
+ uint16_t rx_ring_mask;
+ uint16_t rx_buffer_size;
+ u_char ring_num;
+
+ bus_dma_tag_t ring_dmat;
+ bus_dmamap_t ring_map;
+ void *ring;
+ bus_addr_t tx_ring_busaddr;
+ bus_addr_t rx_ring_busaddr;
+
+ bus_dma_tag_t frames_dmat;
+ bus_dmamap_t frames_map;
+ void *frames;
+ bus_addr_t tx_frames_busaddr;
+ bus_addr_t rx_frames_busaddr;
+};
+
+/* PDF-indexed array of dispatch routines for interrupts */
+typedef void (nhi_ring_cb_t)(void *, union nhi_ring_desc *,
+ struct nhi_cmd_frame *);
+struct nhi_pdf_dispatch {
+ nhi_ring_cb_t *cb;
+ void *context;
+};
+
+struct nhi_intr_tracker {
+ struct nhi_softc *sc;
+ struct nhi_ring_pair *ring;
+ struct nhi_pdf_dispatch txpdf[16];
+ struct nhi_pdf_dispatch rxpdf[16];
+ u_int vector;
+};
+
+struct nhi_softc {
+ device_t dev;
+ device_t ufp;
+ u_int debug;
+ u_int hwflags;
+#define NHI_TYPE_UNKNOWN 0x00
+#define NHI_TYPE_AR 0x01 /* Alpine Ridge */
+#define NHI_TYPE_TR 0x02 /* Titan Ridge */
+#define NHI_TYPE_ICL 0x03 /* IceLake */
+#define NHI_TYPE_MR 0x04 /* Maple Ridge */
+#define NHI_TYPE_ADL 0x05 /* AlderLake */
+#define NHI_TYPE_USB4 0x0f
+#define NHI_TYPE_MASK 0x0f
+#define NHI_MBOX_BUSY 0x10
+ u_int caps;
+#define NHI_CAP_ICM 0x01
+#define NHI_CAP_HCM 0x02
+#define NHI_USE_ICM(sc) ((sc)->caps & NHI_CAP_ICM)
+#define NHI_USE_HCM(sc) ((sc)->caps & NHI_CAP_HCM)
+ struct hcm_softc *hcm;
+ struct router_softc *root_rsc;
+
+ struct nhi_ring_pair *ring0;
+ struct nhi_intr_tracker *intr_trackers;
+
+ uint16_t path_count;
+ uint16_t max_ring_count;
+
+ struct mtx nhi_mtx;
+ SLIST_HEAD(, nhi_ring_pair) ring_list;
+
+ int msix_count;
+ struct resource *irqs[NHI_MSIX_MAX];
+ void *intrhand[NHI_MSIX_MAX];
+ int irq_rid[NHI_MSIX_MAX];
+ struct resource *irq_pba;
+ int irq_pba_rid;
+ struct resource *irq_table;
+ int irq_table_rid;
+
+ struct resource *regs_resource;
+ bus_space_handle_t regs_bhandle;
+ bus_space_tag_t regs_btag;
+ int regs_rid;
+
+ bus_dma_tag_t parent_dmat;
+
+ bus_dma_tag_t ring0_dmat;
+ bus_dmamap_t ring0_map;
+ void *ring0_frames;
+ bus_addr_t ring0_frames_busaddr;
+ struct nhi_cmd_frame *ring0_cmds;
+
+ struct sysctl_ctx_list *sysctl_ctx;
+ struct sysctl_oid *sysctl_tree;
+
+ struct intr_config_hook ich;
+
+ uint8_t force_hcm;
+#define NHI_FORCE_HCM_DEFAULT 0x00
+#define NHI_FORCE_HCM_ON 0x01
+#define NHI_FORCE_HCM_OFF 0x02
+
+ uint8_t uuid[16];
+ uint8_t lc_uuid[16];
+};
+
+struct nhi_dispatch {
+ uint8_t pdf;
+ nhi_ring_cb_t *cb;
+ void *context;
+};
+
+#define NHI_IS_AR(sc) (((sc)->hwflags & NHI_TYPE_MASK) == NHI_TYPE_AR)
+#define NHI_IS_TR(sc) (((sc)->hwflags & NHI_TYPE_MASK) == NHI_TYPE_TR)
+#define NHI_IS_ICL(sc) (((sc)->hwflags & NHI_TYPE_MASK) == NHI_TYPE_ICL)
+#define NHI_IS_USB4(sc) (((sc)->hwflags & NHI_TYPE_MASK) == NHI_TYPE_USB4)
+
+int nhi_pci_configure_interrupts(struct nhi_softc *sc);
+void nhi_pci_enable_interrupt(struct nhi_ring_pair *r);
+void nhi_pci_disable_interrupts(struct nhi_softc *sc);
+int nhi_pci_get_uuid(struct nhi_softc *sc);
+int nhi_read_lc_mailbox(struct nhi_softc *, u_int reg, uint32_t *val);
+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 *);
+
+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 *);
+
+int nhi_inmail_cmd(struct nhi_softc *, uint32_t, uint32_t);
+int nhi_outmail_cmd(struct nhi_softc *, uint32_t *);
+
+int nhi_tx_schedule(struct nhi_ring_pair *, struct nhi_cmd_frame *);
+int nhi_tx_synchronous(struct nhi_ring_pair *, struct nhi_cmd_frame *);
+void nhi_intr(void *);
+
+int nhi_register_pdf(struct nhi_ring_pair *, struct nhi_dispatch *,
+ struct nhi_dispatch *);
+int nhi_deregister_pdf(struct nhi_ring_pair *, struct nhi_dispatch *,
+ struct nhi_dispatch *);
+
+/* Low level read/write MMIO registers */
+static __inline uint32_t
+nhi_read_reg(struct nhi_softc *sc, u_int offset)
+{
+ return (le32toh(bus_space_read_4(sc->regs_btag, sc->regs_bhandle,
+ offset)));
+}
+
+static __inline void
+nhi_write_reg(struct nhi_softc *sc, u_int offset, uint32_t val)
+{
+ bus_space_write_4(sc->regs_btag, sc->regs_bhandle, offset,
+ htole32(val));
+}
+
+static __inline struct nhi_cmd_frame *
+nhi_alloc_tx_frame_locked(struct nhi_ring_pair *r)
+{
+ struct nhi_cmd_frame *cmd;
+
+ if ((cmd = TAILQ_FIRST(&r->tx_head)) != NULL)
+ TAILQ_REMOVE(&r->tx_head, cmd, cm_link);
+ return (cmd);
+}
+
+static __inline void
+nhi_free_tx_frame_locked(struct nhi_ring_pair *r, struct nhi_cmd_frame *cmd)
+{
+ /* Clear all flags except for MAPPED */
+ cmd->flags &= CMD_MAPPED;
+ cmd->resp_buffer = NULL;
+ TAILQ_INSERT_TAIL(&r->tx_head, cmd, cm_link);
+}
+
+#endif /* _NHI_VAR */
diff --git a/sys/dev/thunderbolt/nhi_wmi.c b/sys/dev/thunderbolt/nhi_wmi.c
new file mode 100644
index 000000000000..3feba3bcd8d1
--- /dev/null
+++ b/sys/dev/thunderbolt/nhi_wmi.c
@@ -0,0 +1,198 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_acpi.h"
+#include "opt_thunderbolt.h"
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/proc.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/sbuf.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+#include "acpi_wmi_if.h"
+
+ACPI_MODULE_NAME("THUNDERBOLT-NHI-WMI")
+
+#define ACPI_INTEL_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341"
+
+struct nhi_wmi_softc {
+ device_t dev;
+ device_t wmi_dev;
+ u_int state;
+ char *guid;
+ struct sysctl_ctx_list *sysctl_ctx;
+ struct sysctl_oid *sysctl_tree;
+};
+
+ACPI_SERIAL_DECL(nhi_wmi, "Thunderbolt NHI WMI device");
+
+static void nhi_wmi_identify(driver_t *driver, device_t parent);
+static int nhi_wmi_probe(device_t dev);
+static int nhi_wmi_attach(device_t dev);
+static int nhi_wmi_detach(device_t dev);
+static int nhi_wmi_sysctl(SYSCTL_HANDLER_ARGS);
+static int nhi_wmi_evaluate_method(struct nhi_wmi_softc *sc,
+ int method, uint32_t arg0, uint32_t *retval);
+
+static device_method_t nhi_wmi_methods[] = {
+ DEVMETHOD(device_identify, nhi_wmi_identify),
+ DEVMETHOD(device_probe, nhi_wmi_probe),
+ DEVMETHOD(device_attach, nhi_wmi_attach),
+ DEVMETHOD(device_detach, nhi_wmi_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t nhi_wmi_driver = {
+ "nhi_wmi",
+ nhi_wmi_methods,
+ sizeof(struct nhi_wmi_softc)
+};
+
+DRIVER_MODULE(nhi_wmi, acpi_wmi, nhi_wmi_driver,
+ NULL, NULL);
+MODULE_DEPEND(nhi_wmi, acpi_wmi, 1, 1, 1);
+MODULE_DEPEND(nhi_wmi, acpi, 1, 1, 1);
+
+static void
+nhi_wmi_identify(driver_t *driver, device_t parent)
+{
+
+ if (acpi_disabled("nhi_wmi") != 0)
+ return;
+
+ if (device_find_child(parent, "nhi_wmi", -1) != NULL)
+ return;
+
+ if (ACPI_WMI_PROVIDES_GUID_STRING(parent,
+ ACPI_INTEL_THUNDERBOLT_GUID) == 0)
+ return;
+
+ if (BUS_ADD_CHILD(parent, 0, "nhi_wmi", -1) == NULL)
+ device_printf(parent, "failed to add nhi_wmi\n");
+}
+
+static int
+nhi_wmi_probe(device_t dev)
+{
+
+ if (ACPI_WMI_PROVIDES_GUID_STRING(device_get_parent(dev),
+ ACPI_INTEL_THUNDERBOLT_GUID) == 0)
+ return (EINVAL);
+ device_set_desc(dev, "Thunderbolt WMI Endpoint");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+nhi_wmi_attach(device_t dev)
+{
+ struct nhi_wmi_softc *sc;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ sc->wmi_dev = device_get_parent(dev);
+
+ sc->sysctl_ctx = device_get_sysctl_ctx(dev);
+ sc->sysctl_tree = device_get_sysctl_tree(dev);
+ sc->state = 0;
+ sc->guid = ACPI_INTEL_THUNDERBOLT_GUID;
+
+ SYSCTL_ADD_STRING(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
+ OID_AUTO, "GUID", CTLFLAG_RD, sc->guid, 0, "WMI GUID");
+ SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
+ OID_AUTO, "force_power", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_MPSAFE,
+ sc, 0, nhi_wmi_sysctl, "I", "Force controller power on");
+
+ return (0);
+}
+
+static int
+nhi_wmi_detach(device_t dev)
+{
+
+ return (0);
+}
+
+static int
+nhi_wmi_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ struct nhi_wmi_softc *sc;
+ int error, arg;
+
+ sc = (struct nhi_wmi_softc *)arg1;
+ arg = !!sc->state;
+ error = sysctl_handle_int(oidp, &arg, 0, req);
+ if (!error && req->newptr != NULL) {
+ ACPI_SERIAL_BEGIN(nhi_wmi);
+ error = nhi_wmi_evaluate_method(sc, 1, arg, NULL);
+ ACPI_SERIAL_END(nhi_wmi);
+ if (error == 0)
+ sc->state = arg;
+ }
+ return (error);
+}
+
+static int
+nhi_wmi_evaluate_method(struct nhi_wmi_softc *sc, int method, uint32_t arg0,
+ uint32_t *retval)
+{
+ ACPI_OBJECT *obj;
+ ACPI_BUFFER in, out;
+ uint32_t val, params[1];
+
+ params[0] = arg0;
+ in.Pointer = &params;
+ in.Length = sizeof(params);
+ out.Pointer = NULL;
+ out.Length = ACPI_ALLOCATE_BUFFER;
+
+ if (ACPI_FAILURE(ACPI_WMI_EVALUATE_CALL(sc->wmi_dev,
+ ACPI_INTEL_THUNDERBOLT_GUID, 0, method, &in, &out))) {
+ AcpiOsFree(out.Pointer);
+ return (EINVAL);
+ }
+
+ obj = out.Pointer;
+ if (obj != NULL && obj->Type == ACPI_TYPE_INTEGER)
+ val = (uint32_t)obj->Integer.Value;
+ else
+ val = 0;
+
+ AcpiOsFree(out.Pointer);
+ if (retval)
+ *retval = val;
+
+ return (0);
+}
diff --git a/sys/dev/thunderbolt/router.c b/sys/dev/thunderbolt/router.c
new file mode 100644
index 000000000000..a3b418d77fac
--- /dev/null
+++ b/sys/dev/thunderbolt/router.c
@@ -0,0 +1,939 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_thunderbolt.h"
+
+/* Config space access for switches, ports, and devices in TB3 and USB4 */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/taskqueue.h>
+#include <sys/gsb_crc32.h>
+#include <sys/endian.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/bus.h>
+#include <machine/stdarg.h>
+
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_var.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/router_var.h>
+#include <dev/thunderbolt/tb_debug.h>
+
+static int router_alloc_cmd(struct router_softc *, struct router_command **);
+static void router_free_cmd(struct router_softc *, struct router_command *);
+static int _tb_router_attach(struct router_softc *);
+static void router_prepare_read(struct router_softc *, struct router_command *,
+ int);
+static int _tb_config_read(struct router_softc *, u_int, u_int, u_int, u_int,
+ uint32_t *, void *, struct router_command **);
+static int router_schedule(struct router_softc *, struct router_command *);
+static int router_schedule_locked(struct router_softc *,
+ struct router_command *);
+static nhi_ring_cb_t router_complete_intr;
+static nhi_ring_cb_t router_response_intr;
+static nhi_ring_cb_t router_notify_intr;
+
+#define CFG_DEFAULT_RETRIES 3
+#define CFG_DEFAULT_TIMEOUT 2
+
+static int
+router_lookup_device(struct router_softc *sc, tb_route_t route,
+ struct router_softc **dev)
+{
+ struct router_softc *cursor;
+ uint64_t search_rt, remainder_rt, this_rt;
+ uint8_t hop;
+
+ KASSERT(dev != NULL, ("dev cannot be NULL\n"));
+
+ cursor = tb_config_get_root(sc);
+ remainder_rt = search_rt = route.lo | ((uint64_t)route.hi << 32);
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "%s: Searching for router 0x%016jx\n", __func__, search_rt);
+
+ while (cursor != NULL) {
+ this_rt = TB_ROUTE(cursor);
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "Comparing cursor route 0x%016jx\n", this_rt);
+ if (this_rt == search_rt)
+ break;
+
+ /* Prepare to go to the next hop node in the route */
+ hop = remainder_rt & 0xff;
+ remainder_rt >>= 8;
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "hop= 0x%02x, remainder= 0x%016jx\n", hop, remainder_rt);
+
+ /*
+ * An adapter index of 0x0 is only for the host interface
+ * adapter on the root route. The only time that
+ * it's valid for searches is when you're looking for the
+ * root route, and that case has already been handled.
+ */
+ if (hop == 0) {
+ tb_debug(sc, DBG_ROUTER,
+ "End of route chain, route not found\n");
+ return (ENOENT);
+ }
+
+ if (hop > cursor->max_adap) {
+ tb_debug(sc, DBG_ROUTER,
+ "Route hop out of range for parent\n");
+ return (EINVAL);
+ }
+
+ if (cursor->adapters == NULL) {
+ tb_debug(sc, DBG_ROUTER,
+ "Error, router not fully initialized\n");
+ return (EINVAL);
+ }
+
+ cursor = cursor->adapters[hop];
+ }
+
+ if (cursor == NULL)
+ return (ENOENT);
+
+ *dev = cursor;
+ return (0);
+}
+
+static int
+router_insert(struct router_softc *sc, struct router_softc *parent)
+{
+ uint64_t this_rt;
+ uint8_t this_hop;
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_insert called\n");
+
+ if (parent == NULL) {
+ tb_debug(sc, DBG_ROUTER, "Parent cannot be NULL in insert\n");
+ return (EINVAL);
+ }
+
+ this_rt = TB_ROUTE(sc);
+ if (((this_rt >> (sc->depth * 8)) > 0xffULL) ||
+ (parent->depth + 1 != sc->depth)) {
+ tb_debug(sc, DBG_ROUTER, "Added route 0x%08x%08x is not a "
+ "direct child of the parent route 0x%08x%08x\n",
+ sc->route.hi, sc->route.lo, parent->route.hi,
+ parent->route.lo);
+ return (EINVAL);
+ }
+
+ this_hop = (uint8_t)(this_rt >> (sc->depth * 8));
+
+ tb_debug(sc, DBG_ROUTER, "Inserting route 0x%08x%08x with last hop "
+ "of 0x%02x and depth of %d\n", sc->route.hi, sc->route.lo,
+ this_hop, sc->depth);
+
+ if (this_hop > parent->max_adap) {
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "Inserted route is out of range of the parent\n");
+ return (EINVAL);
+ }
+
+ if (parent->adapters[this_hop] != NULL) {
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "Inserted route already exists\n");
+ return (EEXIST);
+ }
+
+ parent->adapters[this_hop] = sc;
+
+ tb_debug(sc, DBG_ROUTER, "Added router 0x%08x%08x to parent "
+ "0x%08x%08x\n", sc->route.hi, sc->route.lo, parent->route.hi,
+ parent->route.lo);
+ return (0);
+}
+
+static int
+router_register_interrupts(struct router_softc *sc)
+{
+ struct nhi_dispatch tx[] = { { PDF_READ, router_complete_intr, sc },
+ { PDF_WRITE, router_complete_intr, sc },
+ { 0, NULL, NULL } };
+ struct nhi_dispatch rx[] = { { PDF_READ, router_response_intr, sc },
+ { PDF_WRITE, router_response_intr, sc },
+ { PDF_NOTIFY, router_notify_intr, sc },
+ { 0, NULL, NULL } };
+
+ return (nhi_register_pdf(sc->ring0, tx, rx));
+}
+
+int
+tb_router_attach(struct router_softc *parent, tb_route_t route)
+{
+ struct router_softc *sc;
+
+ tb_debug(parent, DBG_ROUTER|DBG_EXTRA, "tb_router_attach called\n");
+
+ sc = malloc(sizeof(*sc), M_THUNDERBOLT, M_ZERO|M_NOWAIT);
+ if (sc == NULL) {
+ tb_debug(parent, DBG_ROUTER, "Cannot allocate root router\n");
+ return (ENOMEM);
+ }
+
+ sc->dev = parent->dev;
+ sc->debug = parent->debug;
+ sc->ring0 = parent->ring0;
+ sc->route = route;
+ sc->nsc = parent->nsc;
+
+ mtx_init(&sc->mtx, "tbcfg", "Thunderbolt Router Config", MTX_DEF);
+ TAILQ_INIT(&sc->cmd_queue);
+
+ router_insert(sc, parent);
+
+ return (_tb_router_attach(sc));
+}
+
+int
+tb_router_attach_root(struct nhi_softc *nsc, tb_route_t route)
+{
+ struct router_softc *sc;
+ int error;
+
+ tb_debug(nsc, DBG_ROUTER|DBG_EXTRA, "tb_router_attach_root called\n");
+
+ sc = malloc(sizeof(*sc), M_THUNDERBOLT, M_ZERO|M_NOWAIT);
+ if (sc == NULL) {
+ tb_debug(nsc, DBG_ROUTER, "Cannot allocate root router\n");
+ return (ENOMEM);
+ }
+
+ sc->dev = nsc->dev;
+ sc->debug = nsc->debug;
+ sc->ring0 = nsc->ring0;
+ sc->route = route;
+ sc->nsc = nsc;
+
+ mtx_init(&sc->mtx, "tbcfg", "Thunderbolt Router Config", MTX_DEF);
+ TAILQ_INIT(&sc->cmd_queue);
+
+ /*
+ * This router is semi-virtual and represents the router that's part
+ * of the NHI DMA engine. Commands can't be issued to the topology
+ * until the NHI is initialized and this router is initialized, so
+ * there's no point in registering router interrupts earlier than this,
+ * even if other routers are found first.
+ */
+ tb_config_set_root(sc);
+ error = router_register_interrupts(sc);
+ if (error) {
+ tb_router_detach(sc);
+ return (error);
+ }
+
+ error = _tb_router_attach(sc);
+ if (error)
+ return (error);
+
+ bcopy((uint8_t *)sc->uuid, nsc->uuid, 16);
+ return (0);
+}
+
+static int
+_tb_router_attach(struct router_softc *sc)
+{
+ struct tb_cfg_router *cfg;
+ uint32_t *buf;
+ int error, up;
+
+ buf = malloc(9 * 4, M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+ if (buf == NULL)
+ return (ENOMEM);
+
+ error = tb_config_router_read_polled(sc, 0, 9, buf);
+ if (error != 0) {
+ free(buf, M_THUNDERBOLT);
+ return (error);
+ }
+
+ cfg = (struct tb_cfg_router *)buf;
+ up = GET_ROUTER_CS_UPSTREAM_ADAP(cfg);
+ sc->max_adap = GET_ROUTER_CS_MAX_ADAP(cfg);
+ sc->depth = GET_ROUTER_CS_DEPTH(cfg);
+ sc->uuid[0] = cfg->uuid_lo;
+ sc->uuid[1] = cfg->uuid_hi;
+ sc->uuid[2] = 0xffffffff;
+ sc->uuid[3] = 0xffffffff;
+ tb_debug(sc, DBG_ROUTER, "Router upstream_port= %d, max_port= %d, "
+ "depth= %d\n", up, sc->max_adap, sc->depth);
+ free(buf, M_THUNDERBOLT);
+
+ /* Downstream adapters are indexed in the array allocated here. */
+ sc->max_adap = MIN(sc->max_adap, ROUTER_CS1_MAX_ADAPTERS);
+ sc->adapters = malloc((1 + sc->max_adap) * sizeof(void *),
+ M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+ if (sc->adapters == NULL) {
+ tb_debug(sc, DBG_ROUTER,
+ "Cannot allocate downstream adapter memory\n");
+ return (ENOMEM);
+ }
+
+ tb_debug(sc, DBG_ROUTER, "Router created, route 0x%08x%08x\n",
+ sc->route.hi, sc->route.lo);
+
+ return (0);
+}
+
+int
+tb_router_detach(struct router_softc *sc)
+{
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "tb_router_deattach called\n");
+
+ if (TAILQ_FIRST(&sc->cmd_queue) != NULL)
+ return (EBUSY);
+
+ mtx_destroy(&sc->mtx);
+
+ if (sc->adapters != NULL)
+ free(sc->adapters, M_THUNDERBOLT);
+
+ if (sc != NULL)
+ free(sc, M_THUNDERBOLT);
+
+ return (0);
+}
+
+static void
+router_get_config_cb(struct router_softc *sc, struct router_command *cmd,
+ void *arg)
+{
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_get_config_cb called\n");
+
+ /*
+ * Only do the copy if the command didn't have a notify event thrown.
+ * These events serve as asynchronous exception signals, which is
+ * cumbersome.
+ */
+ if (cmd->ev == 0)
+ bcopy((uint8_t *)cmd->resp_buffer,
+ (uint8_t *)cmd->callback_arg, cmd->dwlen * 4);
+
+ mtx_lock(&sc->mtx);
+ sc->inflight_cmd = NULL;
+
+ if ((cmd->flags & RCMD_POLLED) == 0)
+ wakeup(cmd);
+ else
+ cmd->flags |= RCMD_POLL_COMPLETE;
+
+ router_schedule_locked(sc, NULL);
+ mtx_unlock(&sc->mtx);
+}
+
+int
+tb_config_read(struct router_softc *sc, u_int space, u_int adapter,
+ u_int offset, u_int dwlen, uint32_t *buf)
+{
+ struct router_command *cmd;
+ int error, retries;
+
+ if ((error = _tb_config_read(sc, space, adapter, offset, dwlen, buf,
+ router_get_config_cb, &cmd)) != 0)
+ return (error);
+
+ retries = cmd->retries;
+ mtx_lock(&sc->mtx);
+ while (retries-- >= 0) {
+ error = router_schedule_locked(sc, cmd);
+ if (error)
+ break;
+
+ error = msleep(cmd, &sc->mtx, 0, "tbtcfg", cmd->timeout * hz);
+ if (error != EWOULDBLOCK)
+ break;
+ sc->inflight_cmd = NULL;
+ tb_debug(sc, DBG_ROUTER, "Config command timed out, retries=%d\n", retries);
+ }
+
+ if (cmd->ev != 0)
+ error = EINVAL;
+ router_free_cmd(sc, cmd);
+ mtx_unlock(&sc->mtx);
+ return (error);
+}
+
+int
+tb_config_read_polled(struct router_softc *sc, u_int space, u_int adapter,
+ u_int offset, u_int dwlen, uint32_t *buf)
+{
+ struct router_command *cmd;
+ int error, retries, timeout;
+
+ if ((error = _tb_config_read(sc, space, adapter, offset, dwlen, buf,
+ router_get_config_cb, &cmd)) != 0)
+ return (error);
+
+ retries = cmd->retries;
+ cmd->flags |= RCMD_POLLED;
+ timeout = cmd->timeout * 1000000;
+
+ mtx_lock(&sc->mtx);
+ while (retries-- >= 0) {
+ error = router_schedule_locked(sc, cmd);
+ if (error)
+ break;
+ mtx_unlock(&sc->mtx);
+
+ while (timeout > 0) {
+ DELAY(100 * 1000);
+ if ((cmd->flags & RCMD_POLL_COMPLETE) != 0)
+ break;
+ timeout -= 100000;
+ }
+
+ mtx_lock(&sc->mtx);
+ if ((cmd->flags & RCMD_POLL_COMPLETE) == 0) {
+ error = ETIMEDOUT;
+ sc->inflight_cmd = NULL;
+ tb_debug(sc, DBG_ROUTER, "Config command timed out, retries=%d\n", retries);
+ continue;
+ } else
+ break;
+ }
+
+ if (cmd->ev != 0)
+ error = EINVAL;
+ router_free_cmd(sc, cmd);
+ mtx_unlock(&sc->mtx);
+ return (error);
+}
+
+int
+tb_config_read_async(struct router_softc *sc, u_int space, u_int adapter,
+ u_int offset, u_int dwlen, uint32_t *buf, void *cb)
+{
+ struct router_command *cmd;
+ int error;
+
+ if ((error = _tb_config_read(sc, space, adapter, offset, dwlen, buf,
+ cb, &cmd)) != 0)
+ return (error);
+
+ error = router_schedule(sc, cmd);
+
+ return (error);
+}
+
+static int
+_tb_config_read(struct router_softc *sc, u_int space, u_int adapter,
+ u_int offset, u_int dwlen, uint32_t *buf, void *cb,
+ struct router_command **rcmd)
+{
+ struct router_command *cmd;
+ struct tb_cfg_read *msg;
+ int error;
+
+ if ((error = router_alloc_cmd(sc, &cmd)) != 0)
+ return (error);
+
+ msg = router_get_frame_data(cmd);
+ bzero(msg, sizeof(*msg));
+ msg->route.hi = sc->route.hi;
+ msg->route.lo = sc->route.lo;
+ msg->addr_attrs = TB_CONFIG_ADDR(0, space, adapter, dwlen, offset);
+ cmd->callback = cb;
+ cmd->callback_arg = buf;
+ cmd->dwlen = dwlen;
+ router_prepare_read(sc, cmd, sizeof(*msg));
+
+ if (rcmd != NULL)
+ *rcmd = cmd;
+
+ return (0);
+}
+
+int
+tb_config_write(struct router_softc *sc, u_int space, u_int adapter,
+ u_int offset, u_int dwlen, uint32_t *buf)
+{
+
+ return(0);
+}
+
+static int
+router_alloc_cmd(struct router_softc *sc, struct router_command **rcmd)
+{
+ struct router_command *cmd;
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_alloc_cmd\n");
+
+ cmd = malloc(sizeof(*cmd), M_THUNDERBOLT, M_ZERO|M_NOWAIT);
+ if (cmd == NULL) {
+ tb_debug(sc, DBG_ROUTER, "Cannot allocate cmd/response\n");
+ return (ENOMEM);
+ }
+
+ cmd->nhicmd = nhi_alloc_tx_frame(sc->ring0);
+ if (cmd->nhicmd == NULL) {
+ tb_debug(sc, DBG_ROUTER, "Cannot allocate command frame\n");
+ free(cmd, M_THUNDERBOLT);
+ return (EBUSY);
+ }
+
+ cmd->sc = sc;
+ *rcmd = cmd;
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "Allocated command with index %d\n",
+ cmd->nhicmd->idx);
+
+ return (0);
+}
+
+static void
+router_free_cmd(struct router_softc *sc, struct router_command *cmd)
+{
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_free_cmd\n");
+
+ if (cmd == NULL)
+ return;
+
+ if (cmd->nhicmd != NULL) {
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "Freeing nhi command %d\n",
+ cmd->nhicmd->idx);
+ nhi_free_tx_frame(sc->ring0, cmd->nhicmd);
+ }
+ free(cmd, M_THUNDERBOLT);
+
+ return;
+}
+
+static void
+router_prepare_read(struct router_softc *sc, struct router_command *cmd,
+ int len)
+{
+ struct nhi_cmd_frame *nhicmd;
+ uint32_t *msg;
+ int msglen, i;
+
+ KASSERT(cmd != NULL, ("cmd cannot be NULL\n"));
+ KASSERT(len != 0, ("Invalid zero-length command\n"));
+ KASSERT(len % 4 == 0, ("Message must be 32bit padded\n"));
+
+ nhicmd = cmd->nhicmd;
+ msglen = (len - 4) / 4;
+ for (i = 0; i < msglen; i++)
+ nhicmd->data[i] = htobe32(nhicmd->data[i]);
+
+ msg = (uint32_t *)nhicmd->data;
+ msg[msglen] = htobe32(tb_calc_crc(nhicmd->data, len-4));
+
+ nhicmd->pdf = PDF_READ;
+ nhicmd->req_len = len;
+
+ nhicmd->timeout = NHI_CMD_TIMEOUT;
+ nhicmd->retries = 0;
+ nhicmd->resp_buffer = (uint32_t *)cmd->resp_buffer;
+ nhicmd->resp_len = (cmd->dwlen + 3) * 4;
+ nhicmd->context = cmd;
+
+ cmd->retries = CFG_DEFAULT_RETRIES;
+ cmd->timeout = CFG_DEFAULT_TIMEOUT;
+
+ return;
+}
+
+static int
+router_schedule(struct router_softc *sc, struct router_command *cmd)
+{
+ int error;
+
+ mtx_lock(&sc->mtx);
+ error = router_schedule_locked(sc, cmd);
+ mtx_unlock(&sc->mtx);
+
+ return(error);
+}
+
+static int
+router_schedule_locked(struct router_softc *sc, struct router_command *cmd)
+{
+ struct nhi_cmd_frame *nhicmd;
+ int error;
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_schedule\n");
+
+ if (cmd != NULL)
+ TAILQ_INSERT_TAIL(&sc->cmd_queue, cmd, link);
+
+ while ((sc->inflight_cmd == NULL) &&
+ ((cmd = TAILQ_FIRST(&sc->cmd_queue)) != NULL)) {
+
+ TAILQ_REMOVE(&sc->cmd_queue, cmd, link);
+ nhicmd = cmd->nhicmd;
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "Scheduling command with index %d\n", nhicmd->idx);
+ sc->inflight_cmd = cmd;
+ if ((error = nhi_tx_schedule(sc->ring0, nhicmd)) != 0) {
+ tb_debug(sc, DBG_ROUTER, "nhi ring error "
+ "%d\n", error);
+ sc->inflight_cmd = NULL;
+ if (error == EBUSY) {
+ TAILQ_INSERT_HEAD(&sc->cmd_queue, cmd, link);
+ error = 0;
+ }
+ break;
+ }
+ }
+
+ return (error);
+}
+
+static void
+router_complete_intr(void *context, union nhi_ring_desc *ring,
+ struct nhi_cmd_frame *nhicmd)
+{
+ struct router_softc *sc;
+ struct router_command *cmd;
+
+ KASSERT(context != NULL, ("context cannot be NULL\n"));
+ KASSERT(nhicmd != NULL, ("nhicmd cannot be NULL\n"));
+
+ cmd = (struct router_command *)(nhicmd->context);
+ sc = cmd->sc;
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_complete_intr called\n");
+
+ if (nhicmd->flags & CMD_RESP_COMPLETE) {
+ cmd->callback(sc, cmd, cmd->callback_arg);
+ }
+
+ return;
+}
+
+static void
+router_response_intr(void *context, union nhi_ring_desc *ring, struct nhi_cmd_frame *nhicmd)
+{
+ struct router_softc *sc, *dev;
+ struct tb_cfg_read_resp *read;
+ struct tb_cfg_write_resp *write;
+ struct router_command *cmd;
+ tb_route_t route;
+ u_int error, i, eof, len;
+ uint32_t attrs;
+
+ KASSERT(context != NULL, ("context cannot be NULL\n"));
+
+ sc = (struct router_softc *)context;
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_response_intr called\n");
+
+ eof = ring->rxpost.eof_len >> RX_BUFFER_DESC_EOF_SHIFT;
+
+ if (eof == PDF_WRITE) {
+ write = (struct tb_cfg_write_resp *)nhicmd->data;
+ route.hi = be32toh(write->route.hi);
+ route.lo = be32toh(write->route.lo);
+ } else {
+ read = (struct tb_cfg_read_resp *)nhicmd->data;
+ route.hi = be32toh(read->route.hi);
+ route.lo = be32toh(read->route.lo);
+ attrs = be32toh(read->addr_attrs);
+ len = (attrs & TB_CFG_SIZE_MASK) >> TB_CFG_SIZE_SHIFT;
+ }
+
+ /* XXX Is this a problem? */
+ if ((route.hi & 0x80000000) == 0)
+ tb_debug(sc, DBG_ROUTER, "Invalid route\n");
+ route.hi &= ~0x80000000;
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "Looking up route 0x%08x%08x\n",
+ route.hi, route.lo);
+
+ error = router_lookup_device(sc, route, &dev);
+ if (error != 0 || dev == NULL) {
+ tb_debug(sc, DBG_ROUTER, "Cannot find device, error= %d\n",
+ error);
+ return;
+ }
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "Found device %s route 0x%08x%08x, "
+ "inflight_cmd= %p\n", device_get_nameunit(dev->dev), dev->route.hi,
+ dev->route.lo, dev->inflight_cmd);
+
+ cmd = dev->inflight_cmd;
+ if (cmd == NULL) {
+ tb_debug(dev, DBG_ROUTER, "Null inflight cmd\n");
+ return;
+ }
+
+ if (eof == PDF_READ) {
+ for (i = 0; i < len; i++)
+ cmd->nhicmd->resp_buffer[i] = be32toh(read->data[i]);
+ }
+
+ cmd->nhicmd->flags |= CMD_RESP_COMPLETE;
+ if (cmd->nhicmd->flags & CMD_REQ_COMPLETE) {
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "TX_COMPLETE set\n");
+ cmd->callback(dev, cmd, cmd->callback_arg);
+ }
+
+ return;
+}
+
+static void
+router_notify_intr(void *context, union nhi_ring_desc *ring, struct nhi_cmd_frame *nhicmd)
+{
+ struct router_softc *sc;
+ struct router_command *cmd;
+ struct tb_cfg_notify event;
+ u_int ev, adap;
+
+ KASSERT(context != NULL, ("context cannot be NULL\n"));
+
+ sc = (struct router_softc *)context;
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "router_notify_intr called\n");
+
+ event.route.hi = be32toh(nhicmd->data[0]);
+ event.route.lo = be32toh(nhicmd->data[1]);
+ event.event_adap = be32toh(nhicmd->data[2]);
+
+ ev = GET_NOTIFY_EVENT(&event);
+ adap = GET_NOTIFY_ADAPTER(&event);
+
+ tb_debug(sc, DBG_ROUTER, "Event route 0x%08x%08x adap %d code %s\n",
+ event.route.hi, event.route.lo, adap,
+ tb_get_string(ev, tb_notify_event));
+
+ switch (ev) {
+ case TB_CFG_ERR_CONN:
+ case TB_CFG_ERR_LINK:
+ case TB_CFG_ERR_ADDR:
+ case TB_CFG_ERR_ADP:
+ case TB_CFG_ERR_ENUM:
+ case TB_CFG_ERR_NUA:
+ case TB_CFG_ERR_LEN:
+ case TB_CFG_ERR_HEC:
+ case TB_CFG_ERR_FC:
+ case TB_CFG_ERR_PLUG:
+ case TB_CFG_ERR_LOCK:
+ case TB_CFG_HP_ACK:
+ case TB_CFG_DP_BW:
+ if (sc->inflight_cmd != NULL) {
+ cmd = sc->inflight_cmd;
+ cmd->ev = ev;
+ cmd->callback(sc, cmd, cmd->callback_arg);
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+int
+tb_config_next_cap(struct router_softc *sc, struct router_cfg_cap *cap)
+{
+ union tb_cfg_cap *tbcap;
+ uint32_t *buf;
+ uint16_t current;
+ int error;
+
+ KASSERT(cap != NULL, ("cap cannot be NULL\n"));
+ KASSERT(cap->next_cap != 0, ("next_cap cannot be 0\n"));
+
+ buf = malloc(sizeof(*tbcap), M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+
+ current = cap->next_cap;
+ error = tb_config_read(sc, cap->space, cap->adap, current, 1, buf);
+ if (error)
+ return (error);
+
+ tbcap = (union tb_cfg_cap *)buf;
+ cap->cap_id = tbcap->hdr.cap_id;
+ cap->next_cap = tbcap->hdr.next_cap;
+ cap->current_cap = current;
+
+ if ((cap->space != TB_CFG_CS_ROUTER) &&
+ (tbcap->hdr.cap_id != TB_CFG_CAP_VSC)) {
+ free(buf, M_THUNDERBOLT);
+ return (0);
+ }
+
+ tb_config_read(sc, cap->space, cap->adap, current, 2, buf);
+ if (error) {
+ free(buf, M_THUNDERBOLT);
+ return (error);
+ }
+
+ cap->vsc_id = tbcap->vsc.vsc_id;
+ cap->vsc_len = tbcap->vsc.len;
+ if (tbcap->vsc.len == 0) {
+ cap->next_cap = tbcap->vsec.vsec_next_cap;
+ cap->vsec_len = tbcap->vsec.vsec_len;
+ }
+
+ free(buf, M_THUNDERBOLT);
+ return (0);
+}
+
+int
+tb_config_find_cap(struct router_softc *sc, struct router_cfg_cap *cap)
+{
+ u_int cap_id, vsc_id;
+ int error;
+
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "tb_config_find_cap called\n");
+
+ cap_id = cap->cap_id;
+ vsc_id = cap->vsc_id;
+
+ cap->cap_id = cap->vsc_id = 0;
+ while ((cap->cap_id != cap_id) || (cap->vsc_id != vsc_id)) {
+ tb_debug(sc, DBG_ROUTER|DBG_EXTRA,
+ "Looking for cap %d at offset %d\n", cap->cap_id,
+ cap->next_cap);
+ if ((cap->next_cap == 0) ||
+ (cap->next_cap > TB_CFG_CAP_OFFSET_MAX))
+ return (EINVAL);
+ error = tb_config_next_cap(sc, cap);
+ if (error)
+ break;
+ }
+
+ return (0);
+}
+
+int
+tb_config_find_router_cap(struct router_softc *sc, u_int cap, u_int vsc, u_int *offset)
+{
+ struct router_cfg_cap rcap;
+ struct tb_cfg_router *cfg;
+ uint32_t *buf;
+ int error;
+
+ buf = malloc(8 * 4, M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+ if (buf == NULL)
+ return (ENOMEM);
+
+ error = tb_config_router_read(sc, 0, 5, buf);
+ if (error != 0) {
+ free(buf, M_THUNDERBOLT);
+ return (error);
+ }
+
+ cfg = (struct tb_cfg_router *)buf;
+ rcap.space = TB_CFG_CS_ROUTER;
+ rcap.adap = 0;
+ rcap.next_cap = GET_ROUTER_CS_NEXT_CAP(cfg);
+ rcap.cap_id = cap;
+ rcap.vsc_id = vsc;
+ error = tb_config_find_cap(sc, &rcap);
+ if (error == 0)
+ *offset = rcap.current_cap;
+
+ free(buf, M_THUNDERBOLT);
+ return (error);
+}
+
+int
+tb_config_find_router_vsc(struct router_softc *sc, u_int cap, u_int *offset)
+{
+
+ return (tb_config_find_router_cap(sc, TB_CFG_CAP_VSC, cap, offset));
+}
+
+int
+tb_config_find_router_vsec(struct router_softc *sc, u_int cap, u_int *offset)
+{
+
+ return (tb_config_find_router_cap(sc, TB_CFG_CAP_VSEC, cap, offset));
+}
+
+int
+tb_config_find_adapter_cap(struct router_softc *sc, u_int adap, u_int cap, u_int *offset)
+{
+ struct router_cfg_cap rcap;
+ struct tb_cfg_adapter *cfg;
+ uint32_t *buf;
+ int error;
+
+ buf = malloc(8 * 4, M_THUNDERBOLT, M_NOWAIT|M_ZERO);
+ if (buf == NULL)
+ return (ENOMEM);
+
+ error = tb_config_adapter_read(sc, adap, 0, 8, buf);
+ if (error != 0) {
+ free(buf, M_THUNDERBOLT);
+ return (error);
+ }
+
+ cfg = (struct tb_cfg_adapter *)buf;
+ rcap.space = TB_CFG_CS_ADAPTER;
+ rcap.adap = adap;
+ rcap.next_cap = GET_ADP_CS_NEXT_CAP(cfg);
+ rcap.cap_id = cap;
+ rcap.vsc_id = 0;
+ error = tb_config_find_cap(sc, &rcap);
+ if (error == 0)
+ *offset = rcap.current_cap;
+
+ free(buf, M_THUNDERBOLT);
+ return (error);
+}
+
+int
+tb_config_get_lc_uuid(struct router_softc *rsc, uint8_t *uuid)
+{
+ u_int error, offset;
+ uint32_t buf[8];
+
+ bzero(buf, sizeof(buf));
+
+ error = tb_config_find_router_vsec(rsc, TB_CFG_VSEC_LC, &offset);
+ if (error != 0) {
+ tb_debug(rsc, DBG_ROUTER, "Error finding LC registers: %d\n",
+ error);
+ return (error);
+ }
+
+ error = tb_config_router_read(rsc, offset + TB_LC_UUID, 4, buf);
+ if (error != 0) {
+ tb_debug(rsc, DBG_ROUTER, "Error fetching UUID: %d\n", error);
+ return (error);
+ }
+
+ bcopy(buf, uuid, 16);
+ return (0);
+}
diff --git a/sys/dev/thunderbolt/router_var.h b/sys/dev/thunderbolt/router_var.h
new file mode 100644
index 000000000000..8366ede852e7
--- /dev/null
+++ b/sys/dev/thunderbolt/router_var.h
@@ -0,0 +1,242 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _ROUTER_VAR_H
+#define _ROUTER_VAR_H
+
+struct router_softc;
+struct router_command;
+struct router_topo;
+
+typedef void (*router_callback_t)(struct router_softc *,
+ struct router_command *, void *);
+
+struct router_command {
+ TAILQ_ENTRY(router_command) link;
+ struct router_softc *sc;
+ struct nhi_cmd_frame *nhicmd;
+ u_int flags;
+#define RCMD_POLLED (1 << 0)
+#define RCMD_POLL_COMPLETE (1 << 1)
+ int resp_len;
+ router_callback_t callback;
+ void *callback_arg;
+ u_int dwlen;
+ u_int timeout;
+ int retries;
+ u_int ev;
+ uint8_t resp_buffer[NHI_RING0_FRAME_SIZE];
+};
+
+struct router_softc {
+ TAILQ_ENTRY(router_softc) link;
+ u_int debug;
+ tb_route_t route;
+ device_t dev;
+ struct nhi_softc *nsc;
+
+ struct mtx mtx;
+ struct nhi_ring_pair *ring0;
+ TAILQ_HEAD(,router_command) cmd_queue;
+
+ struct router_command *inflight_cmd;
+
+ uint8_t depth;
+ uint8_t max_adap;
+
+ struct router_softc **adapters;
+
+ uint32_t uuid[4];
+};
+
+struct router_cfg_cap {
+ uint16_t current_cap;
+ uint16_t next_cap;
+ uint32_t space;
+ uint8_t adap;
+ uint8_t cap_id;
+ uint8_t vsc_id;
+ uint8_t vsc_len;
+ uint16_t vsec_len;
+};
+
+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_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,
+ uint32_t *);
+int tb_config_read_async(struct router_softc *, u_int, u_int, u_int, u_int,
+ uint32_t *, void *);
+int tb_config_write(struct router_softc *, u_int, u_int, u_int, u_int,
+ uint32_t *);
+int tb_config_next_cap(struct router_softc *, struct router_cfg_cap *);
+int tb_config_find_cap(struct router_softc *, struct router_cfg_cap *);
+int tb_config_find_router_cap(struct router_softc *, u_int, u_int, u_int *);
+int tb_config_find_router_vsc(struct router_softc *, u_int, u_int *);
+int tb_config_find_router_vsec(struct router_softc *, u_int, u_int *);
+int tb_config_find_adapter_cap(struct router_softc *, u_int, u_int, u_int *);
+int tb_config_get_lc_uuid(struct router_softc *, uint8_t *);
+
+#define TB_CONFIG_ADDR(seq, space, adapter, dwlen, offset) \
+ ((seq << TB_CFG_SEQ_SHIFT) | space | \
+ (adapter << TB_CFG_ADAPTER_SHIFT) | (dwlen << TB_CFG_SIZE_SHIFT) | \
+ (offset & TB_CFG_ADDR_MASK))
+
+#define TB_ROUTE(router) \
+ ((uint64_t)(router)->route.hi << 32) | (router)->route.lo
+
+static __inline void *
+router_get_frame_data(struct router_command *cmd)
+{
+ return ((void *)cmd->nhicmd->data);
+}
+
+/*
+ * Read the Router config space for the router referred to in the softc.
+ * addr - The dword offset in the config space
+ * dwlen - The number of dwords
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_router_read(struct router_softc *sc, u_int addr, u_int dwlen,
+ uint32_t *buf)
+{
+ return (tb_config_read(sc, TB_CFG_CS_ROUTER, 0, addr, dwlen, buf));
+}
+
+static __inline int
+tb_config_router_read_polled(struct router_softc *sc, u_int addr, u_int dwlen,
+ uint32_t *buf)
+{
+ return (tb_config_read_polled(sc, TB_CFG_CS_ROUTER, 0, addr, dwlen, buf));
+}
+
+/*
+ * Write the Router config space for the router referred to in the softc.
+ * addr - The dword offset in the config space
+ * dwlen - The number of dwords
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_router_write(struct router_softc *sc, u_int addr, u_int dwlen,
+ uint32_t *buf)
+{
+ return (tb_config_write(sc, TB_CFG_CS_ROUTER, 0, addr, dwlen, buf));
+}
+
+/*
+ * Read the Adapter config space for the router referred to in the softc.
+ * adap - Adapter number
+ * addr - The dword offset in the config space
+ * dwlen - The number of dwords
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_adapter_read(struct router_softc *sc, u_int adap, u_int addr,
+ u_int dwlen, uint32_t *buf)
+{
+ return (tb_config_read(sc, TB_CFG_CS_ADAPTER, adap, addr, dwlen, buf));
+}
+
+/*
+ * Read the Adapter config space for the router referred to in the softc.
+ * adap - Adapter number
+ * addr - The dword offset in the config space
+ * dwlen - The number of dwords
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_adapter_write(struct router_softc *sc, u_int adap, u_int addr,
+ u_int dwlen, uint32_t *buf)
+{
+ return (tb_config_write(sc, TB_CFG_CS_ADAPTER, adap, addr, dwlen, buf));
+}
+
+/*
+ * Read the Path config space for the router referred to in the softc.
+ * adap - Adapter number
+ * hopid - HopID of the path
+ * len - The number of adjacent paths
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_path_read(struct router_softc *sc, u_int adap, u_int hopid,
+ u_int num, uint32_t *buf)
+{
+ return (tb_config_read(sc, TB_CFG_CS_PATH, adap, hopid * 2,
+ num * 2, buf));
+}
+
+/*
+ * Write the Path config space for the router referred to in the softc.
+ * adap - Adapter number
+ * hopid - HopID of the path
+ * len - The number of adjacent paths
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_path_write(struct router_softc *sc, u_int adap, u_int hopid,
+ u_int num, uint32_t *buf)
+{
+ return (tb_config_write(sc, TB_CFG_CS_PATH, adap, hopid * 2,
+ num * 2, buf));
+}
+
+/*
+ * Read the Counters config space for the router referred to in the softc.
+ * Counters come in sets of 3 dwords.
+ * adap - Adapter number
+ * set - The counter set index
+ * num - The number of adjacent counter sets to read
+ * buf - must be large enough to hold the number of dwords requested.
+ */
+static __inline int
+tb_config_counters_read(struct router_softc *sc, u_int adap, u_int set,
+ u_int num, uint32_t *buf)
+{
+ return (tb_config_read(sc, TB_CFG_CS_COUNTERS, adap, set * 3,
+ num * 3, buf));
+}
+
+static __inline void
+tb_config_set_root(struct router_softc *sc)
+{
+ sc->nsc->root_rsc = sc;
+}
+
+static __inline void *
+tb_config_get_root(struct router_softc *sc)
+{
+ return (sc->nsc->root_rsc);
+}
+
+#endif /* _ROUTER_VAR_H */
diff --git a/sys/dev/thunderbolt/tb_acpi_pcib.c b/sys/dev/thunderbolt/tb_acpi_pcib.c
new file mode 100644
index 000000000000..947df3688535
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_acpi_pcib.c
@@ -0,0 +1,181 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_acpi.h"
+#include "opt_thunderbolt.h"
+
+/* ACPI identified PCIe bridge for Thunderbolt */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/param.h>
+#include <sys/endian.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <machine/stdarg.h>
+#include <sys/rman.h>
+
+#include <machine/pci_cfgreg.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcib_private.h>
+#include <dev/pci/pci_private.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+#include <dev/acpica/acpi_pcibvar.h>
+#include <machine/md_var.h>
+
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_pcib.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/tb_debug.h>
+
+static int tb_acpi_pcib_probe(device_t);
+static int tb_acpi_pcib_attach(device_t);
+static int tb_acpi_pcib_detach(device_t);
+
+/* ACPI attachment for Thudnerbolt Bridges */
+
+static int
+tb_acpi_pcib_probe(device_t dev)
+{
+ char desc[TB_DESC_MAX], desc1[TB_DESC_MAX];
+ int val;
+
+ if (pci_get_class(dev) != PCIC_BRIDGE ||
+ pci_get_subclass(dev) != PCIS_BRIDGE_PCI ||
+ acpi_disabled("pci"))
+ return (ENXIO);
+ if (acpi_get_handle(dev) == NULL)
+ return (ENXIO);
+ if (pci_cfgregopen() == 0)
+ return (ENXIO);
+
+ /*
+ * Success? Specify a higher probe priority than the conventional
+ * Thunderbolt PCIb driver
+ */
+ if ((val = tb_pcib_probe_common(dev, desc)) < 0) {
+ val++;
+ snprintf(desc1, TB_DESC_MAX, "ACPI %s", desc);
+ device_set_desc_copy(dev, desc1);
+ }
+
+ return (val);
+}
+
+static int
+tb_acpi_pcib_attach(device_t dev)
+{
+ struct tb_pcib_softc *sc;
+ int error;
+
+ error = tb_pcib_attach_common(dev);
+ if (error)
+ return (error);
+
+ sc = device_get_softc(dev);
+ sc->ap_handle = acpi_get_handle(dev);
+ KASSERT(sc->ap_handle != NULL, ("ACPI handle cannot be NULL\n"));
+
+ /* Execute OSUP in case the BIOS didn't */
+ if (TB_IS_ROOT(sc)) {
+ ACPI_OBJECT_LIST list;
+ ACPI_OBJECT arg;
+ ACPI_BUFFER buf;
+ ACPI_STATUS s;
+
+ tb_debug(sc, DBG_BRIDGE, "Executing OSUP\n");
+
+ list.Pointer = &arg;
+ list.Count = 1;
+ arg.Integer.Value = 0;
+ arg.Type = ACPI_TYPE_INTEGER;
+ buf.Length = ACPI_ALLOCATE_BUFFER;
+ buf.Pointer = NULL;
+
+ s = AcpiEvaluateObject(sc->ap_handle, "\\_GPE.OSUP", &list,
+ &buf);
+ tb_debug(sc, DBG_BRIDGE|DBG_FULL,
+ "ACPI returned %d, buf= %p\n", s, buf.Pointer);
+ if (buf.Pointer != NULL)
+ tb_debug(sc, DBG_BRIDGE|DBG_FULL, "buffer= 0x%x\n",
+ *(uint32_t *)buf.Pointer);
+
+ AcpiOsFree(buf.Pointer);
+ }
+
+ pcib_attach_common(dev);
+ acpi_pcib_fetch_prt(dev, &sc->ap_prt);
+
+ return (pcib_attach_child(dev));
+}
+
+static int
+tb_acpi_pcib_detach(device_t dev)
+{
+ struct tb_pcib_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ tb_debug(sc, DBG_BRIDGE|DBG_ROUTER|DBG_EXTRA, "tb_acpi_pcib_detach\n");
+
+ error = pcib_detach(dev);
+ if (error == 0)
+ AcpiOsFree(sc->ap_prt.Pointer);
+ return (error);
+}
+
+static device_method_t tb_acpi_pcib_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, tb_acpi_pcib_probe),
+ DEVMETHOD(device_attach, tb_acpi_pcib_attach),
+ DEVMETHOD(device_detach, tb_acpi_pcib_detach),
+
+ /* Thunderbolt interface is inherited */
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_2(tbolt, tb_acpi_pcib_driver, tb_acpi_pcib_methods,
+ sizeof(struct tb_pcib_softc), pcib_driver, tb_pcib_driver);
+DRIVER_MODULE_ORDERED(tb_acpi_pcib, pci, tb_acpi_pcib_driver,
+ NULL, NULL, SI_ORDER_MIDDLE);
+MODULE_DEPEND(tb_acpi_pcib, acpi, 1, 1, 1);
diff --git a/sys/dev/thunderbolt/tb_debug.c b/sys/dev/thunderbolt/tb_debug.c
new file mode 100644
index 000000000000..f455ee72e9f6
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_debug.c
@@ -0,0 +1,334 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_thunderbolt.h"
+
+/* PCIe bridge for Thunderbolt */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/sbuf.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
+#include <sys/gsb_crc32.h>
+#include <sys/endian.h>
+
+#include <machine/bus.h>
+#include <machine/stdarg.h>
+
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_var.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/tb_debug.h>
+
+tb_string_t nhi_outmailcmd_opmode[] = {
+ { 0x000, "Safe Mode" },
+ { 0x100, "Authentication Mode" },
+ { 0x200, "Endpoint Mode" },
+ { 0x300, "Connection Manager Fully Functional" },
+ { 0, NULL }
+};
+
+tb_string_t nhi_frame_pdf[] = {
+ { 0x01, "PDF_READ" },
+ { 0x02, "PDF_WRITE" },
+ { 0x03, "PDF_NOTIFY" },
+ { 0x04, "PDF_NOTIFY_ACK" },
+ { 0x05, "PDF_HOTPLUG" },
+ { 0x06, "PDF_XDOMAIN_REQ" },
+ { 0x07, "PDF_XDOMAIN_RESP" },
+ { 0x0a, "PDF_CM_EVENT" },
+ { 0x0b, "PDF_CM_REQ" },
+ { 0x0c, "PDF_CM_RESP" },
+ { 0, NULL }
+};
+
+tb_string_t tb_security_level[] = {
+ { TBSEC_NONE, "None" },
+ { TBSEC_USER, "User" },
+ { TBSEC_SECURE, "Secure Authorization" },
+ { TBSEC_DP, "Display Port" },
+ { TBSEC_UNKNOWN,"Unknown" },
+ { 0, NULL }
+};
+
+tb_string_t tb_mbox_connmode[] = {
+ { INMAILCMD_SETMODE_CERT_TB_1ST_DEPTH, "Certified/1st" },
+ { INMAILCMD_SETMODE_ANY_TB_1ST_DEPTH, "Any/1st" },
+ { INMAILCMD_SETMODE_CERT_TB_ANY_DEPTH, "Certified/Any" },
+ { INMAILCMD_SETMODE_ANY_TB_ANY_DEPTH, "Any/Any" },
+ { 0, NULL }
+};
+
+tb_string_t tb_device_power[] = {
+ { 0x0, "Self-powered" },
+ { 0x1, "Normal power" },
+ { 0x2, "High power" },
+ { 0x3, "Unknown power draw" },
+ { 0, NULL }
+};
+
+tb_string_t tb_notify_code[] = {
+ { 0x03, "DEVCONN" },
+ { 0x04, "DISCONN" },
+ { 0x05, "DPCONN" },
+ { 0x06, "DOMCONN" },
+ { 0x07, "DOMDISCONN" },
+ { 0x08, "DPCHANGE" },
+ { 0x09, "I2C" },
+ { 0x0a, "RTD3" },
+ { 0, NULL }
+};
+
+tb_string_t tb_adapter_type[] = {
+ { ADP_CS2_UNSUPPORTED, "Unsupported Adapter" },
+ { ADP_CS2_LANE, "Lane Adapter" },
+ { ADP_CS2_HOSTIF, "Host Interface Adapter" },
+ { ADP_CS2_PCIE_DFP, "Downstream PCIe Adapter" },
+ { ADP_CS2_PCIE_UFP, "Upstream PCIe Adapter" },
+ { ADP_CS2_DP_OUT, "DP OUT Adapter" },
+ { ADP_CS2_DP_IN, "DP IN Adapter" },
+ { ADP_CS2_USB3_DFP, "Downstream USB3 Adapter" },
+ { ADP_CS2_USB3_UFP, "Upstream USB3 Adapter" },
+ { 0, NULL }
+};
+
+tb_string_t tb_adapter_state[] = {
+ { CAP_LANE_STATE_DISABLE, "Disabled" },
+ { CAP_LANE_STATE_TRAINING, "Training" },
+ { CAP_LANE_STATE_CL0, "CL0" },
+ { CAP_LANE_STATE_TXCL0, "TX CL0s" },
+ { CAP_LANE_STATE_RXCL0, "RX CL0s" },
+ { CAP_LANE_STATE_CL1, "CL1" },
+ { CAP_LANE_STATE_CL2, "CL2" },
+ { CAP_LANE_STATE_CLD, "CLd" },
+ { 0, NULL }
+};
+
+tb_string_t tb_notify_event[] = {
+ { TB_CFG_ERR_CONN, "Connection error" },
+ { TB_CFG_ERR_LINK, "Link error" },
+ { TB_CFG_ERR_ADDR, "Addressing error" },
+ { TB_CFG_ERR_ADP, "Invalid adapter" },
+ { TB_CFG_ERR_ENUM, "Enumeration error" },
+ { TB_CFG_ERR_NUA, "Adapter not enumerated" },
+ { TB_CFG_ERR_LEN, "Invalid request length" },
+ { TB_CFG_ERR_HEC, "Invalid packet header" },
+ { TB_CFG_ERR_FC, "Flow control error" },
+ { TB_CFG_ERR_PLUG, "Hot plug error" },
+ { TB_CFG_ERR_LOCK, "Adapter locked" },
+ { TB_CFG_HP_ACK, "Hotplug acknowledgement" },
+ { TB_CFG_DP_BW, "Display port bandwidth change" },
+ { 0, NULL }
+};
+
+const char *
+tb_get_string(uintmax_t key, tb_string_t *table)
+{
+
+ if (table == NULL)
+ return ("<null>");
+
+ while (table->value != NULL) {
+ if (table->key == key)
+ return (table->value);
+ table++;
+ }
+
+ return ("<unknown>");
+}
+
+static struct tb_debug_string {
+ char *name;
+ int flag;
+} tb_debug_strings[] = {
+ {"info", DBG_INFO},
+ {"init", DBG_INIT},
+ {"info", DBG_INFO},
+ {"rxq", DBG_RXQ},
+ {"txq", DBG_TXQ},
+ {"intr", DBG_INTR},
+ {"tb", DBG_TB},
+ {"mbox", DBG_MBOX},
+ {"bridge", DBG_BRIDGE},
+ {"cfg", DBG_CFG},
+ {"router", DBG_ROUTER},
+ {"port", DBG_PORT},
+ {"hcm", DBG_HCM},
+ {"extra", DBG_EXTRA},
+ {"noisy", DBG_NOISY},
+ {"full", DBG_FULL}
+};
+
+enum tb_debug_level_combiner {
+ COMB_NONE,
+ COMB_ADD,
+ COMB_SUB
+};
+
+int
+tb_debug_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ struct sbuf *sbuf;
+#if defined (THUNDERBOLT_DEBUG) && (THUNDERBOLT_DEBUG > 0)
+ struct tb_debug_string *string;
+ char *buffer;
+ size_t sz;
+ u_int *debug;
+ int i, len;
+#endif
+ int error;
+
+ error = sysctl_wire_old_buffer(req, 0);
+ if (error != 0)
+ return (error);
+
+ sbuf = sbuf_new_for_sysctl(NULL, NULL, 128, req);
+
+#if defined (THUNDERBOLT_DEBUG) && (THUNDERBOLT_DEBUG > 0)
+ debug = (u_int *)arg1;
+
+ sbuf_printf(sbuf, "%#x", *debug);
+
+ sz = sizeof(tb_debug_strings) / sizeof(tb_debug_strings[0]);
+ for (i = 0; i < sz; i++) {
+ string = &tb_debug_strings[i];
+ if (*debug & string->flag)
+ sbuf_printf(sbuf, ",%s", string->name);
+ }
+
+ error = sbuf_finish(sbuf);
+ sbuf_delete(sbuf);
+
+ if (error || req->newptr == NULL)
+ return (error);
+
+ len = req->newlen - req->newidx;
+ if (len == 0)
+ return (0);
+
+ buffer = malloc(len, M_THUNDERBOLT, M_ZERO|M_WAITOK);
+ error = SYSCTL_IN(req, buffer, len);
+
+ tb_parse_debug(debug, buffer);
+
+ free(buffer, M_THUNDERBOLT);
+#else
+ sbuf_printf(sbuf, "debugging unavailable");
+ error = sbuf_finish(sbuf);
+ sbuf_delete(sbuf);
+#endif
+
+ return (error);
+}
+
+void
+tb_parse_debug(u_int *debug, char *list)
+{
+ struct tb_debug_string *string;
+ enum tb_debug_level_combiner op;
+ char *token, *endtoken;
+ size_t sz;
+ int flags, i;
+
+ if (list == NULL || *list == '\0')
+ return;
+
+ if (*list == '+') {
+ op = COMB_ADD;
+ list++;
+ } else if (*list == '-') {
+ op = COMB_SUB;
+ list++;
+ } else
+ op = COMB_NONE;
+ if (*list == '\0')
+ return;
+
+ flags = 0;
+ sz = sizeof(tb_debug_strings) / sizeof(tb_debug_strings[0]);
+ while ((token = strsep(&list, ":,")) != NULL) {
+
+ /* Handle integer flags */
+ flags |= strtol(token, &endtoken, 0);
+ if (token != endtoken)
+ continue;
+
+ /* Handle text flags */
+ for (i = 0; i < sz; i++) {
+ string = &tb_debug_strings[i];
+ if (strcasecmp(token, string->name) == 0) {
+ flags |= string->flag;
+ break;
+ }
+ }
+ }
+
+ switch (op) {
+ case COMB_NONE:
+ *debug = flags;
+ break;
+ case COMB_ADD:
+ *debug |= flags;
+ break;
+ case COMB_SUB:
+ *debug &= (~flags);
+ break;
+ }
+ return;
+}
+
+void
+tbdbg_dprintf(device_t dev, u_int debug, u_int val, const char *fmt, ...)
+{
+#if defined(THUNDERBOLT_DEBUG) && (THUNDERBOLT_DEBUG > 0)
+ va_list ap;
+ u_int lvl, dbg;
+
+ lvl = debug & 0xc0000000;
+ dbg = debug & 0x3fffffff;
+ va_start(ap, fmt);
+ if ((lvl >= (val & 0xc0000000)) &&
+ ((dbg & (val & 0x3fffffff)) != 0)) {
+ device_printf(dev, "");
+ vprintf(fmt, ap);
+ }
+ va_end(ap);
+#endif
+}
diff --git a/sys/dev/thunderbolt/tb_debug.h b/sys/dev/thunderbolt/tb_debug.h
new file mode 100644
index 000000000000..4f5584420882
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_debug.h
@@ -0,0 +1,93 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt 3 driver debug strings
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TB_DEBUG_H
+#define _TB_DEBUG_H
+
+typedef struct {
+ uintmax_t key;
+ const char * value;
+} tb_string_t;
+
+const char * tb_get_string(uintmax_t, tb_string_t *);
+int tb_debug_sysctl(SYSCTL_HANDLER_ARGS);
+void tb_parse_debug(u_int *, char *);
+
+extern tb_string_t nhi_outmailcmd_opmode[];
+extern tb_string_t nhi_frame_pdf[];
+extern tb_string_t tb_security_level[];
+extern tb_string_t tb_rdy_connmode[];
+extern tb_string_t tb_mbox_connmode[];
+extern tb_string_t tb_device_power[];
+extern tb_string_t tb_notify_code[];
+extern tb_string_t tb_adapter_type[];
+extern tb_string_t tb_adapter_state[];
+extern tb_string_t tb_notify_event[];
+
+enum {
+ /* Debug subsystems */
+ DBG_NONE = 0,
+ DBG_INIT = (1 << 0),
+ DBG_INFO = (1 << 1),
+ DBG_RXQ = (1 << 2),
+ DBG_TXQ = (1 << 3),
+ DBG_INTR = (1 << 4),
+ DBG_TB = (1 << 5),
+ DBG_MBOX = (1 << 6),
+ DBG_BRIDGE = (1 << 7),
+ DBG_CFG = (1 << 8),
+ DBG_ROUTER = (1 << 9),
+ DBG_PORT = (1 << 10),
+ DBG_HCM = (1 << 11),
+ /* Debug levels */
+ DBG_EXTRA = (1 << 30),
+ DBG_NOISY = (1 << 31),
+ DBG_FULL = DBG_EXTRA | DBG_NOISY
+};
+
+/*
+ * Macros to wrap printing.
+ * Each softc type needs a `dev` and `debug` field. Do tbdbg_printf as a
+ * function to make format errors more clear during compile.
+ */
+void tbdbg_dprintf(device_t dev, u_int debug, u_int val, const char *fmt, ...) __printflike(4, 5);
+
+#if defined(THUNDERBOLT_DEBUG) && (THUNDERBOLT_DEBUG > 0)
+#define tb_debug(sc, level, fmt...) \
+ tbdbg_dprintf((sc)->dev, (sc)->debug, level, ##fmt)
+#else
+#define tb_debug(sc, level, fmt...)
+#endif
+#define tb_printf(sc, fmt...) \
+ device_printf((sc)->dev, ##fmt)
+
+#endif /* _TB_DEBUG_H */
diff --git a/sys/dev/thunderbolt/tb_dev.c b/sys/dev/thunderbolt/tb_dev.c
new file mode 100644
index 000000000000..7ea545dee0c3
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_dev.c
@@ -0,0 +1,331 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_thunderbolt.h"
+
+/* Userspace control device for USB4 / TB3 */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/nv.h>
+#include <sys/taskqueue.h>
+#include <sys/gsb_crc32.h>
+#include <sys/endian.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/bus.h>
+#include <machine/stdarg.h>
+
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_var.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/router_var.h>
+#include <dev/thunderbolt/tb_debug.h>
+#include <dev/thunderbolt/tb_dev.h>
+#include <dev/thunderbolt/tb_ioctl.h>
+
+struct tbdev_if;
+struct tbdev_dm;
+struct tbdev_rt;
+
+struct tbdev_if {
+ TAILQ_ENTRY(tbdev_if) dev_next;
+ char name[SPECNAMELEN];
+};
+
+struct tbdev_dm {
+ TAILQ_ENTRY(tbdev_dm) dev_next;
+ char uid[16];
+};
+
+struct tbdev_rt {
+ TAILQ_ENTRY(tbdev_rt) dev_next;
+ uint64_t route;
+};
+
+static int tbdev_static_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td);
+
+static struct cdevsw tbdev_static_devsw = {
+ .d_version = D_VERSION,
+ .d_ioctl = tbdev_static_ioctl,
+ .d_name = "tbt"
+};
+static struct cdev *tb_dev = NULL;
+
+static TAILQ_HEAD(, tbdev_if) tbdev_head = TAILQ_HEAD_INITIALIZER(tbdev_head);
+static TAILQ_HEAD(, tbdev_dm) tbdomain_head = TAILQ_HEAD_INITIALIZER(tbdomain_head);
+static TAILQ_HEAD(, tbdev_rt) tbrouter_head = TAILQ_HEAD_INITIALIZER(tbrouter_head);
+
+static struct mtx tbdev_mtx;
+MTX_SYSINIT(tbdev_mtx, &tbdev_mtx, "TBT Device Mutex", MTX_DEF);
+
+MALLOC_DEFINE(M_THUNDERBOLT, "thunderbolt", "memory for thunderbolt");
+
+static void
+tbdev_init(void *arg)
+{
+
+ tb_dev = make_dev(&tbdev_static_devsw, 0, UID_ROOT, GID_OPERATOR,
+ 0644, TBT_DEVICE_NAME);
+ if (tb_dev == NULL)
+ printf("Cannot create Thunderbolt system device\n");
+
+ return;
+}
+
+SYSINIT(tbdev_init, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, tbdev_init, NULL);
+
+static void
+tbdev_uninit(void *arg)
+{
+ if (tb_dev != NULL) {
+ destroy_dev(tb_dev);
+ tb_dev = NULL;
+ }
+}
+
+SYSUNINIT(tbdev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, tbdev_uninit, NULL);
+
+int
+tbdev_add_interface(struct nhi_softc *nhi)
+{
+ struct tbdev_if *ifce;
+
+ ifce = malloc(sizeof(struct tbdev_if), M_THUNDERBOLT, M_ZERO|M_NOWAIT);
+ if (ifce == NULL)
+ return (ENOMEM);
+
+ strlcpy(ifce->name, device_get_nameunit(nhi->dev), SPECNAMELEN);
+ mtx_lock(&tbdev_mtx);
+ TAILQ_INSERT_TAIL(&tbdev_head, ifce, dev_next);
+ mtx_unlock(&tbdev_mtx);
+
+ return (0);
+}
+
+int
+tbdev_remove_interface(struct nhi_softc *nhi)
+{
+ struct tbdev_if *ifce = NULL, *if_back;
+ const char *name;
+
+ name = device_get_nameunit(nhi->dev);
+ mtx_lock(&tbdev_mtx);
+ TAILQ_FOREACH_SAFE(ifce, &tbdev_head, dev_next, if_back) {
+ if (strncmp(name, ifce->name, SPECNAMELEN) == 0) {
+ TAILQ_REMOVE(&tbdev_head, ifce, dev_next);
+ break;
+ }
+ }
+ mtx_unlock(&tbdev_mtx);
+
+ if (ifce != NULL)
+ free(ifce, M_THUNDERBOLT);
+
+ return (0);
+}
+
+int
+tbdev_add_domain(void *domain)
+{
+
+ return (0);
+}
+
+int
+tbdev_remove_domain(void *domain)
+{
+
+ return (0);
+}
+
+int
+tbdev_add_router(struct router_softc *rt)
+{
+
+ return (0);
+}
+
+int
+tbdev_remove_router(struct router_softc *rt)
+{
+
+ return (0);
+}
+
+static int
+tbdev_discover(caddr_t addr)
+{
+ nvlist_t *nvl = NULL;
+ struct tbt_ioc *ioc = (struct tbt_ioc *)addr;
+ struct tbdev_if *dev;
+ struct tbdev_dm *dm;
+ struct tbdev_rt *rt;
+ void *nvlpacked = NULL;
+ const char *cmd = NULL;
+ int error = 0;
+
+ if ((ioc->data == NULL) || (ioc->size == 0)) {
+ printf("data or size is 0\n");
+ return (EINVAL);
+ }
+
+ if ((ioc->len == 0) || (ioc->len > TBT_IOCMAXLEN) ||
+ (ioc->len > ioc->size)) {
+ printf("len is wrong\n");
+ return (EINVAL);
+ }
+
+ nvlpacked = malloc(ioc->len, M_THUNDERBOLT, M_NOWAIT);
+ if (nvlpacked == NULL) {
+ printf("cannot allocate nvlpacked\n");
+ return (ENOMEM);
+ }
+
+ error = copyin(ioc->data, nvlpacked, ioc->len);
+ if (error) {
+ free(nvlpacked, M_THUNDERBOLT);
+ printf("error %d from copyin\n", error);
+ return (error);
+ }
+
+ nvl = nvlist_unpack(nvlpacked, ioc->len, NV_FLAG_NO_UNIQUE);
+ if (nvl == NULL) {
+ free(nvlpacked, M_THUNDERBOLT);
+ printf("cannot unpack nvlist\n");
+ return (EINVAL);
+ }
+ free(nvlpacked, M_THUNDERBOLT);
+ nvlpacked = NULL;
+
+ if (nvlist_exists_string(nvl, TBT_DISCOVER_TYPE))
+ cmd = nvlist_get_string(nvl, TBT_DISCOVER_TYPE);
+ if (cmd == NULL) {
+ printf("cannot find type string\n");
+ error = EINVAL;
+ goto out;
+ }
+
+ mtx_lock(&tbdev_mtx);
+ if (strncmp(cmd, TBT_DISCOVER_IFACE, TBT_NAMLEN) == 0) {
+ TAILQ_FOREACH(dev, &tbdev_head, dev_next)
+ nvlist_add_string(nvl, TBT_DISCOVER_IFACE, dev->name);
+ } else if (strncmp(cmd, TBT_DISCOVER_DOMAIN, TBT_NAMLEN) == 0) {
+ TAILQ_FOREACH(dm, &tbdomain_head, dev_next)
+ nvlist_add_string(nvl, TBT_DISCOVER_DOMAIN, dm->uid);
+ } else if (strncmp(cmd, TBT_DISCOVER_ROUTER, TBT_NAMLEN) == 0) {
+ TAILQ_FOREACH(rt, &tbrouter_head, dev_next)
+ nvlist_add_number(nvl, TBT_DISCOVER_ROUTER, rt->route);
+ } else {
+ printf("cannot find supported tpye\n");
+ error = EINVAL;
+ goto out;
+ }
+ mtx_unlock(&tbdev_mtx);
+
+ error = nvlist_error(nvl);
+ if (error != 0) {
+ printf("error %d state in nvlist\n", error);
+ return (error);
+ }
+
+ nvlpacked = nvlist_pack(nvl, &ioc->len);
+ if (nvlpacked == NULL) {
+ printf("cannot allocate new packed buffer\n");
+ return (ENOMEM);
+ }
+ if (ioc->size < ioc->len) {
+ printf("packed buffer is too big to copyout\n");
+ return (ENOSPC);
+ }
+
+ error = copyout(nvlpacked, ioc->data, ioc->len);
+ if (error)
+ printf("error %d on copyout\n", error);
+
+out:
+ if (nvlpacked != NULL)
+ free(nvlpacked, M_NVLIST);
+ if (nvl != NULL)
+ nvlist_destroy(nvl);
+
+ return (error);
+}
+
+static int
+tbdev_request(caddr_t addr)
+{
+ struct tbt_ioc *ioc = (struct tbt_ioc *)addr;
+ nvlist_t *nvl = NULL;
+ void *nvlpacked = NULL;
+ int error = 0;
+
+ if ((ioc->data == NULL) || (ioc->size == 0))
+ return (ENOMEM);
+
+ nvlpacked = nvlist_pack(nvl, &ioc->len);
+ if (nvlpacked == NULL)
+ return (ENOMEM);
+ if (ioc->size < ioc->len)
+ return (ENOSPC);
+
+ error = copyout(nvlpacked, ioc->data, ioc->len);
+ return (error);
+}
+
+static int
+tbdev_static_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
+ struct thread *td)
+{
+ int error = 0;
+
+ switch (cmd) {
+ case TBT_DISCOVER:
+ error = tbdev_discover(addr);
+ break;
+ case TBT_REQUEST:
+ error = tbdev_request(addr);
+ break;
+ default:
+ error = EINVAL;
+ }
+
+ return (error);
+}
diff --git a/sys/dev/thunderbolt/tb_dev.h b/sys/dev/thunderbolt/tb_dev.h
new file mode 100644
index 000000000000..c40a7fbc3d5a
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_dev.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TB_DEV_H
+#define _TB_DEV_H
+
+int tbdev_add_interface(struct nhi_softc *);
+int tbdev_remove_interface(struct nhi_softc *);
+int tbdev_add_domain(void *);
+int tbdev_remove_domain(void *);
+int tbdev_add_router(struct router_softc *);
+int tbdev_remove_router(struct router_softc *);
+
+#endif /* _TB_DEV_H */
diff --git a/sys/dev/thunderbolt/tb_if.m b/sys/dev/thunderbolt/tb_if.m
new file mode 100644
index 000000000000..8b0918811a5d
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_if.m
@@ -0,0 +1,121 @@
+#-
+# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+#
+# Copyright (c) 2022 Scott Long
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+#include <sys/bus.h>
+#include <sys/types.h>
+#include <dev/thunderbolt/tb_reg.h>
+
+INTERFACE tb;
+
+CODE {
+ struct nhi_softc;
+
+ int
+ tb_generic_find_ufp(device_t dev, device_t *ufp)
+ {
+ device_t parent;
+
+ parent = device_get_parent(dev);
+ if (parent == NULL)
+ return (EOPNOTSUPP);
+
+ return (TB_FIND_UFP(parent, ufp));
+ }
+
+ int
+ tb_generic_get_debug(device_t dev, u_int *debug)
+ {
+ device_t parent;
+
+ parent = device_get_parent(dev);
+ if (parent == NULL)
+ return (EOPNOTSUPP);
+
+ return (TB_GET_DEBUG(parent, debug));
+ }
+
+}
+
+HEADER {
+ struct nhi_softc;
+
+ struct tb_lcmbox_cmd {
+ uint32_t cmd;
+ uint32_t cmd_resp;
+ uint32_t data_in;
+ uint32_t data_out;
+ };
+
+ int tb_generic_find_ufp(device_t, device_t *);
+ int tb_generic_get_debug(device_t, u_int *);
+}
+
+#
+# Read the LC Mailbox
+#
+METHOD int lc_mailbox {
+ device_t dev;
+ struct tb_lcmbox_cmd *cmd;
+};
+
+#
+# Read from the PCIE2CIO port
+#
+METHOD int pcie2cio_read {
+ device_t dev;
+ u_int space;
+ u_int port;
+ u_int index;
+ uint32_t *val;
+}
+
+#
+# Write to the PCIE2CIO port
+#
+METHOD int pcie2cio_write {
+ device_t dev;
+ u_int space;
+ u_int port;
+ u_int index;
+ uint32_t val;
+}
+
+#
+# Return the device that's the upstream facing port
+#
+METHOD int find_ufp {
+ device_t dev;
+ device_t *ufp;
+} DEFAULT tb_generic_find_ufp;
+
+METHOD int get_debug {
+ device_t dev;
+ u_int *debug;
+} DEFAULT tb_generic_get_debug;
diff --git a/sys/dev/thunderbolt/tb_ioctl.h b/sys/dev/thunderbolt/tb_ioctl.h
new file mode 100644
index 000000000000..60fafb091cef
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_ioctl.h
@@ -0,0 +1,52 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TB_IOCTL_H
+#define _TB_IOCTL_H
+
+struct tbt_ioc {
+ void *data; /* user-supplied buffer for the nvlist */
+ size_t size; /* size of the user-supplied buffer */
+ size_t len; /* amount of data in the nvlist */
+};
+
+#define TBT_NAMLEN 16
+#define TBT_DEVICE_NAME "tbtctl"
+#define TBT_IOCMAXLEN 4096
+
+#define TBT_DISCOVER _IOWR('h', 1, struct tbt_ioc)
+#define TBT_DISCOVER_TYPE "type"
+#define TBT_DISCOVER_IFACE "iface"
+#define TBT_DISCOVER_DOMAIN "domain"
+#define TBT_DISCOVER_ROUTER "router"
+
+#define TBT_REQUEST _IOWR('h', 2, struct tbt_ioc)
+
+#endif /* _TB_IOCTL_H */
diff --git a/sys/dev/thunderbolt/tb_pcib.c b/sys/dev/thunderbolt/tb_pcib.c
new file mode 100644
index 000000000000..00738984ad1c
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_pcib.c
@@ -0,0 +1,614 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_acpi.h"
+#include "opt_thunderbolt.h"
+
+/* PCIe bridge for Thunderbolt */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/param.h>
+#include <sys/endian.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <machine/stdarg.h>
+#include <sys/rman.h>
+
+#include <machine/pci_cfgreg.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcib_private.h>
+#include <dev/pci/pci_private.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+#include <dev/acpica/acpi_pcibvar.h>
+#include <machine/md_var.h>
+
+#include <dev/thunderbolt/tb_reg.h>
+#include <dev/thunderbolt/tb_pcib.h>
+#include <dev/thunderbolt/nhi_var.h>
+#include <dev/thunderbolt/nhi_reg.h>
+#include <dev/thunderbolt/tbcfg_reg.h>
+#include <dev/thunderbolt/tb_debug.h>
+#include "tb_if.h"
+
+static int tb_pcib_probe(device_t);
+static int tb_pcib_attach(device_t);
+static int tb_pcib_detach(device_t);
+static int tb_pcib_lc_mailbox(device_t, struct tb_lcmbox_cmd *);
+static int tb_pcib_pcie2cio_read(device_t, u_int, u_int, u_int,
+ uint32_t *);
+static int tb_pcib_pcie2cio_write(device_t, u_int, u_int, u_int, uint32_t);
+static int tb_pcib_find_ufp(device_t, device_t *);
+static int tb_pcib_get_debug(device_t, u_int *);
+
+static int tb_pci_probe(device_t);
+static int tb_pci_attach(device_t);
+static int tb_pci_detach(device_t);
+
+struct tb_pcib_ident {
+ uint16_t vendor;
+ uint16_t device;
+ uint16_t subvendor;
+ uint16_t subdevice;
+ uint32_t flags; /* This follows the tb_softc flags */
+ const char *desc;
+} tb_pcib_identifiers[] = {
+ { VENDOR_INTEL, TB_DEV_AR_2C, 0xffff, 0xffff, TB_GEN_TB3|TB_HWIF_AR,
+ "Thunderbolt 3 PCI-PCI Bridge (Alpine Ridge 2C)" },
+ { VENDOR_INTEL, TB_DEV_AR_LP, 0xffff, 0xffff, TB_GEN_TB3|TB_HWIF_AR,
+ "Thunderbolt 3 PCI-PCI Bridge (Alpine Ridge LP)" },
+ { VENDOR_INTEL, TB_DEV_AR_C_4C, 0xffff, 0xffff, TB_GEN_TB3|TB_HWIF_AR,
+ "Thunderbolt 3 PCI-PCI Bridge (Alpine Ridge C 4C)" },
+ { VENDOR_INTEL, TB_DEV_AR_C_2C, 0xffff, 0xffff, TB_GEN_TB3|TB_HWIF_AR,
+ "Thunderbolt 3 PCI-PCI Bridge C (Alpine Ridge C 2C)" },
+ { VENDOR_INTEL, TB_DEV_ICL_0, 0xffff, 0xffff, TB_GEN_TB3|TB_HWIF_ICL,
+ "Thunderbolt 3 PCI-PCI Bridge (IceLake)" },
+ { VENDOR_INTEL, TB_DEV_ICL_1, 0xffff, 0xffff, TB_GEN_TB3|TB_HWIF_ICL,
+ "Thunderbolt 3 PCI-PCI Bridge (IceLake)" },
+ { 0, 0, 0, 0, 0, NULL }
+};
+
+static struct tb_pcib_ident *
+tb_pcib_find_ident(device_t dev)
+{
+ struct tb_pcib_ident *n;
+ uint16_t v, d, sv, sd;
+
+ v = pci_get_vendor(dev);
+ d = pci_get_device(dev);
+ sv = pci_get_subvendor(dev);
+ sd = pci_get_subdevice(dev);
+
+ for (n = tb_pcib_identifiers; n->vendor != 0; n++) {
+ if ((n->vendor != v) || (n->device != d))
+ continue;
+ if (((n->subvendor != 0xffff) && (n->subvendor != sv)) ||
+ ((n->subdevice != 0xffff) && (n->subdevice != sd)))
+ continue;
+ return (n);
+ }
+
+ return (NULL);
+}
+
+static void
+tb_pcib_get_tunables(struct tb_pcib_softc *sc)
+{
+ char tmpstr[80], oid[80];
+
+ /* Set the default */
+ sc->debug = 0;
+
+ /* Grab global variables */
+ bzero(oid, 80);
+ if (TUNABLE_STR_FETCH("hw.tbolt.debug_level", oid, 80) != 0)
+ tb_parse_debug(&sc->debug, oid);
+
+ /* Grab instance variables */
+ bzero(oid, 80);
+ snprintf(tmpstr, sizeof(tmpstr), "dev.tbolt.%d.debug_level",
+ device_get_unit(sc->dev));
+ if (TUNABLE_STR_FETCH(tmpstr, oid, 80) != 0)
+ tb_parse_debug(&sc->debug, oid);
+
+ return;
+}
+
+static int
+tb_pcib_setup_sysctl(struct tb_pcib_softc *sc)
+{
+ struct sysctl_ctx_list *ctx = NULL;
+ struct sysctl_oid *tree = NULL;
+
+ ctx = device_get_sysctl_ctx(sc->dev);
+ if (ctx != NULL)
+ tree = device_get_sysctl_tree(sc->dev);
+
+ if (tree == NULL) {
+ tb_printf(sc, "Error: cannot create sysctl nodes\n");
+ return (EINVAL);
+ }
+ sc->sysctl_tree = tree;
+ sc->sysctl_ctx = ctx;
+
+ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree),
+ OID_AUTO, "debug_level", CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
+ &sc->debug, 0, tb_debug_sysctl, "A", "Thunderbolt debug level");
+
+ return (0);
+}
+
+/*
+ * This is used for both the PCI and ACPI attachments. It shouldn't return
+ * 0, doing so will force the ACPI attachment to fail.
+ */
+int
+tb_pcib_probe_common(device_t dev, char *desc)
+{
+ device_t ufp;
+ struct tb_pcib_ident *n;
+ char *suffix;
+
+ if ((n = tb_pcib_find_ident(dev)) != NULL) {
+ ufp = NULL;
+ if ((TB_FIND_UFP(dev, &ufp) == 0) && (ufp == dev))
+ suffix = "(Upstream port)";
+ else
+ suffix = "(Downstream port)";
+ snprintf(desc, TB_DESC_MAX, "%s %s", n->desc, suffix);
+ return (BUS_PROBE_VENDOR);
+ }
+ return (ENXIO);
+}
+
+static int
+tb_pcib_probe(device_t dev)
+{
+ char desc[TB_DESC_MAX];
+ int val;
+
+ if ((val = tb_pcib_probe_common(dev, desc)) <= 0)
+ device_set_desc_copy(dev, desc);
+
+ return (val);
+}
+
+int
+tb_pcib_attach_common(device_t dev)
+{
+ device_t ufp;
+ struct tb_pcib_ident *n;
+ struct tb_pcib_softc *sc;
+ uint32_t val;
+ int error;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ sc->vsec = -1;
+
+ n = tb_pcib_find_ident(dev);
+ KASSERT(n != NULL, ("Cannot find TB ident"));
+ sc->flags = n->flags;
+
+ tb_pcib_get_tunables(sc);
+ tb_pcib_setup_sysctl(sc);
+
+ /* XXX Is this necessary for ACPI attachments? */
+ tb_debug(sc, DBG_BRIDGE, "busmaster status was %s\n",
+ (pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_BUSMASTEREN)
+ ? "enabled" : "disabled");
+ pci_enable_busmaster(dev);
+
+ /*
+ * Determine if this is an upstream or downstream facing device, and
+ * whether it's the root of the Thunderbolt topology. It's too bad
+ * that there aren't unique PCI ID's to help with this.
+ */
+ ufp = NULL;
+ if ((TB_FIND_UFP(dev, &ufp) == 0) && (ufp != NULL)) {
+ if (ufp == dev) {
+ sc->flags |= TB_FLAGS_ISUFP;
+ if (TB_FIND_UFP(device_get_parent(dev), NULL) ==
+ EOPNOTSUPP) {
+ sc->flags |= TB_FLAGS_ISROOT;
+ }
+ }
+ }
+
+ /*
+ * Find the PCI Vendor Specific Extended Capability. It's the magic
+ * wand to configuring the Thunderbolt root bridges.
+ */
+ if (TB_IS_AR(sc) || TB_IS_TR(sc)) {
+ error = pci_find_extcap(dev, PCIZ_VENDOR, &sc->vsec);
+ if (error) {
+ tb_printf(sc, "Cannot find VSEC capability: %d\n",
+ error);
+ return (ENXIO);
+ }
+ }
+
+ /*
+ * Take the AR bridge out of low-power mode.
+ * XXX AR only?
+ */
+ if ((1 || TB_IS_AR(sc)) && TB_IS_ROOT(sc)) {
+ struct tb_lcmbox_cmd cmd;
+
+ cmd.cmd = LC_MBOXOUT_CMD_SXEXIT_TBT;
+ cmd.data_in = 0;
+
+ error = TB_LC_MAILBOX(dev, &cmd);
+ tb_debug(sc, DBG_BRIDGE, "SXEXIT returned error= %d resp= 0x%x "
+ "data= 0x%x\n", error, cmd.cmd_resp, cmd.data_out);
+ }
+
+ /* The downstream facing port on AR needs some help */
+ if (TB_IS_AR(sc) && TB_IS_DFP(sc)) {
+ tb_debug(sc, DBG_BRIDGE, "Doing AR L1 fixup\n");
+ val = pci_read_config(dev, sc->vsec + AR_VSCAP_1C, 4);
+ tb_debug(sc, DBG_BRIDGE|DBG_FULL, "VSEC+0x1c= 0x%08x\n", val);
+ val |= (1 << 8);
+ pci_write_config(dev, sc->vsec + AR_VSCAP_1C, val, 4);
+
+ val = pci_read_config(dev, sc->vsec + AR_VSCAP_B0, 4);
+ tb_debug(sc, DBG_BRIDGE|DBG_FULL, "VSEC+0xb0= 0x%08x\n", val);
+ val |= (1 << 12);
+ pci_write_config(dev, sc->vsec + AR_VSCAP_B0, val, 4);
+ }
+
+ return (0);
+}
+
+static int
+tb_pcib_attach(device_t dev)
+{
+ int error;
+
+ error = tb_pcib_attach_common(dev);
+ if (error)
+ return (error);
+ return (pcib_attach(dev));
+}
+
+static int
+tb_pcib_detach(device_t dev)
+{
+ struct tb_pcib_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ tb_debug(sc, DBG_BRIDGE|DBG_ROUTER|DBG_EXTRA, "tb_pcib_detach\n");
+
+ /* Put the AR bridge back to sleep */
+ /* XXX disable this until power control for downstream switches works */
+ if (0 && TB_IS_ROOT(sc)) {
+ struct tb_lcmbox_cmd cmd;
+
+ cmd.cmd = LC_MBOXOUT_CMD_GO2SX;
+ cmd.data_in = 0;
+
+ error = TB_LC_MAILBOX(dev, &cmd);
+ tb_debug(sc, DBG_BRIDGE, "SXEXIT returned error= %d resp= 0x%x "
+ "data= 0x%x\n", error, cmd.cmd_resp, cmd.data_out);
+ }
+
+ return (pcib_detach(dev));
+}
+
+/* Read/write the Link Controller registers in CFG space */
+static int
+tb_pcib_lc_mailbox(device_t dev, struct tb_lcmbox_cmd *cmd)
+{
+ struct tb_pcib_softc *sc;
+ uint32_t regcmd, result;
+ uint16_t m_in, m_out;
+ int vsec, i;
+
+ sc = device_get_softc(dev);
+ vsec = TB_PCIB_VSEC(dev);
+ if (vsec == -1)
+ return (EOPNOTSUPP);
+
+ if (TB_IS_AR(sc)) {
+ m_in = AR_LC_MBOX_IN;
+ m_out = AR_LC_MBOX_OUT;
+ } else if (TB_IS_ICL(sc)) {
+ m_in = ICL_LC_MBOX_IN;
+ m_out = ICL_LC_MBOX_OUT;
+ } else
+ return (EOPNOTSUPP);
+
+ /* Set the valid bit to signal we're sending a command */
+ regcmd = LC_MBOXOUT_VALID | (cmd->cmd & LC_MBOXOUT_CMD_MASK);
+ regcmd |= (cmd->data_in << LC_MBOXOUT_DATA_SHIFT);
+ tb_debug(sc, DBG_BRIDGE|DBG_FULL, "Writing LC cmd 0x%x\n", regcmd);
+ pci_write_config(dev, vsec + m_out, regcmd, 4);
+
+ for (i = 0; i < 10; i++) {
+ pause("nhi", 1 * hz);
+ result = pci_read_config(dev, vsec + m_in, 4);
+ tb_debug(sc, DBG_BRIDGE|DBG_FULL, "LC Mailbox= 0x%08x\n",
+ result);
+ if ((result & LC_MBOXIN_DONE) != 0)
+ break;
+ }
+
+ /* Clear the valid bit to signal we're done sending the command */
+ pci_write_config(dev, vsec + m_out, 0, 4);
+
+ cmd->cmd_resp = result & LC_MBOXIN_CMD_MASK;
+ cmd->data_out = result >> LC_MBOXIN_CMD_SHIFT;
+
+ if ((result & LC_MBOXIN_DONE) == 0)
+ return (ETIMEDOUT);
+
+ return (0);
+}
+
+static int
+tb_pcib_pcie2cio_wait(device_t dev, u_int timeout)
+{
+#if 0
+ uint32_t val;
+ int vsec;
+
+ vsec = TB_PCIB_VSEC(dev);
+ do {
+ pci_read_config(dev, vsec + PCIE2CIO_CMD, &val);
+ if ((val & PCIE2CIO_CMD_START) == 0) {
+ if (val & PCIE2CIO_CMD_TIMEOUT)
+ break;
+ return 0;
+ }
+
+ msleep(50);
+ } while (time_before(jiffies, end));
+
+#endif
+ return ETIMEDOUT;
+}
+
+static int
+tb_pcib_pcie2cio_read(device_t dev, u_int space, u_int port, u_int offset,
+ uint32_t *val)
+{
+#if 0
+ uint32_t cmd;
+ int ret, vsec;
+
+ vsec = TB_PCIB_VSEC(dev);
+ if (vsec == -1)
+ return (EOPNOTSUPP);
+
+ cmd = index;
+ cmd |= (port << PCIE2CIO_CMD_PORT_SHIFT) & PCIE2CIO_CMD_PORT_MASK;
+ cmd |= (space << PCIE2CIO_CMD_CS_SHIFT) & PCIE2CIO_CMD_CS_MASK;
+ cmd |= PCIE2CIO_CMD_START;
+ pci_write_config(dev, vsec + PCIE2CIO_CMD, cmd, 4);
+
+ if ((ret = pci2cio_wait_completion(dev, 5000)) != 0)
+ return (ret);
+
+ *val = pci_read_config(dev, vsec + PCIE2CIO_RDDATA, 4);
+#endif
+ return (0);
+}
+
+static int
+tb_pcib_pcie2cio_write(device_t dev, u_int space, u_int port, u_int offset,
+ uint32_t val)
+{
+#if 0
+ uint32_t cmd;
+ int ret, vsec;
+
+ vsec = TB_PCIB_VSEC(dev);
+ if (vsec == -1)
+ return (EOPNOTSUPP);
+
+ pci_write_config(dev, vsec + PCIE2CIO_WRDATA, val, 4);
+
+ cmd = index;
+ cmd |= (port << PCIE2CIO_CMD_PORT_SHIFT) & PCIE2CIO_CMD_PORT_MASK;
+ cmd |= (space << PCIE2CIO_CMD_CS_SHIFT) & PCIE2CIO_CMD_CS_MASK;
+ cmd |= PCIE2CIO_CMD_WRITE | PCIE2CIO_CMD_START;
+ pci_write_config(dev, vsec + PCIE2CIO_CMD, cmd);
+
+#endif
+ return (tb_pcib_pcie2cio_wait(dev, 5000));
+}
+
+/*
+ * The Upstream Facing Port (UFP) in a switch is special, it's the function
+ * that responds to some of the special programming mailboxes. It can't be
+ * differentiated by PCI ID, so a heuristic approach to identifying it is
+ * required.
+ */
+static int
+tb_pcib_find_ufp(device_t dev, device_t *ufp)
+{
+ device_t upstream;
+ struct tb_pcib_softc *sc;
+ uint32_t vsec, val;
+ int error;
+
+ upstream = NULL;
+ sc = device_get_softc(dev);
+ if (sc == NULL)
+ return (EOPNOTSUPP);
+
+ if (TB_IS_UFP(sc)) {
+ upstream = dev;
+ error = 0;
+ goto out;
+ }
+
+ /*
+ * This register is supposed to be filled in on the upstream port
+ * and tells how many downstream ports there are. It doesn't seem
+ * to get filled in on AR host controllers, but is on various
+ * peripherals.
+ */
+ error = pci_find_extcap(dev, PCIZ_VENDOR, &vsec);
+ if (error == 0) {
+ val = pci_read_config(dev, vsec + 0x18, 4);
+ if ((val & 0x1f) > 0) {
+ upstream = dev;
+ goto out;
+ }
+ }
+
+ /*
+ * Since we can't trust that the VSEC register is filled in, the only
+ * other option is to see if we're at the top of the topology, which
+ * implies that we're at the upstream port of the host controller.
+ */
+ error = TB_FIND_UFP(device_get_parent(dev), ufp);
+ if (error == EOPNOTSUPP) {
+ upstream = dev;
+ error = 0;
+ goto out;
+ } else
+ return (error);
+
+out:
+ if (ufp != NULL)
+ *ufp = upstream;
+
+ return (error);
+}
+
+static int
+tb_pcib_get_debug(device_t dev, u_int *debug)
+{
+ struct tb_pcib_softc *sc;
+
+ sc = device_get_softc(dev);
+ if ((sc == NULL) || (debug == NULL))
+ return (EOPNOTSUPP);
+
+ *debug = sc->debug;
+ return (0);
+}
+
+static device_method_t tb_pcib_methods[] = {
+ DEVMETHOD(device_probe, tb_pcib_probe),
+ DEVMETHOD(device_attach, tb_pcib_attach),
+ DEVMETHOD(device_detach, tb_pcib_detach),
+
+ DEVMETHOD(tb_lc_mailbox, tb_pcib_lc_mailbox),
+ DEVMETHOD(tb_pcie2cio_read, tb_pcib_pcie2cio_read),
+ DEVMETHOD(tb_pcie2cio_write, tb_pcib_pcie2cio_write),
+
+ DEVMETHOD(tb_find_ufp, tb_pcib_find_ufp),
+ DEVMETHOD(tb_get_debug, tb_pcib_get_debug),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(tbolt, tb_pcib_driver, tb_pcib_methods,
+ sizeof(struct tb_pcib_softc), pcib_driver);
+DRIVER_MODULE_ORDERED(tb_pcib, pci, tb_pcib_driver,
+ NULL, NULL, SI_ORDER_MIDDLE);
+MODULE_DEPEND(tb_pcib, pci, 1, 1, 1);
+MODULE_PNP_INFO("U16:vendor;U16:device;U16:subvendor;U16:subdevice;U32:#;D:#",
+ pci, tb_pcib, tb_pcib_identifiers, nitems(tb_pcib_identifiers) - 1);
+
+static int
+tb_pci_probe(device_t dev)
+{
+ struct tb_pcib_ident *n;
+
+ if ((n = tb_pcib_find_ident(device_get_parent(dev))) != NULL) {
+ switch (n->flags & TB_GEN_MASK) {
+ case TB_GEN_TB1:
+ device_set_desc(dev, "Thunderbolt 1 Link");
+ break;
+ case TB_GEN_TB2:
+ device_set_desc(dev, "Thunderbolt 2 Link");
+ break;
+ case TB_GEN_TB3:
+ device_set_desc(dev, "Thunderbolt 3 Link");
+ break;
+ case TB_GEN_USB4:
+ device_set_desc(dev, "USB4 Link");
+ break;
+ case TB_GEN_UNK:
+ /* Fallthrough */
+ default:
+ device_set_desc(dev, "Thunderbolt Link");
+ }
+ return (BUS_PROBE_VENDOR);
+ }
+ return (ENXIO);
+}
+
+static int
+tb_pci_attach(device_t dev)
+{
+
+ return (pci_attach(dev));
+}
+
+static int
+tb_pci_detach(device_t dev)
+{
+
+ return (pci_detach(dev));
+}
+
+static device_method_t tb_pci_methods[] = {
+ DEVMETHOD(device_probe, tb_pci_probe),
+ DEVMETHOD(device_attach, tb_pci_attach),
+ DEVMETHOD(device_detach, tb_pci_detach),
+
+ DEVMETHOD(tb_find_ufp, tb_generic_find_ufp),
+ DEVMETHOD(tb_get_debug, tb_generic_get_debug),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(pci, tb_pci_driver, tb_pci_methods, sizeof(struct pci_softc),
+ pci_driver);
+DRIVER_MODULE(tb_pci, pcib, tb_pci_driver, NULL, NULL);
+MODULE_DEPEND(tb_pci, pci, 1, 1, 1);
+MODULE_VERSION(tb_pci, 1);
diff --git a/sys/dev/thunderbolt/tb_pcib.h b/sys/dev/thunderbolt/tb_pcib.h
new file mode 100644
index 000000000000..6928e866a083
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_pcib.h
@@ -0,0 +1,93 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt PCIe bridge/switch definitions
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TB_PCIB_H
+#define _TB_PCIB_H
+
+DECLARE_CLASS(tb_pcib_driver);
+
+/*
+ * The order of the fields is very important. Class inherentence replies on
+ * implicitly knowing the location of the first 3 fields.
+ */
+struct tb_pcib_softc {
+ struct pcib_softc pcibsc;
+ ACPI_HANDLE ap_handle;
+ ACPI_BUFFER ap_prt;
+ device_t dev;
+ u_int debug;
+ int vsec;
+ int flags;
+ struct sysctl_ctx_list *sysctl_ctx;
+ struct sysctl_oid *sysctl_tree;
+};
+
+/* Flags for tb_softc */
+#define TB_GEN_UNK 0x00
+#define TB_GEN_TB1 0x01
+#define TB_GEN_TB2 0x02
+#define TB_GEN_TB3 0x03
+#define TB_GEN_USB4 0x04
+#define TB_GEN_MASK 0x0f
+#define TB_HWIF_UNK 0x00
+#define TB_HWIF_AR 0x10
+#define TB_HWIF_TR 0x20
+#define TB_HWIF_ICL 0x30
+#define TB_HWIF_USB4 0x40
+#define TB_HWIF_MASK 0xf0
+#define TB_FLAGS_ISROOT 0x100
+#define TB_FLAGS_ISUFP 0x200
+
+#define TB_IS_AR(sc) (((sc)->flags & TB_HWIF_MASK) == TB_HWIF_AR)
+#define TB_IS_TR(sc) (((sc)->flags & TB_HWIF_MASK) == TB_HWIF_TR)
+#define TB_IS_ICL(sc) (((sc)->flags & TB_HWIF_MASK) == TB_HWIF_ICL)
+#define TB_IS_USB4(sc) (((sc)->flags & TB_HWIF_MASK) == TB_HWIF_USB4)
+
+#define TB_IS_ROOT(sc) (((sc)->flags & TB_FLAGS_ISROOT) != 0)
+#define TB_IS_UFP(sc) (((sc)->flags & TB_FLAGS_ISUFP) != 0)
+#define TB_IS_DFP(sc) (((sc)->flags & TB_FLAGS_ISUFP) == 0)
+
+/* PCI IDs for the TB bridges */
+#define TB_DEV_AR_2C 0x1576
+#define TB_DEV_AR_LP 0x15c0
+#define TB_DEV_AR_C_4C 0x15d3
+#define TB_DEV_AR_C_2C 0x15da
+#define TB_DEV_ICL_0 0x8a1d
+#define TB_DEV_ICL_1 0x8a21
+
+#define TB_PCIB_VSEC(dev) ((struct tb_pcib_softc *)(device_get_softc(dev)))->vsec;
+#define TB_DESC_MAX 80
+
+int tb_pcib_probe_common(device_t, char *);
+int tb_pcib_attach_common(device_t dev);
+
+#endif /* _TB_PCIB_H */
diff --git a/sys/dev/thunderbolt/tb_reg.h b/sys/dev/thunderbolt/tb_reg.h
new file mode 100644
index 000000000000..b065e01e6972
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_reg.h
@@ -0,0 +1,52 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt Variables
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TB_REG_H
+#define _TB_REG_H
+
+#define TBSEC_NONE 0x00
+#define TBSEC_USER 0x01
+#define TBSEC_SECURE 0x02
+#define TBSEC_DP 0x03
+#define TBSEC_UNKNOWN 0xff
+
+/*
+ * SW-FW commands and responses. These are sent over Ring0 to communicate
+ * with the fabric and the TBT Connection Manager firmware.
+ */
+
+typedef struct {
+ uint32_t hi;
+ uint32_t lo;
+} __packed tb_route_t;
+
+#endif /* _TB_REG_H */
diff --git a/sys/dev/thunderbolt/tb_var.h b/sys/dev/thunderbolt/tb_var.h
new file mode 100644
index 000000000000..4874c420300e
--- /dev/null
+++ b/sys/dev/thunderbolt/tb_var.h
@@ -0,0 +1,54 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt firmware connection manager functions.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TB_VAR_H
+#define _TB_VAR_H
+
+typedef struct {
+ int8_t link;
+ int8_t depth;
+} tb_addr_t;
+
+MALLOC_DECLARE(M_THUNDERBOLT);
+
+#define TB_VENDOR_LEN 48
+#define TB_MODEL_LEN 48
+#define TB_MAX_LINKS 4
+#define TB_MAX_DEPTH 6
+
+static __inline uint32_t
+tb_calc_crc(void *data, u_int len)
+{
+ return ( ~ (calculate_crc32c(~0L, data, len)));
+}
+
+#endif /* _TB_VAR_H */
diff --git a/sys/dev/thunderbolt/tbcfg_reg.h b/sys/dev/thunderbolt/tbcfg_reg.h
new file mode 100644
index 000000000000..bb68faa543b0
--- /dev/null
+++ b/sys/dev/thunderbolt/tbcfg_reg.h
@@ -0,0 +1,363 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Scott Long
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Thunderbolt3/USB4 config space register definitions
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TBCFG_REG_H
+#define _TBCFG_REG_H
+
+/* Config space read request, 6.4.2.3 */
+struct tb_cfg_read {
+ tb_route_t route;
+ uint32_t addr_attrs;
+#define TB_CFG_ADDR_SHIFT 0
+#define TB_CFG_ADDR_MASK GENMASK(12,0)
+#define TB_CFG_SIZE_SHIFT 13
+#define TB_CFG_SIZE_MASK GENMASK(18,13)
+#define TB_CFG_ADAPTER_SHIFT 19
+#define TB_CFG_ADAPTER_MASK GENMASK(24,19)
+#define TB_CFG_CS_PATH (0x00 << 25)
+#define TB_CFG_CS_ADAPTER (0x01 << 25)
+#define TB_CFG_CS_ROUTER (0x02 << 25)
+#define TB_CFG_CS_COUNTERS (0x03 << 25)
+#define TB_CFG_SEQ_SHIFT 27
+#define TB_CFG_SEQ_MASK (28,27)
+ uint32_t crc;
+};
+
+/* Config space read request, 6.4.2.4 */
+struct tb_cfg_read_resp {
+ tb_route_t route;
+ uint32_t addr_attrs;
+ uint32_t data[0]; /* Up to 60 dwords */
+ /* uint32_t crc is at the end */
+} __packed;
+
+/* Config space write request, 6.4.2.5 */
+struct tb_cfg_write {
+ tb_route_t route;
+ uint32_t addr_attrs;
+ uint32_t data[0]; /* Up to 60 dwords */
+ /* uint32_t crc is at the end */
+} __packed;
+
+/* Config space write response, 6.4.2.6 */
+struct tb_cfg_write_resp {
+ tb_route_t route;
+ uint32_t addr_attrs;
+ uint32_t crc;
+} __packed;
+
+/* Config space event, 6.4.2.7 */
+struct tb_cfg_notify {
+ tb_route_t route;
+ uint32_t event_adap;
+#define TB_CFG_EVENT_MASK GENMASK(7,0)
+#define GET_NOTIFY_EVENT(n) ((n)->event_adap & TB_CFG_EVENT_MASK)
+#define TB_CFG_ERR_CONN 0x00
+#define TB_CFG_ERR_LINK 0x01
+#define TB_CFG_ERR_ADDR 0x02
+#define TB_CFG_ERR_ADP 0x04
+#define TB_CFG_ERR_ENUM 0x08
+#define TB_CFG_ERR_NUA 0x09
+#define TB_CFG_ERR_LEN 0x0b
+#define TB_CFG_ERR_HEC 0x0c
+#define TB_CFG_ERR_FC 0x0d
+#define TB_CFG_ERR_PLUG 0x0e
+#define TB_CFG_ERR_LOCK 0x0f
+#define TB_CFG_HP_ACK 0x07
+#define TB_CFG_DP_BW 0x20
+#define TB_CFG_EVENT_ADAPTER_SHIFT 8
+#define TB_CFG_EVENT_ADAPTER_MASK GENMASK(13,8)
+#define GET_NOTIFY_ADAPTER(n) (((n)->event_adap & \
+ TB_CFG_EVENT_ADAPTER_MASK) >> \
+ TB_CFG_EVENT_ADAPTER_SHIFT)
+#define TB_CFG_PG_NONE 0x00000000
+#define TB_CFG_PG_PLUG 0x80000000
+#define TB_CFG_PG_UNPLUG 0xc0000000
+ uint32_t crc;
+} __packed;
+
+/* Config space event acknowledgement, 6.4.2.8 */
+struct tb_cfg_notify_ack {
+ tb_route_t route;
+ uint32_t crc;
+} __packed;
+
+/* Config space hot plug event, 6.4.2.10 */
+struct tb_cfg_hotplug {
+ tb_route_t route;
+ uint32_t adapter_attrs;
+#define TB_CFG_ADPT_MASK GENMASK(5,0)
+#define TB_CFG_UPG_PLUG (0x0 << 31)
+#define TB_CFG_UPG_UNPLUG (0x1 << 31)
+ uint32_t crc;
+} __packed;
+
+/* Config space inter-domain request, 6.4.2.11 */
+struct tb_cfg_xdomain {
+ tb_route_t route;
+ uint32_t data[0];
+ /* uint32_t crc is at the end */
+} __packed;
+
+/* Config space inter-domain response, 6.4.2.12 */
+struct tb_cfg_xdomain_resp {
+ tb_route_t route;
+ uint32_t data[0];
+ /* uint32_t crc is at the end */
+} __packed;
+
+/* Config space router basic registers 8.2.1.1 */
+struct tb_cfg_router {
+ uint16_t vendor_id; /* ROUTER_CS_0 */
+ uint16_t product_id;
+ uint32_t router_cs_1; /* ROUTER_CS_1 */
+#define ROUTER_CS1_NEXT_CAP_MASK GENMASK(7,0)
+#define GET_ROUTER_CS_NEXT_CAP(r) (r->router_cs_1 & \
+ ROUTER_CS1_NEXT_CAP_MASK)
+#define ROUTER_CS1_UPSTREAM_SHIFT 8
+#define ROUTER_CS1_UPSTREAM_MASK GENMASK(13,8)
+#define GET_ROUTER_CS_UPSTREAM_ADAP(r) ((r->router_cs_1 & \
+ ROUTER_CS1_UPSTREAM_MASK) >> \
+ ROUTER_CS1_UPSTREAM_SHIFT)
+#define ROUTER_CS1_MAX_SHIFT 14
+#define ROUTER_CS1_MAX_MASK GENMASK(19,14)
+#define GET_ROUTER_CS_MAX_ADAP(r) ((r->router_cs_1 & \
+ ROUTER_CS1_MAX_MASK) >> \
+ ROUTER_CS1_MAX_SHIFT)
+#define ROUTER_CS1_MAX_ADAPTERS 64
+#define ROUTER_CS1_DEPTH_SHIFT 20
+#define ROUTER_CS1_DEPTH_MASK GENMASK(22,20)
+#define GET_ROUTER_CS_DEPTH(r) ((r->router_cs_1 & \
+ ROUTER_CS1_DEPTH_MASK) >> \
+ ROUTER_CS1_DEPTH_SHIFT)
+#define ROUTER_CS1_REVISION_SHIFT 24
+#define ROUTER_CS1_REVISION_MASK GENMASK(31,24)
+#define GET_ROUTER_CS_REVISION ((r->router_cs_1 & \
+ ROUTER_CS1_REVISION_MASK) >> \
+ ROUTER_CS1_REVISION_SHIFT)
+ uint32_t topology_lo; /* ROUTER_CS_2 */
+ uint32_t topology_hi; /* ROUTER_CS_3 */
+#define CFG_TOPOLOGY_VALID (1 << 31)
+ uint8_t notification_timeout; /* ROUTER_CS_4 */
+ uint8_t cm_version;
+#define CFG_CM_USB4 0x10
+ uint8_t rsrvd1;
+ uint8_t usb4_version;
+#define CFG_USB4_V1_0 0x10
+ uint32_t flags_cs5; /* ROUTER_CS_5 */
+#define CFG_CS5_SLP (1 << 0)
+#define CFG_CS5_WOP (1 << 1)
+#define CFG_CS5_WOU (1 << 2)
+#define CFG_CS5_DP (1 << 3)
+#define CFG_CS5_C3S (1 << 23)
+#define CFG_CS5_PTO (1 << 24)
+#define CFG_CS5_UTO (1 << 25)
+#define CFG_CS5_HCO (1 << 26)
+#define CFG_CS5_CV (1 << 31)
+ uint32_t flags_cs6; /* ROUTER_CS_6 */
+#define CFG_CS6_SLPR (1 << 0)
+#define CFG_CS6_TNS (1 << 1)
+#define CFG_CS6_WAKE_PCIE (1 << 2)
+#define CFG_CS6_WAKE_USB3 (1 << 3)
+#define CFG_CS6_WAKE_DP (1 << 4)
+#define CFG_CS6_HCI (1 << 18)
+#define CFG_CS6_RR (1 << 24)
+#define CFG_CS6_CR (1 << 25)
+ uint32_t uuid_hi; /* ROUTER_CS_7 */
+ uint32_t uuid_lo; /* ROUTER_CS_8 */
+ uint32_t data[16]; /* ROUTER_CS_9-24 */
+ uint32_t metadata; /* ROUTER_CS_25 */
+ uint32_t opcode_status; /* ROUTER_CS_26 */
+/* TBD: Opcodes and status */
+#define CFG_ONS (1 << 30)
+#define CFG_OV (1 << 31)
+} __packed;
+
+#define TB_CFG_CAP_OFFSET_MAX 0xfff
+
+/* Config space router capability header 8.2.1.3/8.2.1.4 */
+struct tb_cfg_cap_hdr {
+ uint8_t next_cap;
+ uint8_t cap_id;
+} __packed;
+
+/* Config space router TMU registers 8.2.1.2 */
+struct tb_cfg_cap_tmu {
+ struct tb_cfg_cap_hdr hdr;
+#define TB_CFG_CAP_TMU 0x03
+} __packed;
+
+struct tb_cfg_vsc_cap {
+ struct tb_cfg_cap_hdr hdr;
+#define TB_CFG_CAP_VSC 0x05
+ uint8_t vsc_id;
+ uint8_t len;
+} __packed;
+
+struct tb_cfg_vsec_cap {
+ struct tb_cfg_cap_hdr hdr;
+#define TB_CFG_CAP_VSEC 0x05
+ uint8_t vsec_id;
+ uint8_t len;
+ uint16_t vsec_next_cap;
+ uint16_t vsec_len;
+} __packed;
+
+union tb_cfg_cap {
+ struct tb_cfg_cap_hdr hdr;
+ struct tb_cfg_cap_tmu tmu;
+ struct tb_cfg_vsc_cap vsc;
+ struct tb_cfg_vsec_cap vsec;
+} __packed;
+
+#define TB_CFG_VSC_PLUG 0x01 /* Hot Plug and DROM */
+
+#define TB_CFG_VSEC_LC 0x06 /* Link Controller */
+#define TB_LC_DESC 0x02 /* LC Descriptor fields */
+#define TB_LC_DESC_NUM_LC_MASK GENMASK(3, 0)
+#define TB_LC_DESC_SIZE_SHIFT 8
+#define TB_LC_DESC_SIZE_MASK GENMASK(15, 8)
+#define TB_LC_DESC_PORT_SHIFT 16
+#define TB_LC_DESC_PORT_MASK GENMASK(27, 16)
+#define TB_LC_UUID 0x03
+#define TB_LC_DP_SINK 0x10 /* Display Port config */
+#define TB_LC_PORT_ATTR 0x8d /* Port attributes */
+#define TB_LC_PORT_ATTR_BE (1 << 12) /* Bonding enabled */
+#define TB_LC_SX_CTRL 0x96 /* Sleep control */
+#define TB_LC_SX_CTRL_WOC (1 << 1)
+#define TB_LC_SX_CTRL_WOD (1 << 2)
+#define TB_LC_SX_CTRL_WOU4 (1 << 5)
+#define TB_LC_SX_CTRL_WOP (1 << 6)
+#define TB_LC_SX_CTRL_L1C (1 << 16)
+#define TB_LC_SX_CTRL_L1D (1 << 17)
+#define TB_LC_SX_CTRL_L2C (1 << 20)
+#define TB_LC_SX_CTRL_L2D (1 << 21)
+#define TB_LC_SX_CTRL_UFP (1 << 30)
+#define TB_LC_SX_CTRL_SLP (1 << 31)
+#define TB_LC_POWER 0x740
+
+/* Config space adapter basic registers 8.2.2.1 */
+struct tb_cfg_adapter {
+ uint16_t vendor_id; /* ADP CS0 */
+ uint16_t product_id;
+ uint32_t adp_cs1; /* ADP CS1 */
+#define ADP_CS1_NEXT_CAP_MASK GENMASK(7,0)
+#define GET_ADP_CS_NEXT_CAP(a) (a->adp_cs1 & \
+ ADP_CS1_NEXT_CAP_MASK)
+#define ADP_CS1_COUNTER_SHIFT 8
+#define ADP_CS1_COUNTER_MASK GENMASK(18,8)
+#define GET_ADP_CS_MAX_COUNTERS(a) ((a->adp_cs1 & \
+ ADP_CS1_COUNTER_MASK) >> \
+ ADP_CS1_COUNTER_SHIFT)
+#define CFG_COUNTER_CONFIG_FLAG (1 << 19)
+ uint32_t adp_cs2; /* ADP CS2 */
+#define ADP_CS2_TYPE_MASK GENMASK(23,0)
+#define GET_ADP_CS_TYPE(a) (a->adp_cs2 & ADP_CS2_TYPE_MASK)
+#define ADP_CS2_UNSUPPORTED 0x000000
+#define ADP_CS2_LANE 0x000001
+#define ADP_CS2_HOSTIF 0x000002
+#define ADP_CS2_PCIE_DFP 0x100101
+#define ADP_CS2_PCIE_UFP 0x100102
+#define ADP_CS2_DP_OUT 0x0e0102
+#define ADP_CS2_DP_IN 0x0e0101
+#define ADP_CS2_USB3_DFP 0x200101
+#define ADP_CS2_USB3_UFP 0x200102
+ uint32_t adp_cs3; /* ADP CS 3 */
+#define ADP_CS3_ADP_NUM_SHIFT 20
+#define ADP_CS3_ADP_NUM_MASK GENMASK(25,20)
+#define GET_ADP_CS_ADP_NUM(a) ((a->adp_cs3 & \
+ ADP_CS3_ADP_NUM_MASK) >> \
+ ADP_CS3_ADP_NUM_SHIFT)
+#define CFG_ADP_HEC_ERROR (1 << 29)
+#define CFG_ADP_FC_ERROR (1 << 30)
+#define CFG_ADP_SBC (1 << 31)
+} __packed;
+
+/* Config space lane adapter capability 8.2.2.3 */
+struct tb_cfg_cap_lane {
+ struct tb_cfg_cap_hdr hdr; /* LANE_ADP_CS_0 */
+#define TB_CFG_CAP_LANE 0x01
+ /* Supported link/width/power */
+ uint16_t supp_lwp;
+#define CAP_LANE_LINK_MASK GENMASK(3,0)
+#define CAP_LANE_LINK_GEN3 0x0004
+#define CAP_LANE_LINK_GEN2 0x0008
+#define CAP_LANE_WIDTH_MASK GENMASK(9,4)
+#define CAP_LANE_WIDTH_1X 0x0010
+#define CAP_LANE_WIDTH_2X 0x0020
+#define CAP_LANE_POWER_CL0 0x0400
+#define CAP_LANE_POWER_CL1 0x0800
+#define CAP_LANE_POWER_CL2 0x1000
+ /* Target link/width/power */
+ uint16_t targ_lwp; /* LANE_ADP_CS_1 */
+#define CAP_LANE_TARGET_GEN2 0x0008
+#define CAP_LANE_TARGET_GEN3 0x000c
+#define CAP_LANE_TARGET_SINGLE 0x0010
+#define CAP_LANE_TARGET_DUAL 0x0030
+#define CAP_LANE_DISABLE 0x4000
+#define CAP_LANE_BONDING 0x8000
+ /* Current link/width/state */
+ uint16_t current_lws;
+/* Same definitions a supp_lwp for bits 0 - 9 */
+#define CAP_LANE_STATE_SHIFT 10
+#define CAP_LANE_STATE_MASK GENMASK(13,10)
+#define CAP_LANE_STATE_DISABLE (0x0 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_TRAINING (0x1 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_CL0 (0x2 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_TXCL0 (0x3 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_RXCL0 (0x4 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_CL1 (0x5 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_CL2 (0x6 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_STATE_CLD (0x7 << CAP_LANE_STATE_SHIFT)
+#define CAP_LANE_PMS 0x4000
+ /* Logical Layer Errors */
+ uint16_t lle; /* LANE_ADP_CS_2 */
+#define CAP_LANE_LLE_MASK GENMASK(6,0)
+#define CAP_LANE_LLE_ALE 0x01
+#define CAP_LANE_LLE_OSE 0x02
+#define CAP_LANE_LLE_TE 0x04
+#define CAP_LANE_LLE_EBE 0x08
+#define CAP_LANE_LLE_DBE 0x10
+#define CAP_LANE_LLE_RDE 0x20
+#define CAP_LANE_LLE_RST 0x40
+ uint16_t lle_enable;
+} __packed;
+
+/* Config space path registers 8.2.3.1 */
+struct tb_cfg_path {
+} __packed;
+
+/* Config space counter registers 8.2.4 */
+struct tb_cfg_counters {
+} __packed;
+
+#endif /* _TBCFG_REG_H */