Re: git: 2ed9833791f2 - main - thunderbolt: Import USB4 code

From: Zhenlei Huang <zlei_at_FreeBSD.org>
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 ***