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