Re: git: 2ed9833791f2 - main - thunderbolt: Import USB4 code
- In reply to: Aymeric Wibo : "git: 2ed9833791f2 - main - thunderbolt: Import USB4 code"
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 30 Sep 2025 15:17:20 UTC
Nice work !
Now my thunderbolt ethernet is correctly recognized and works great on my old MBP.
Cheers !
Best regards,
Zhenlei
> On Sep 28, 2025, at 1:13 AM, Aymeric Wibo <obiwac@freebsd.org> wrote:
>
> The branch main has been updated by obiwac:
>
> URL: https://cgit.FreeBSD.org/src/commit/?id=2ed9833791f28e14843ac813f90cb030e45948dc
>
> commit 2ed9833791f28e14843ac813f90cb030e45948dc
> Author: Aymeric Wibo <obiwac@FreeBSD.org>
> AuthorDate: 2025-09-27 11:50:43 +0000
> Commit: Aymeric Wibo <obiwac@FreeBSD.org>
> CommitDate: 2025-09-27 17:13:13 +0000
>
> thunderbolt: Import USB4 code
>
> Add initial USB4 code written by Scott Long and originally passed on to
> HPS (source: https://github.com/hselasky/usb4), minus the ICM code and
> with some small fixes.
>
> For context, older TB chips implemented the connection manager in
> firmware (ICM) instead of in the OS (HCM), but maintaining the ICM code
> would be a huge burden for not many chips.
>
> Mostly completed work:
>
> - Debug/trace framework.
> - NHI controller driver.
> - PCIe bridge driver.
> - Router and config space layer handling (just reading in this commit).
>
> Link to the email where Scott shared details about the initial USB4
> work:
>
> https://lists.freebsd.org/archives/freebsd-hackers/2024-July/003411.html
>
> Glanced at by: emaste, imp
> Sponsored by: The FreeBSD Foundation
> Differential Revision: https://reviews.freebsd.org/D49450
> Event: EuroBSDcon 2025
> ---
> sys/dev/thunderbolt/hcm.c | 223 +++++++
> sys/dev/thunderbolt/hcm_var.h | 47 ++
> sys/dev/thunderbolt/nhi.c | 1170 ++++++++++++++++++++++++++++++++++++
> sys/dev/thunderbolt/nhi_pci.c | 529 ++++++++++++++++
> sys/dev/thunderbolt/nhi_reg.h | 332 ++++++++++
> sys/dev/thunderbolt/nhi_var.h | 277 +++++++++
> sys/dev/thunderbolt/nhi_wmi.c | 198 ++++++
> sys/dev/thunderbolt/router.c | 939 +++++++++++++++++++++++++++++
> sys/dev/thunderbolt/router_var.h | 242 ++++++++
> sys/dev/thunderbolt/tb_acpi_pcib.c | 181 ++++++
> sys/dev/thunderbolt/tb_debug.c | 334 ++++++++++
> sys/dev/thunderbolt/tb_debug.h | 93 +++
> sys/dev/thunderbolt/tb_dev.c | 331 ++++++++++
> sys/dev/thunderbolt/tb_dev.h | 41 ++
> sys/dev/thunderbolt/tb_if.m | 121 ++++
> sys/dev/thunderbolt/tb_ioctl.h | 52 ++
> sys/dev/thunderbolt/tb_pcib.c | 614 +++++++++++++++++++
> sys/dev/thunderbolt/tb_pcib.h | 93 +++
> sys/dev/thunderbolt/tb_reg.h | 52 ++
> sys/dev/thunderbolt/tb_var.h | 54 ++
> sys/dev/thunderbolt/tbcfg_reg.h | 363 +++++++++++
> sys/modules/Makefile | 5 +
> sys/modules/thunderbolt/Makefile | 13 +
> 23 files changed, 6304 insertions(+)
>
> 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;
> *** 5529 LINES SKIPPED ***