Date: Thu, 2 Oct 2025 22:17:40 +0100 From: Nuno Teixeira <eduardo@freebsd.org> To: Wolfram Schneider <wosch@freebsd.org> Cc: Aymeric Wibo <obiwac@freebsd.org>, src-committers@freebsd.org, dev-commits-src-all@freebsd.org, dev-commits-src-main@freebsd.org Subject: Re: git: 2ed9833791f2 - main - thunderbolt: Import USB4 code Message-ID: <CAFDf7ULhDCEf6ZZniWAtHP0Qv0zi64UgD=VBiyAaM=Uv51bRDg@mail.gmail.com> In-Reply-To: <CAMWY7CAy4Gv-7tTHdgZMwq=kGUV2NPtC4BvBBiS=93J0OhZnCA@mail.gmail.com> References: <202509271713.58RHDTTL008060@gitrepo.freebsd.org> <CAMWY7CAy4Gv-7tTHdgZMwq=kGUV2NPtC4BvBBiS=93J0OhZnCA@mail.gmail.com>
next in thread | previous in thread | raw e-mail | index | archive | help
[-- Attachment #1 --] yep, seing that symlink too. Wolfram Schneider <wosch@freebsd.org> escreveu (terça, 30/09/2025 à(s) 07:44): > I'm now getting a stale symlink: > > ./tools/build/stale-symlink-buildworld.sh > stale symlink detected: lrwxrwxr-x 1 wosch wheel 91 Sep 29 22:14 > > /var/tmp/freebsd-obj-wosch/home/projects/freebsd-src/amd64.amd64/sys/GENERIC/modules/home/projects/freebsd-src/sys/modules/thunderbolt/opt_acpi_wmi.h > -> > /var/tmp/freebsd-obj-wosch/home/projects/freebsd-src/amd64.amd64/sys/GENERIC/opt_acpi_wmi.h > > the only reference to the file "opt_acpi_wmi.h" is in the Makefile > > sys/modules/thunderbolt/Makefile:SRCS+= opt_acpi.h opt_acpi_wmi.h > acpi_if.h acpi_wmi_if.h > > What is this file supposed to do, where does it come from? > > -Wolfram > > On Sat, 27 Sept 2025 at 19:13, 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 *** > > > > -- > Wolfram Schneider <wosch@FreeBSD.org> https://wolfram.schneider.org > > -- Nuno Teixeira FreeBSD UNIX: <eduardo@FreeBSD.org> Web: https://FreeBSD.org [-- Attachment #2 --] <div dir="ltr">yep, seing that symlink too.</div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">Wolfram Schneider <<a href="mailto:wosch@freebsd.org">wosch@freebsd.org</a>> escreveu (terça, 30/09/2025 à(s) 07:44):<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">I'm now getting a stale symlink:<br> <br> ./tools/build/stale-symlink-buildworld.sh<br> stale symlink detected: lrwxrwxr-x 1 wosch wheel 91 Sep 29 22:14<br> /var/tmp/freebsd-obj-wosch/home/projects/freebsd-src/amd64.amd64/sys/GENERIC/modules/home/projects/freebsd-src/sys/modules/thunderbolt/opt_acpi_wmi.h<br> -> /var/tmp/freebsd-obj-wosch/home/projects/freebsd-src/amd64.amd64/sys/GENERIC/opt_acpi_wmi.h<br> <br> the only reference to the file "opt_acpi_wmi.h" is in the Makefile<br> <br> sys/modules/thunderbolt/Makefile:SRCS+= opt_acpi.h opt_acpi_wmi.h<br> acpi_if.h acpi_wmi_if.h<br> <br> What is this file supposed to do, where does it come from?<br> <br> -Wolfram<br> <br> On Sat, 27 Sept 2025 at 19:13, Aymeric Wibo <<a href="mailto:obiwac@freebsd.org" target="_blank">obiwac@freebsd.org</a>> wrote:<br> ><br> > The branch main has been updated by obiwac:<br> ><br> > URL: <a href="https://cgit.FreeBSD.org/src/commit/?id=2ed9833791f28e14843ac813f90cb030e45948dc" rel="noreferrer" target="_blank">https://cgit.FreeBSD.org/src/commit/?id=2ed9833791f28e14843ac813f90cb030e45948dc</a><br> ><br> > commit 2ed9833791f28e14843ac813f90cb030e45948dc<br> > Author: Aymeric Wibo <obiwac@FreeBSD.org><br> > AuthorDate: 2025-09-27 11:50:43 +0000<br> > Commit: Aymeric Wibo <obiwac@FreeBSD.org><br> > CommitDate: 2025-09-27 17:13:13 +0000<br> ><br> > thunderbolt: Import USB4 code<br> ><br> > Add initial USB4 code written by Scott Long and originally passed on to<br> > HPS (source: <a href="https://github.com/hselasky/usb4" rel="noreferrer" target="_blank">https://github.com/hselasky/usb4</a>), minus the ICM code and<br> > with some small fixes.<br> ><br> > For context, older TB chips implemented the connection manager in<br> > firmware (ICM) instead of in the OS (HCM), but maintaining the ICM code<br> > would be a huge burden for not many chips.<br> ><br> > Mostly completed work:<br> ><br> > - Debug/trace framework.<br> > - NHI controller driver.<br> > - PCIe bridge driver.<br> > - Router and config space layer handling (just reading in this commit).<br> ><br> > Link to the email where Scott shared details about the initial USB4<br> > work:<br> ><br> > <a href="https://lists.freebsd.org/archives/freebsd-hackers/2024-July/003411.html" rel="noreferrer" target="_blank">https://lists.freebsd.org/archives/freebsd-hackers/2024-July/003411.html</a><br> ><br> > Glanced at by: emaste, imp<br> > Sponsored by: The FreeBSD Foundation<br> > Differential Revision: <a href="https://reviews.freebsd.org/D49450" rel="noreferrer" target="_blank">https://reviews.freebsd.org/D49450</a><br> > Event: EuroBSDcon 2025<br> > ---<br> > sys/dev/thunderbolt/hcm.c | 223 +++++++<br> > sys/dev/thunderbolt/hcm_var.h | 47 ++<br> > sys/dev/thunderbolt/nhi.c | 1170 ++++++++++++++++++++++++++++++++++++<br> > sys/dev/thunderbolt/nhi_pci.c | 529 ++++++++++++++++<br> > sys/dev/thunderbolt/nhi_reg.h | 332 ++++++++++<br> > sys/dev/thunderbolt/nhi_var.h | 277 +++++++++<br> > sys/dev/thunderbolt/nhi_wmi.c | 198 ++++++<br> > sys/dev/thunderbolt/router.c | 939 +++++++++++++++++++++++++++++<br> > sys/dev/thunderbolt/router_var.h | 242 ++++++++<br> > sys/dev/thunderbolt/tb_acpi_pcib.c | 181 ++++++<br> > sys/dev/thunderbolt/tb_debug.c | 334 ++++++++++<br> > sys/dev/thunderbolt/tb_debug.h | 93 +++<br> > sys/dev/thunderbolt/tb_dev.c | 331 ++++++++++<br> > sys/dev/thunderbolt/tb_dev.h | 41 ++<br> > sys/dev/thunderbolt/tb_if.m | 121 ++++<br> > sys/dev/thunderbolt/tb_ioctl.h | 52 ++<br> > sys/dev/thunderbolt/tb_pcib.c | 614 +++++++++++++++++++<br> > sys/dev/thunderbolt/tb_pcib.h | 93 +++<br> > sys/dev/thunderbolt/tb_reg.h | 52 ++<br> > sys/dev/thunderbolt/tb_var.h | 54 ++<br> > sys/dev/thunderbolt/tbcfg_reg.h | 363 +++++++++++<br> > sys/modules/Makefile | 5 +<br> > sys/modules/thunderbolt/Makefile | 13 +<br> > 23 files changed, 6304 insertions(+)<br> ><br> > diff --git a/sys/dev/thunderbolt/hcm.c b/sys/dev/thunderbolt/hcm.c<br> > new file mode 100644<br> > index 000000000000..b8f703fc3b52<br> > --- /dev/null<br> > +++ b/sys/dev/thunderbolt/hcm.c<br> > @@ -0,0 +1,223 @@<br> > +/*-<br> > + * SPDX-License-Identifier: BSD-2-Clause<br> > + *<br> > + * Copyright (c) 2022 Scott Long<br> > + * All rights reserved.<br> > + *<br> > + * Redistribution and use in source and binary forms, with or without<br> > + * modification, are permitted provided that the following conditions<br> > + * are met:<br> > + * 1. Redistributions of source code must retain the above copyright<br> > + * notice, this list of conditions and the following disclaimer.<br> > + * 2. Redistributions in binary form must reproduce the above copyright<br> > + * notice, this list of conditions and the following disclaimer in the<br> > + * documentation and/or other materials provided with the distribution.<br> > + *<br> > + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND<br> > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE<br> > + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE<br> > + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE<br> > + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL<br> > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS<br> > + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)<br> > + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT<br> > + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY<br> > + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF<br> > + * SUCH DAMAGE.<br> > + */<br> > +<br> > +#include "opt_thunderbolt.h"<br> > +<br> > +/* Host Configuration Manager (HCM) for USB4 and later TB3 */<br> > +#include <sys/types.h><br> > +#include <sys/param.h><br> > +#include <sys/systm.h><br> > +#include <sys/kernel.h><br> > +#include <sys/module.h><br> > +#include <sys/bus.h><br> > +#include <sys/conf.h><br> > +#include <sys/malloc.h><br> > +#include <sys/queue.h><br> > +#include <sys/sysctl.h><br> > +#include <sys/lock.h><br> > +#include <sys/mutex.h><br> > +#include <sys/taskqueue.h><br> > +#include <sys/gsb_crc32.h><br> > +#include <sys/endian.h><br> > +#include <vm/vm.h><br> > +#include <vm/pmap.h><br> > +<br> > +#include <machine/bus.h><br> > +#include <machine/stdarg.h><br> > +<br> > +#include <dev/thunderbolt/nhi_reg.h><br> > +#include <dev/thunderbolt/nhi_var.h><br> > +#include <dev/thunderbolt/tb_reg.h><br> > +#include <dev/thunderbolt/tb_var.h><br> > +#include <dev/thunderbolt/tb_debug.h><br> > +#include <dev/thunderbolt/tbcfg_reg.h><br> > +#include <dev/thunderbolt/router_var.h><br> > +#include <dev/thunderbolt/hcm_var.h><br> > +<br> > +static void hcm_cfg_task(void *, int);<br> > +<br> > +int<br> > +hcm_attach(struct nhi_softc *nsc)<br> > +{<br> > + struct hcm_softc *hcm;<br> > +<br> > + tb_debug(nsc, DBG_HCM|DBG_EXTRA, "hcm_attach called\n");<br> > +<br> > + hcm = malloc(sizeof(struct hcm_softc), M_THUNDERBOLT, M_NOWAIT|M_ZERO);<br> > + if (hcm == NULL) {<br> > + tb_debug(nsc, DBG_HCM, "Cannot allocate hcm object\n");<br> > + return (ENOMEM);<br> > + }<br> > +<br> > + hcm->dev = nsc->dev;<br> > + hcm->nsc = nsc;<br> > + nsc->hcm = hcm;<br> > +<br> > + hcm->taskqueue = taskqueue_create("hcm_event", M_NOWAIT,<br> > + taskqueue_thread_enqueue, &hcm->taskqueue);<br> > + if (hcm->taskqueue == NULL)<br> > + return (ENOMEM);<br> > + taskqueue_start_threads(&hcm->taskqueue, 1, PI_DISK, "tbhcm%d_tq",<br> > + device_get_unit(nsc->dev));<br> > + TASK_INIT(&hcm->cfg_task, 0, hcm_cfg_task, hcm);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +int<br> > +hcm_detach(struct nhi_softc *nsc)<br> > +{<br> > + struct hcm_softc *hcm;<br> > +<br> > + hcm = nsc->hcm;<br> > + if (hcm->taskqueue)<br> > + taskqueue_free(hcm->taskqueue);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +int<br> > +hcm_router_discover(struct hcm_softc *hcm)<br> > +{<br> > +<br> > + taskqueue_enqueue(hcm->taskqueue, &hcm->cfg_task);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +static void<br> > +hcm_cfg_task(void *arg, int pending)<br> > +{<br> > + struct hcm_softc *hcm;<br> > + struct router_softc *rsc;<br> > + struct router_cfg_cap cap;<br> > + struct tb_cfg_router *cfg;<br> > + struct tb_cfg_adapter *adp;<br> > + struct tb_cfg_cap_lane *lane;<br> > + uint32_t *buf;<br> > + uint8_t *u;<br> > + u_int error, i, offset;<br> > +<br> > + hcm = (struct hcm_softc *)arg;<br> > +<br> > + tb_debug(hcm, DBG_HCM|DBG_EXTRA, "hcm_cfg_task called\n");<br> > +<br> > + buf = malloc(8 * 4, M_THUNDERBOLT, M_NOWAIT|M_ZERO);<br> > + if (buf == NULL) {<br> > + tb_debug(hcm, DBG_HCM, "Cannot alloc memory for discovery\n");<br> > + return;<br> > + }<br> > +<br> > + rsc = hcm->nsc->root_rsc;<br> > + error = tb_config_router_read(rsc, 0, 5, buf);<br> > + if (error != 0) {<br> > + free(buf, M_NHI);<br> > + return;<br> > + }<br> > +<br> > + cfg = (struct tb_cfg_router *)buf;<br> > +<br> > + <a href="http://cap.space" rel="noreferrer" target="_blank">cap.space</a> = TB_CFG_CS_ROUTER;<br> > + cap.adap = 0;<br> > + cap.next_cap = GET_ROUTER_CS_NEXT_CAP(cfg);<br> > + while (cap.next_cap != 0) {<br> > + error = tb_config_next_cap(rsc, &cap);<br> > + if (error != 0)<br> > + break;<br> > +<br> > + if ((cap.cap_id == TB_CFG_CAP_VSEC) && (cap.vsc_len == 0)) {<br> > + tb_debug(hcm, DBG_HCM, "Router Cap= %d, vsec= %d, "<br> > + "len= %d, next_cap= %d\n", cap.cap_id,<br> > + cap.vsc_id, cap.vsec_len, cap.next_cap);<br> > + } else if (cap.cap_id == TB_CFG_CAP_VSC) {<br> > + tb_debug(hcm, DBG_HCM, "Router cap= %d, vsc= %d, "<br> > + "len= %d, next_cap= %d\n", cap.cap_id,<br> > + cap.vsc_id, cap.vsc_len, cap.next_cap);<br> > + } else<br> > + tb_debug(hcm, DBG_HCM, "Router cap= %d, "<br> > + "next_cap= %d\n", cap.cap_id, cap.next_cap);<br> > + if (cap.next_cap > TB_CFG_CAP_OFFSET_MAX)<br> > + cap.next_cap = 0;<br> > + }<br> > +<br> > + u = (uint8_t *)buf;<br> > + error = tb_config_get_lc_uuid(rsc, u);<br> > + if (error == 0) {<br> > + tb_debug(hcm, DBG_HCM, "Router LC UUID: %02x%02x%02x%02x-"<br> > + "%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",<br> > + u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8],<br> > + u[9], u[10], u[11], u[12], u[13], u[14], u[15]);<br> > + } else<br> > + tb_printf(hcm, "Error finding LC registers: %d\n", error);<br> > +<br> > + for (i = 1; i <= rsc->max_adap; i++) {<br> > + error = tb_config_adapter_read(rsc, i, 0, 8, buf);<br> > + if (error != 0) {<br> > + tb_debug(hcm, DBG_HCM, "Adapter %d: no adapter\n", i);<br> > + continue;<br> > + }<br> > + adp = (struct tb_cfg_adapter *)buf;<br> > + tb_debug(hcm, DBG_HCM, "Adapter %d: %s, max_counters= 0x%08x,"<br> > + " adapter_num= %d\n", i,<br> > + tb_get_string(GET_ADP_CS_TYPE(adp), tb_adapter_type),<br> > + GET_ADP_CS_MAX_COUNTERS(adp), GET_ADP_CS_ADP_NUM(adp));<br> > +<br> > + if (GET_ADP_CS_TYPE(adp) != ADP_CS2_LANE)<br> > + continue;<br> > +<br> > + error = tb_config_find_adapter_cap(rsc, i, TB_CFG_CAP_LANE,<br> > + &offset);<br> > + if (error)<br> > + continue;<br> > +<br> > + error = tb_config_adapter_read(rsc, i, offset, 3, buf);<br> > + if (error)<br> > + continue;<br> > +<br> > + lane = (struct tb_cfg_cap_lane *)buf;<br> > + tb_debug(hcm, DBG_HCM, "Lane Adapter State= %s %s\n",<br> > + tb_get_string((lane->current_lws & CAP_LANE_STATE_MASK),<br> > + tb_adapter_state), (lane->targ_lwp & CAP_LANE_DISABLE) ?<br> > + "disabled" : "enabled");<br> > +<br> > + if ((lane->current_lws & CAP_LANE_STATE_MASK) ==<br> > + CAP_LANE_STATE_CL0) {<br> > + tb_route_t newr;<br> > +<br> > + newr.hi = rsc->route.hi;<br> > + newr.lo = rsc->route.lo | (i << rsc->depth * 8);<br> > +<br> > + tb_printf(hcm, "want to add router at 0x%08x%08x\n",<br> > + newr.hi, newr.lo);<br> > + error = tb_router_attach(rsc, newr);<br> > + tb_printf(rsc, "tb_router_attach returned %d\n", error);<br> > + }<br> > + }<br> > +<br> > + free(buf, M_THUNDERBOLT);<br> > +}<br> > diff --git a/sys/dev/thunderbolt/hcm_var.h b/sys/dev/thunderbolt/hcm_var.h<br> > new file mode 100644<br> > index 000000000000..a11c8e9b6a92<br> > --- /dev/null<br> > +++ b/sys/dev/thunderbolt/hcm_var.h<br> > @@ -0,0 +1,47 @@<br> > +/*-<br> > + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD<br> > + *<br> > + * Copyright (c) 2022 Scott Long<br> > + * All rights reserved.<br> > + *<br> > + * Redistribution and use in source and binary forms, with or without<br> > + * modification, are permitted provided that the following conditions<br> > + * are met:<br> > + * 1. Redistributions of source code must retain the above copyright<br> > + * notice, this list of conditions and the following disclaimer.<br> > + * 2. Redistributions in binary form must reproduce the above copyright<br> > + * notice, this list of conditions and the following disclaimer in the<br> > + * documentation and/or other materials provided with the distribution.<br> > + *<br> > + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND<br> > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE<br> > + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE<br> > + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE<br> > + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL<br> > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS<br> > + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)<br> > + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT<br> > + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY<br> > + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF<br> > + * SUCH DAMAGE.<br> > + *<br> > + * $FreeBSD$<br> > + */<br> > +<br> > +#ifndef _HCM_VAR_H<br> > +#define _HCM_VAR_H<br> > +<br> > +struct hcm_softc {<br> > + u_int debug;<br> > + device_t dev;<br> > + struct nhi_softc *nsc;<br> > +<br> > + struct task cfg_task;<br> > + struct taskqueue *taskqueue;<br> > +};<br> > +<br> > +int hcm_attach(struct nhi_softc *);<br> > +int hcm_detach(struct nhi_softc *);<br> > +int hcm_router_discover(struct hcm_softc *);<br> > +<br> > +#endif /* _HCM_VAR_H */<br> > diff --git a/sys/dev/thunderbolt/nhi.c b/sys/dev/thunderbolt/nhi.c<br> > new file mode 100644<br> > index 000000000000..205e69c16253<br> > --- /dev/null<br> > +++ b/sys/dev/thunderbolt/nhi.c<br> > @@ -0,0 +1,1170 @@<br> > +/*-<br> > + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD<br> > + *<br> > + * Copyright (c) 2022 Scott Long<br> > + * All rights reserved.<br> > + *<br> > + * Redistribution and use in source and binary forms, with or without<br> > + * modification, are permitted provided that the following conditions<br> > + * are met:<br> > + * 1. Redistributions of source code must retain the above copyright<br> > + * notice, this list of conditions and the following disclaimer.<br> > + * 2. Redistributions in binary form must reproduce the above copyright<br> > + * notice, this list of conditions and the following disclaimer in the<br> > + * documentation and/or other materials provided with the distribution.<br> > + *<br> > + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND<br> > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE<br> > + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE<br> > + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE<br> > + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL<br> > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS<br> > + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)<br> > + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT<br> > + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY<br> > + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF<br> > + * SUCH DAMAGE.<br> > + */<br> > +<br> > +#include "opt_thunderbolt.h"<br> > +<br> > +/* PCIe interface for Thunderbolt Native Host Interface (nhi) */<br> > +#include <sys/types.h><br> > +#include <sys/param.h><br> > +#include <sys/systm.h><br> > +#include <sys/kernel.h><br> > +#include <sys/module.h><br> > +#include <sys/bus.h><br> > +#include <sys/conf.h><br> > +#include <sys/malloc.h><br> > +#include <sys/queue.h><br> > +#include <sys/sysctl.h><br> > +#include <sys/lock.h><br> > +#include <sys/mutex.h><br> > +#include <sys/taskqueue.h><br> > +#include <sys/gsb_crc32.h><br> > +#include <sys/endian.h><br> > +#include <vm/vm.h><br> > +#include <vm/pmap.h><br> > +<br> > +#include <machine/bus.h><br> > +#include <machine/stdarg.h><br> > +<br> > +#include <dev/thunderbolt/nhi_reg.h><br> > +#include <dev/thunderbolt/nhi_var.h><br> > +#include <dev/thunderbolt/tb_reg.h><br> > +#include <dev/thunderbolt/tb_var.h><br> > +#include <dev/thunderbolt/tb_debug.h><br> > +#include <dev/thunderbolt/hcm_var.h><br> > +#include <dev/thunderbolt/tbcfg_reg.h><br> > +#include <dev/thunderbolt/router_var.h><br> > +#include <dev/thunderbolt/tb_dev.h><br> > +#include "tb_if.h"<br> > +<br> > +static int nhi_alloc_ring(struct nhi_softc *, int, int, int,<br> > + struct nhi_ring_pair **);<br> > +static void nhi_free_ring(struct nhi_ring_pair *);<br> > +static void nhi_free_rings(struct nhi_softc *);<br> > +static int nhi_configure_ring(struct nhi_softc *, struct nhi_ring_pair *);<br> > +static int nhi_activate_ring(struct nhi_ring_pair *);<br> > +static int nhi_deactivate_ring(struct nhi_ring_pair *);<br> > +static int nhi_alloc_ring0(struct nhi_softc *);<br> > +static void nhi_free_ring0(struct nhi_softc *);<br> > +static void nhi_fill_rx_ring(struct nhi_softc *, struct nhi_ring_pair *);<br> > +static int nhi_init(struct nhi_softc *);<br> > +static void nhi_post_init(void *);<br> > +static int nhi_tx_enqueue(struct nhi_ring_pair *, struct nhi_cmd_frame *);<br> > +static int nhi_setup_sysctl(struct nhi_softc *);<br> > +<br> > +SYSCTL_NODE(_hw, OID_AUTO, nhi, CTLFLAG_RD, 0, "NHI Driver Parameters");<br> > +<br> > +MALLOC_DEFINE(M_NHI, "nhi", "nhi driver memory");<br> > +<br> > +#ifndef NHI_DEBUG_LEVEL<br> > +#define NHI_DEBUG_LEVEL 0<br> > +#endif<br> > +<br> > +/* 0 = default, 1 = force-on, 2 = force-off */<br> > +#ifndef NHI_FORCE_HCM<br> > +#define NHI_FORCE_HCM 0<br> > +#endif<br> > +<br> > +void<br> > +nhi_get_tunables(struct nhi_softc *sc)<br> > +{<br> > + devclass_t dc;<br> > + device_t ufp;<br> > + char tmpstr[80], oid[80];<br> > + u_int val;<br> > +<br> > + /* Set local defaults */<br> > + sc->debug = NHI_DEBUG_LEVEL;<br> > + sc->max_ring_count = NHI_DEFAULT_NUM_RINGS;<br> > + sc->force_hcm = NHI_FORCE_HCM;<br> > +<br> > + /* Inherit setting from the upstream thunderbolt switch node */<br> > + val = TB_GET_DEBUG(sc->dev, &sc->debug);<br> > + if (val != 0) {<br> > + dc = devclass_find("tbolt");<br> > + if (dc != NULL) {<br> > + ufp = devclass_get_device(dc, device_get_unit(sc->dev));<br> > + if (ufp != NULL)<br> > + TB_GET_DEBUG(ufp, &sc->debug);<br> > + } else {<br> > + if (TUNABLE_STR_FETCH("hw.tbolt.debug_level", oid,<br> > + 80) != 0)<br> > + tb_parse_debug(&sc->debug, oid);<br> > + }<br> > + }<br> > +<br> > + /*<br> > + * Grab global variables. Allow nhi debug flags to override<br> > + * thunderbolt debug flags, if present.<br> > + */<br> > + bzero(oid, 80);<br> > + if (TUNABLE_STR_FETCH("hw.nhi.debug_level", oid, 80) != 0)<br> > + tb_parse_debug(&sc->debug, oid);<br> > + if (TUNABLE_INT_FETCH("hw.nhi.max_rings", &val) != 0) {<br> > + val = min(val, NHI_MAX_NUM_RINGS);<br> > + sc->max_ring_count = max(val, 1);<br> > + }<br> > + if (TUNABLE_INT_FETCH("hw.nhi.force_hcm", &val) != 0)<br> > + sc->force_hcm = val;<br> > +<br> > + /* Grab instance variables */<br> > + bzero(oid, 80);<br> > + snprintf(tmpstr, sizeof(tmpstr), "dev.nhi.%d.debug_level",<br> > + device_get_unit(sc->dev));<br> > + if (TUNABLE_STR_FETCH(tmpstr, oid, 80) != 0)<br> > + tb_parse_debug(&sc->debug, oid);<br> > + snprintf(tmpstr, sizeof(tmpstr), "dev.nhi.%d.max_rings",<br> > + device_get_unit(sc->dev));<br> > + if (TUNABLE_INT_FETCH(tmpstr, &val) != 0) {<br> > + val = min(val, NHI_MAX_NUM_RINGS);<br> > + sc->max_ring_count = max(val, 1);<br> > + }<br> > + snprintf(tmpstr, sizeof(tmpstr), "dev, nhi.%d.force_hcm",<br> > + device_get_unit(sc->dev));<br> > + if (TUNABLE_INT_FETCH(tmpstr, &val) != 0)<br> > + sc->force_hcm = val;<br> > +<br> > + return;<br> > +}<br> > +<br> > +static void<br> > +nhi_configure_caps(struct nhi_softc *sc)<br> > +{<br> > +<br> > + if (NHI_IS_USB4(sc) || (sc->force_hcm == NHI_FORCE_HCM_ON))<br> > + sc->caps |= NHI_CAP_HCM;<br> > + if (sc->force_hcm == NHI_FORCE_HCM_OFF)<br> > + sc->caps &= ~NHI_CAP_HCM;<br> > +}<br> > +<br> > +struct nhi_cmd_frame *<br> > +nhi_alloc_tx_frame(struct nhi_ring_pair *r)<br> > +{<br> > + struct nhi_cmd_frame *cmd;<br> > +<br> > + mtx_lock(&r->mtx);<br> > + cmd = nhi_alloc_tx_frame_locked(r);<br> > + mtx_unlock(&r->mtx);<br> > +<br> > + return (cmd);<br> > +}<br> > +<br> > +void<br> > +nhi_free_tx_frame(struct nhi_ring_pair *r, struct nhi_cmd_frame *cmd)<br> > +{<br> > + mtx_lock(&r->mtx);<br> > + nhi_free_tx_frame_locked(r, cmd);<br> > + mtx_unlock(&r->mtx);<br> > +}<br> > +<br> > +/*<br> > + * Push a command and data dword through the mailbox to the firmware.<br> > + * Response is either good, error, or timeout. Commands that return data<br> > + * do so by reading OUTMAILDATA.<br> > + */<br> > +int<br> > +nhi_inmail_cmd(struct nhi_softc *sc, uint32_t cmd, uint32_t data)<br> > +{<br> > + uint32_t val;<br> > + u_int error, timeout;<br> > +<br> > + mtx_lock(&sc->nhi_mtx);<br> > + /*<br> > + * XXX Should a defer/reschedule happen here, or is it not worth<br> > + * worrying about?<br> > + */<br> > + if (sc->hwflags & NHI_MBOX_BUSY) {<br> > + mtx_unlock(&sc->nhi_mtx);<br> > + tb_debug(sc, DBG_MBOX, "Driver busy with mailbox\n");<br> > + return (EBUSY);<br> > + }<br> > + sc->hwflags |= NHI_MBOX_BUSY;<br> > +<br> > + val = nhi_read_reg(sc, TBT_INMAILCMD);<br> > + tb_debug(sc, DBG_MBOX|DBG_FULL, "Reading INMAILCMD= 0x%08x\n", val);<br> > + if (val & INMAILCMD_ERROR)<br> > + tb_debug(sc, DBG_MBOX, "Error already set in INMAILCMD\n");<br> > + if (val & INMAILCMD_OPREQ) {<br> > + mtx_unlock(&sc->nhi_mtx);<br> > + tb_debug(sc, DBG_MBOX,<br> > + "INMAILCMD request already in progress\n");<br> > + return (EBUSY);<br> > + }<br> > +<br> > + nhi_write_reg(sc, TBT_INMAILDATA, data);<br> > + nhi_write_reg(sc, TBT_INMAILCMD, cmd | INMAILCMD_OPREQ);<br> > +<br> > + /* Poll at 1s intervals */<br> > + timeout = NHI_MAILBOX_TIMEOUT;<br> > + while (timeout--) {<br> > + DELAY(1000000);<br> > + val = nhi_read_reg(sc, TBT_INMAILCMD);<br> > + tb_debug(sc, DBG_MBOX|DBG_EXTRA,<br> > + "Polling INMAILCMD= 0x%08x\n", val);<br> > + if ((val & INMAILCMD_OPREQ) == 0)<br> > + break;<br> > + }<br> > + sc->hwflags &= ~NHI_MBOX_BUSY;<br> > + mtx_unlock(&sc->nhi_mtx);<br> > +<br> > + error = 0;<br> > + if (val & INMAILCMD_OPREQ) {<br> > + tb_printf(sc, "Timeout waiting for mailbox\n");<br> > + error = ETIMEDOUT;<br> > + }<br> > + if (val & INMAILCMD_ERROR) {<br> > + tb_printf(sc, "Firmware reports error in mailbox\n");<br> > + error = EINVAL;<br> > + }<br> > +<br> > + return (error);<br> > +}<br> > +<br> > +/*<br> > + * Pull command status and data from the firmware mailbox.<br> > + */<br> > +int<br> > +nhi_outmail_cmd(struct nhi_softc *sc, uint32_t *val)<br> > +{<br> > +<br> > + if (val == NULL)<br> > + return (EINVAL);<br> > + *val = nhi_read_reg(sc, TBT_OUTMAILCMD);<br> > + return (0);<br> > +}<br> > +<br> > +int<br> > +nhi_attach(struct nhi_softc *sc)<br> > +{<br> > + uint32_t val;<br> > + int error = 0;<br> > +<br> > + if ((error = nhi_setup_sysctl(sc)) != 0)<br> > + return (error);<br> > +<br> > + mtx_init(&sc->nhi_mtx, "nhimtx", "NHI Control Mutex", MTX_DEF);<br> > +<br> > + nhi_configure_caps(sc);<br> > +<br> > + /*<br> > + * Get the number of TX/RX paths. This sizes some of the register<br> > + * arrays during allocation and initialization. USB4 spec says that<br> > + * the max is 21. Alpine Ridge appears to default to 12.<br> > + */<br> > + val = GET_HOST_CAPS_PATHS(nhi_read_reg(sc, NHI_HOST_CAPS));<br> > + tb_debug(sc, DBG_INIT|DBG_NOISY, "Total Paths= %d\n", val);<br> > + if ((val == 0) || (val > 21) || ((NHI_IS_AR(sc) && val != 12))) {<br> > + tb_printf(sc, "WARN: unexpected number of paths: %d\n", val);<br> > + /* return (ENXIO); */<br> > + }<br> > + sc->path_count = val;<br> > +<br> > + SLIST_INIT(&sc->ring_list);<br> > +<br> > + error = nhi_pci_configure_interrupts(sc);<br> > + if (error == 0)<br> > + error = nhi_alloc_ring0(sc);<br> > + if (error == 0) {<br> > + nhi_configure_ring(sc, sc->ring0);<br> > + nhi_activate_ring(sc->ring0);<br> > + nhi_fill_rx_ring(sc, sc->ring0);<br> > + }<br> > +<br> > + if (error == 0)<br> > + error = tbdev_add_interface(sc);<br> > +<br> > + if ((error == 0) && (NHI_USE_ICM(sc)))<br> > + tb_printf(sc, "WARN: device uses an internal connection manager\n");<br> > + if ((error == 0) && (NHI_USE_HCM(sc)))<br> > + ;<br> > + error = hcm_attach(sc);<br> > +<br> > + if (error == 0)<br> > + error = nhi_init(sc);<br> > +<br> > + return (error);<br> > +}<br> > +<br> > +int<br> > +nhi_detach(struct nhi_softc *sc)<br> > +{<br> > +<br> > + if (NHI_USE_HCM(sc))<br> > + hcm_detach(sc);<br> > +<br> > + if (sc->root_rsc != NULL)<br> > + tb_router_detach(sc->root_rsc);<br> > +<br> > + tbdev_remove_interface(sc);<br> > +<br> > + nhi_pci_disable_interrupts(sc);<br> > +<br> > + nhi_free_ring0(sc);<br> > +<br> > + /* XXX Should the rings be marked as !VALID in the descriptors? */<br> > + nhi_free_rings(sc);<br> > +<br> > + mtx_destroy(&sc->nhi_mtx);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +static void<br> > +nhi_memaddr_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)<br> > +{<br> > + bus_addr_t *addr;<br> > +<br> > + addr = arg;<br> > + if (error == 0 && nsegs == 1) {<br> > + *addr = segs[0].ds_addr;<br> > + } else<br> > + *addr = 0;<br> > +}<br> > +<br> > +static int<br> > +nhi_alloc_ring(struct nhi_softc *sc, int ringnum, int tx_depth, int rx_depth,<br> > + struct nhi_ring_pair **rp)<br> > +{<br> > + bus_dma_template_t t;<br> > + bus_addr_t ring_busaddr;<br> > + struct nhi_ring_pair *r;<br> > + int ring_size, error;<br> > + u_int rxring_len, txring_len;<br> > + char *ring;<br> > +<br> > + if (ringnum >= sc->max_ring_count) {<br> > + tb_debug(sc, DBG_INIT, "Tried to allocate ring number %d\n",<br> > + ringnum);<br> > + return (EINVAL);<br> > + }<br> > +<br> > + /* Allocate the ring structure and the RX ring tacker together. */<br> > + rxring_len = rx_depth * sizeof(void *);<br> > + txring_len = tx_depth * sizeof(void *);<br> > + r = malloc(sizeof(struct nhi_ring_pair) + rxring_len + txring_len,<br> > + M_NHI, M_NOWAIT|M_ZERO);<br> > + if (r == NULL) {<br> > + tb_printf(sc, "ERROR: Cannot allocate ring memory\n");<br> > + return (ENOMEM);<br> > + }<br> > +<br> > + r->sc = sc;<br> > + TAILQ_INIT(&r->tx_head);<br> > + TAILQ_INIT(&r->rx_head);<br> > + r->ring_num = ringnum;<br> > + r->tx_ring_depth = tx_depth;<br> > + r->tx_ring_mask = tx_depth - 1;<br> > + r->rx_ring_depth = rx_depth;<br> > + r->rx_ring_mask = rx_depth - 1;<br> > + r->rx_pici_reg = NHI_RX_RING_PICI + ringnum * 16;<br> > + r->tx_pici_reg = NHI_TX_RING_PICI + ringnum * 16;<br> > + r->rx_cmd_ring = (struct nhi_cmd_frame **)((uint8_t *)r + sizeof (*r));<br> > + r->tx_cmd_ring = (struct nhi_cmd_frame **)((uint8_t *)r->rx_cmd_ring +<br> > + rxring_len);<br> > +<br> > + snprintf(r->name, NHI_RING_NAMELEN, "nhiring%d\n", ringnum);<br> > + mtx_init(&r->mtx, r->name, "NHI Ring Lock", MTX_DEF);<br> > + tb_debug(sc, DBG_INIT | DBG_FULL, "Allocated ring context at %p, "<br> > + "mutex %p\n", r, &r->mtx);<br> > +<br> > + /* Allocate the RX and TX buffer descriptor rings */<br> > + ring_size = sizeof(struct nhi_tx_buffer_desc) * r->tx_ring_depth;<br> > + ring_size += sizeof(struct nhi_rx_buffer_desc) * r->rx_ring_depth;<br> > + tb_debug(sc, DBG_INIT | DBG_FULL, "Ring %d ring_size= %d\n",<br> > + ringnum, ring_size);<br> > +<br> > + bus_dma_template_init(&t, sc->parent_dmat);<br> > + t.alignment = 4;<br> > + t.maxsize = t.maxsegsize = ring_size;<br> > + t.nsegments = 1;<br> > + if ((error = bus_dma_template_tag(&t, &r->ring_dmat)) != 0) {<br> > + tb_printf(sc, "Cannot allocate ring %d DMA tag: %d\n",<br> > + ringnum, error);<br> > + return (ENOMEM);<br> > + }<br> > + if (bus_dmamem_alloc(r->ring_dmat, (void **)&ring, BUS_DMA_NOWAIT,<br> > + &r->ring_map)) {<br> > + tb_printf(sc, "Cannot allocate ring memory\n");<br> > + return (ENOMEM);<br> > + }<br> > + bzero(ring, ring_size);<br> > + bus_dmamap_load(r->ring_dmat, r->ring_map, ring, ring_size,<br> > + nhi_memaddr_cb, &ring_busaddr, 0);<br> > +<br> > + r->ring = ring;<br> > +<br> > + r->tx_ring = (union nhi_ring_desc *)(ring);<br> > + r->tx_ring_busaddr = ring_busaddr;<br> > + ring += sizeof(struct nhi_tx_buffer_desc) * r->tx_ring_depth;<br> > + ring_busaddr += sizeof(struct nhi_tx_buffer_desc) * r->tx_ring_depth;<br> > +<br> > + r->rx_ring = (union nhi_ring_desc *)(ring);<br> > + r->rx_ring_busaddr = ring_busaddr;<br> > +<br> > + tb_debug(sc, DBG_INIT | DBG_EXTRA, "Ring %d: RX %p [0x%jx] "<br> > + "TX %p [0x%jx]\n", ringnum, r->tx_ring, r->tx_ring_busaddr,<br> > + r->rx_ring, r->rx_ring_busaddr);<br> > +<br> > + *rp = r;<br> > + return (0);<br> > +}<br> > +<br> > +static void<br> > +nhi_free_ring(struct nhi_ring_pair *r)<br> > +{<br> > +<br> > + tb_debug(r->sc, DBG_INIT, "Freeing ring %d resources\n", r->ring_num);<br> > + nhi_deactivate_ring(r);<br> > +<br> > + if (r->tx_ring_busaddr != 0) {<br> > + bus_dmamap_unload(r->ring_dmat, r->ring_map);<br> > + r->tx_ring_busaddr = 0;<br> > + }<br> > + if (r->ring != NULL) {<br> > + bus_dmamem_free(r->ring_dmat, r->ring, r->ring_map);<br> > + r->ring = NULL;<br> > + }<br> > + if (r->ring_dmat != NULL) {<br> > + bus_dma_tag_destroy(r->ring_dmat);<br> > + r->ring_dmat = NULL;<br> > + }<br> > + mtx_destroy(&r->mtx);<br> > +}<br> > +<br> > +static void<br> > +nhi_free_rings(struct nhi_softc *sc)<br> > +{<br> > + struct nhi_ring_pair *r;<br> > +<br> > + while ((r = SLIST_FIRST(&sc->ring_list)) != NULL) {<br> > + nhi_free_ring(r);<br> > + mtx_lock(&sc->nhi_mtx);<br> > + SLIST_REMOVE_HEAD(&sc->ring_list, ring_link);<br> > + mtx_unlock(&sc->nhi_mtx);<br> > + free(r, M_NHI);<br> > + }<br> > +<br> > + return;<br> > +}<br> > +<br> > +static int<br> > +nhi_configure_ring(struct nhi_softc *sc, struct nhi_ring_pair *ring)<br> > +{<br> > + bus_addr_t busaddr;<br> > + uint32_t val;<br> > + int idx;<br> > +<br> > + idx = ring->ring_num * 16;<br> > +<br> > + /* Program the TX ring address and size */<br> > + busaddr = ring->tx_ring_busaddr;<br> > + nhi_write_reg(sc, NHI_TX_RING_ADDR_LO + idx, busaddr & 0xffffffff);<br> > + nhi_write_reg(sc, NHI_TX_RING_ADDR_HI + idx, busaddr >> 32);<br> > + nhi_write_reg(sc, NHI_TX_RING_SIZE + idx, ring->tx_ring_depth);<br> > + nhi_write_reg(sc, NHI_TX_RING_TABLE_TIMESTAMP + idx, 0x0);<br> > + tb_debug(sc, DBG_INIT, "TX Ring %d TX_RING_SIZE= 0x%x\n",<br> > + ring->ring_num, ring->tx_ring_depth);<br> > +<br> > + /* Program the RX ring address and size */<br> > + busaddr = ring->rx_ring_busaddr;<br> > + val = (ring->rx_buffer_size << 16) | ring->rx_ring_depth;<br> > + nhi_write_reg(sc, NHI_RX_RING_ADDR_LO + idx, busaddr & 0xffffffff);<br> > + nhi_write_reg(sc, NHI_RX_RING_ADDR_HI + idx, busaddr >> 32);<br> > + nhi_write_reg(sc, NHI_RX_RING_SIZE + idx, val);<br> > + nhi_write_reg(sc, NHI_RX_RING_TABLE_BASE1 + idx, 0xffffffff);<br> > + tb_debug(sc, DBG_INIT, "RX Ring %d RX_RING_SIZE= 0x%x\n",<br> > + ring->ring_num, val);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +static int<br> > +nhi_activate_ring(struct nhi_ring_pair *ring)<br> > +{<br> > + struct nhi_softc *sc = ring->sc;<br> > + int idx;<br> > +<br> > + nhi_pci_enable_interrupt(ring);<br> > +<br> > + idx = ring->ring_num * 32;<br> > + tb_debug(sc, DBG_INIT, "Activating ring %d at idx %d\n",<br> > + ring->ring_num, idx);<br> > + nhi_write_reg(sc, NHI_TX_RING_TABLE_BASE0 + idx,<br> > + TX_TABLE_RAW | TX_TABLE_VALID);<br> > + nhi_write_reg(sc, NHI_RX_RING_TABLE_BASE0 + idx,<br> > + RX_TABLE_RAW | RX_TABLE_VALID);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +static int<br> > +nhi_deactivate_ring(struct nhi_ring_pair *r)<br> > +{<br> > + struct nhi_softc *sc = r->sc;<br> > + int idx;<br> > +<br> > + idx = r->ring_num * 32;<br> > + tb_debug(sc, DBG_INIT, "Deactiving ring %d at idx %d\n",<br> > + r->ring_num, idx);<br> > + nhi_write_reg(sc, NHI_TX_RING_TABLE_BASE0 + idx, 0);<br> > + nhi_write_reg(sc, NHI_RX_RING_TABLE_BASE0 + idx, 0);<br> > +<br> > + idx = r->ring_num * 16;<br> > + tb_debug(sc, DBG_INIT, "Setting ring %d sizes to 0\n", r->ring_num);<br> > + nhi_write_reg(sc, NHI_TX_RING_SIZE + idx, 0);<br> > + nhi_write_reg(sc, NHI_RX_RING_SIZE + idx, 0);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +static int<br> > +nhi_alloc_ring0(struct nhi_softc *sc)<br> > +{<br> > + bus_addr_t frames_busaddr;<br> > + bus_dma_template_t t;<br> > + struct nhi_intr_tracker *trkr;<br> > + struct nhi_ring_pair *r;<br> > + struct nhi_cmd_frame *cmd;<br> > + char *frames;<br> > + int error, size, i;<br> > +<br> > + if ((error = nhi_alloc_ring(sc, 0, NHI_RING0_TX_DEPTH,<br> > + NHI_RING0_RX_DEPTH, &r)) != 0) {<br> > + tb_printf(sc, "Error allocating control ring\n");<br> > + return (error);<br> > + }<br> > +<br> > + r->rx_buffer_size = NHI_RING0_FRAME_SIZE;/* Control packets are small */<br> > +<br> > + /* Allocate the RX and TX buffers that are used for Ring0 comms */<br> > + size = r->tx_ring_depth * NHI_RING0_FRAME_SIZE;<br> > + size += r->rx_ring_depth * NHI_RING0_FRAME_SIZE;<br> > +<br> > + bus_dma_template_init(&t, sc->parent_dmat);<br> > + t.maxsize = t.maxsegsize = size;<br> > + t.nsegments = 1;<br> > + if (bus_dma_template_tag(&t, &sc->ring0_dmat)) {<br> > + tb_printf(sc, "Error allocating control ring buffer tag\n");<br> > + return (ENOMEM);<br> > + }<br> > +<br> > + if (bus_dmamem_alloc(sc->ring0_dmat, (void **)&frames, BUS_DMA_NOWAIT,<br> > + &sc->ring0_map) != 0) {<br> > + tb_printf(sc, "Error allocating control ring memory\n");<br> > + return (ENOMEM);<br> > + }<br> > + bzero(frames, size);<br> > + bus_dmamap_load(sc->ring0_dmat, sc->ring0_map, frames, size,<br> > + nhi_memaddr_cb, &frames_busaddr, 0);<br> > + sc->ring0_frames_busaddr = frames_busaddr;<br> > + sc->ring0_frames = frames;<br> > +<br> > + /* Allocate the driver command trackers */<br> > + sc->ring0_cmds = malloc(sizeof(struct nhi_cmd_frame) *<br> > + (r->tx_ring_depth + r->rx_ring_depth), M_NHI, M_NOWAIT | M_ZERO);<br> > + if (sc->ring0_cmds == NULL)<br> > + return (ENOMEM);<br> > +<br> > + /* Initialize the RX frames so they can be used */<br> > + mtx_lock(&r->mtx);<br> > + for (i = 0; i < r->rx_ring_depth; i++) {<br> > + cmd = &sc->ring0_cmds[i];<br> > + cmd->data = (uint32_t *)(frames + NHI_RING0_FRAME_SIZE * i);<br> > + cmd->data_busaddr = frames_busaddr + NHI_RING0_FRAME_SIZE * i;<br> > + cmd->flags = CMD_MAPPED;<br> > + cmd->idx = i;<br> > + TAILQ_INSERT_TAIL(&r->rx_head, cmd, cm_link);<br> > + }<br> > +<br> > + /* Inititalize the TX frames */<br> > + for ( ; i < r->tx_ring_depth + r->rx_ring_depth - 1; i++) {<br> > + cmd = &sc->ring0_cmds[i];<br> > + cmd->data = (uint32_t *)(frames + NHI_RING0_FRAME_SIZE * i);<br> > + cmd->data_busaddr = frames_busaddr + NHI_RING0_FRAME_SIZE * i;<br> > + cmd->flags = CMD_MAPPED;<br> > + cmd->idx = i;<br> > + nhi_free_tx_frame_locked(r, cmd);<br> > + }<br> > + mtx_unlock(&r->mtx);<br> > +<br> > + /* Do a 1:1 mapping of rings to interrupt vectors. */<br> > + /* XXX Should be abstracted */<br> > + trkr = &sc->intr_trackers[0];<br> > + trkr->ring = r;<br> > + r->tracker = trkr;<br> > +<br> > + /* XXX Should be an array */<br> > + sc->ring0 = r;<br> > + SLIST_INSERT_HEAD(&sc->ring_list, r, ring_link);<br> > +<br> > + return (0);<br> > +}<br> > +<br> > +static void<br> > +nhi_free_ring0(struct nhi_softc *sc)<br> > +{<br> > + if (sc->ring0_cmds != NULL) {<br> > + free(sc->ring0_cmds, M_NHI);<br> > + sc->ring0_cmds = NULL;<br> > + }<br> > +<br> > + if (sc->ring0_frames_busaddr != 0) {<br> > + bus_dmamap_unload(sc->ring0_dmat, sc->ring0_map);<br> > + sc->ring0_frames_busaddr = 0;<br> > *** 5529 LINES SKIPPED ***<br> <br> <br> <br> -- <br> Wolfram Schneider <wosch@FreeBSD.org> <a href="https://wolfram.schneider.org" rel="noreferrer" target="_blank">https://wolfram.schneider.org</a><br> <br> </blockquote></div><div><br clear="all"></div><br><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr"><div><font color="#888888">Nuno Teixeira</font></div><div><div><font color="#888888"> FreeBSD UNIX: <eduardo@FreeBSD.org> Web: <a href="https://FreeBSD.org" rel="noreferrer" target="_blank">https://FreeBSD.org</a><br></font></div></div></div></div>
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAFDf7ULhDCEf6ZZniWAtHP0Qv0zi64UgD=VBiyAaM=Uv51bRDg>
