/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 Scott Long * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_thunderbolt.h" /* 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); MALLOC_DEFINE(M_THUNDERBOLT, "thunderbolt", "memory for thunderbolt"); 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; } } 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); }