Date: Sat, 27 Sep 2025 17:13:29 GMT From: Aymeric Wibo <obiwac@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 2ed9833791f2 - main - thunderbolt: Import USB4 code Message-ID: <202509271713.58RHDTTL008060@gitrepo.freebsd.org>
next in thread | raw e-mail | index | archive | help
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 ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202509271713.58RHDTTL008060>