git: 46e6e9173875 - stable/15 - udbc: Add usb debug host mode driver
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 18 Sep 2025 12:54:21 UTC
The branch stable/15 has been updated by thj: URL: https://cgit.FreeBSD.org/src/commit/?id=46e6e91738757e3090423ce5e0dc700d4e24da7b commit 46e6e91738757e3090423ce5e0dc700d4e24da7b Author: Tom Jones <thj@FreeBSD.org> AuthorDate: 2025-07-08 08:04:00 +0000 Commit: Tom Jones <thj@FreeBSD.org> CommitDate: 2025-09-18 12:53:13 +0000 udbc: Add usb debug host mode driver xhci offers a debugging interface which uses a special usb 3 cable with the D+, D- and VBUS pairs disconnected. This interface allows a target device to configure its xhci controller as a debugging channel which can then be used to provide a serial link between the target and a debug host. This change extracts the udbc host mode driver from hrs@'s xhci debug implementation. Reviewed by: bcr (man page) MFC after: Before 15-ALPHA3 builds Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D51299 (cherry picked from commit d566b6a70bcbc329e8c690464043401fa8bdd49f) --- share/man/man4/udbc.4 | 132 ++++++++++++++ sys/dev/usb/serial/udbc.c | 404 ++++++++++++++++++++++++++++++++++++++++++ sys/modules/usb/Makefile | 7 +- sys/modules/usb/udbc/Makefile | 9 + 4 files changed, 548 insertions(+), 4 deletions(-) diff --git a/share/man/man4/udbc.4 b/share/man/man4/udbc.4 new file mode 100644 index 000000000000..c8fa02ec18f1 --- /dev/null +++ b/share/man/man4/udbc.4 @@ -0,0 +1,132 @@ +.\" +.\" Copyright (c) 2025 The FreeBSD Foundation +.\" +.\" This documentation was written by Tom Jones <thj@freebsd.org> under +.\" sponsorship from the FreeBSD Foundation. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" +.Dd September 3, 2025 +.Dt UDBC 4 +.Os +.Sh NAME +.Nm udbc +.Nd USB Debug Class device driver +.Sh SYNOPSIS +.Cd "device usb" +.Cd "device ucom" +.Cd "device udbc" +.Pp +In +.Xr rc.conf 5 : +.Cd kld_list="udbc" +.Sh DESCRIPTION +The +.Nm +driver provides support for USB Debug Class devices whose +interface class is Diagnostic Class and the subclass is DbC.GP. +.Pp +The USB Debug Class is defined in the USB 3.1 Device Class +Specification for Debug Devices. +This is designed to provide a general-purpose communication channel +for debugging. +It has also been widely implemented in USB xHCs +.Pq USB eXtensible Host Controllers , +which can be found on many commodity computers, +as an optional feature. +Once this feature is enabled on a USB xHC, one of the USB ports will +behave as a USB Debug Class device, +not a host port, +when a USB debug cable is connected. +The supported class in USB xHCs is typically DbC.GP, +while the specification defines several types of Debug Class devices. +The DbC.GP uses IN and OUT endpoint pairs and realizes a single +bidirectional serial communication channel. +On most systems, +including +.Fx , +the DbC.GP is seen as a simple serial device. +.Pp +Most systems with USB xHC can be configured to provide DbC.GP access. +The +.Nm +is a driver that connects to DbC.GP-supported devices, +offering +.Xr tty 4 +devices to connect to them via the +.Xr ucom 4 +device driver. +.Sh HARDWARE CONFIGURATION +A native DbC.GP device can be attached using the +.Nm +driver in a straightforward way. +.Pp +A USB xHC DbC.GP device on a target system needs a special hardware +configuration because all of the ports are supposed to be a USB Host. +There is one method to expose DbC.GP is to use a USB 3.1 A-to-A cable +.Pq section 5.5.2 in USB 3.1 Legacy Cable and Connector Specification . +When this cable is connected to a USB 3.1 port on the target system, +the DbC-enabled USB xHC automatically switches the port as a USB Device. +The +.Nm +driver can find a DbC.GP device on that port. +.Pp +Note that a USB xHC with USB 3.2 support +.Pq USB Type-C connectors +is not compatible with the USB 3.1 A-to-A cable. +Connecting a USB 3.2 C-to-C cable or A-to-C cable does not automatically work, +either, +because it needs role configuration of the port, which is not supported on +.Fx +yet. +.Sh FILES +.Bl -tag -width "/dev/ttyU*.*.init" -compact +.It Pa /dev/ttyU*.* +for callin ports +.It Pa /dev/ttyU*.*.init +.It Pa /dev/ttyU*.*.lock +corresponding callin initial-state and lock-state devices +.Pp +.It Pa /dev/cuaU*.* +for callout ports +.It Pa /dev/cuaU*.*.init +.It Pa /dev/cuaU*.*.lock +corresponding callout initial-state and lock-state devices +.El +.Sh SEE ALSO +.Xr tty 4 , +.Xr ucom 4 , +.Xr usb 4 , +.Xr xhci 4 +.Sh STANDARDS +.Rs +.%T eXtensible Host Controller Interface for Universal Serial Bus (XHCI) +.%U https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf +.Re +.Rs +.%T USB 3.1 Device Class Specification for Debug Devices +.%U https://www.usb.org/sites/default/files/documents/usb_debug_class_rev_1_0_final_0.pdf +.Re +.Rs +.%T USB 3.1 Legacy Cable and Connector Specification +.%U https://www.usb.org/document-library/usb-31-legacy-cable-and-connector-revision-10 +.Re +.Sh HISTORY +The +.Nm +driver first appeared +.Fx +15.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Hiroki Sato Aq Mt hrs@FreeBSD.org . +.Sh BUGS +According to the XHCI specification the host side of USB Debug should work with +any USB 3.0 port, +whether connected directly to a controller or with a hub in between. +Testing on some controllers has encountered issues when using a hub rather than +a directly connected port on the controller. diff --git a/sys/dev/usb/serial/udbc.c b/sys/dev/usb/serial/udbc.c new file mode 100644 index 000000000000..d7ca6b25bf32 --- /dev/null +++ b/sys/dev/usb/serial/udbc.c @@ -0,0 +1,404 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * Copyright (c) 2016-2024 Hiroki Sato <hrs@FreeBSD.org> + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/callout.h> +#include <sys/condvar.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/priv.h> +#include <sys/queue.h> +#include <sys/stddef.h> +#include <sys/stdint.h> +#include <sys/sx.h> +#include <sys/sysctl.h> +#include <sys/unistd.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usb_ioctl.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_core.h> + +#include "usbdevs.h" + +#define USB_DEBUG_VAR udbc_debug +#include <dev/usb/usb_process.h> +#include <dev/usb/serial/usb_serial.h> +#include <dev/usb/usb_debug.h> + +static SYSCTL_NODE(_hw_usb, OID_AUTO, udbc, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "USB DbC Client"); + +#ifdef USB_DEBUG +static int udbc_debug = 0; +SYSCTL_INT(_hw_usb_udbc, OID_AUTO, debug, CTLFLAG_RWTUN, &udbc_debug, 0, + "Debug level"); +#endif + +#define UDBC_CONFIG_INDEX 0 + +#define UDBC_IBUFSIZE 1024 +#define UDBC_OBUFSIZE 1024 + +enum { + UDBC_BULK_DT_WR, + UDBC_BULK_DT_RD, + UDBC_N_TRANSFER, /* n of EP */ +}; + +struct udbc_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UDBC_N_TRANSFER]; + device_t sc_dev; + struct mtx sc_mtx; + + uint32_t sc_unit; +}; + +/* prototypes */ + +static device_probe_t udbc_probe; +static device_attach_t udbc_attach; +static device_detach_t udbc_detach; +static void udbc_free_softc(struct udbc_softc *); + +static usb_callback_t udbc_write_callback; +static usb_callback_t udbc_read_callback; + +static void udbc_free(struct ucom_softc *); +static void udbc_cfg_open(struct ucom_softc *); +static void udbc_cfg_close(struct ucom_softc *); +static int udbc_pre_param(struct ucom_softc *, struct termios *); +static int udbc_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, + struct thread *); +static void udbc_start_read(struct ucom_softc *); +static void udbc_stop_read(struct ucom_softc *); +static void udbc_start_write(struct ucom_softc *); +static void udbc_stop_write(struct ucom_softc *); +static void udbc_poll(struct ucom_softc *ucom); + +static const struct usb_config udbc_config[UDBC_N_TRANSFER] = { + [UDBC_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UDBC_OBUFSIZE, + .flags = {.pipe_bof = 1,}, + .callback = &udbc_write_callback, + }, + + [UDBC_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UDBC_IBUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &udbc_read_callback, + }, +}; + +static const struct ucom_callback udbc_callback = { + .ucom_cfg_open = &udbc_cfg_open, + .ucom_cfg_close = &udbc_cfg_close, + .ucom_pre_param = &udbc_pre_param, + .ucom_ioctl = &udbc_ioctl, + .ucom_start_read = &udbc_start_read, + .ucom_stop_read = &udbc_stop_read, + .ucom_start_write = &udbc_start_write, + .ucom_stop_write = &udbc_stop_write, + .ucom_poll = &udbc_poll, + .ucom_free = &udbc_free, +}; + +static device_method_t udbc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udbc_probe), + DEVMETHOD(device_attach, udbc_attach), + DEVMETHOD(device_detach, udbc_detach), + DEVMETHOD_END +}; + +static int +udbc_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != UDBC_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bInterfaceClass != UICLASS_DIAGNOSTIC) + return (ENXIO); + if (uaa->info.bDeviceProtocol != 0x00) /* GNU GDB == 1 */ + return (ENXIO); + + return (BUS_PROBE_SPECIFIC); +} + +static int +udbc_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct udbc_softc *sc = device_get_softc(dev); + int error; + + DPRINTF("\n"); + + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "udbc", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_ucom.sc_portno = 0; + + error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->sc_xfer, udbc_config, UDBC_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, + "allocating USB transfers failed\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UDBC_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UDBC_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &udbc_callback, &sc->sc_mtx); + if (error) + goto detach; + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + udbc_detach(dev); + return (ENXIO); +} + +static int +udbc_detach(device_t dev) +{ + struct udbc_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UDBC_N_TRANSFER); + + device_claim_softc(dev); + + udbc_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(udbc); + +static void +udbc_free_softc(struct udbc_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +udbc_free(struct ucom_softc *ucom) +{ + udbc_free_softc(ucom->sc_parent); +} + +static void +udbc_cfg_open(struct ucom_softc *ucom) +{ + /* + * This do-nothing open routine exists for the sole purpose of this + * DPRINTF() so that you can see the point at which open gets called + * when debugging is enabled. + */ + DPRINTF("\n"); +} + +static void +udbc_cfg_close(struct ucom_softc *ucom) +{ + /* + * This do-nothing close routine exists for the sole purpose of this + * DPRINTF() so that you can see the point at which close gets called + * when debugging is enabled. + */ + DPRINTF("\n"); +} + +static void +udbc_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udbc_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t buflen; + + DPRINTFN(3, "\n"); + + switch (USB_GET_STATE(xfer)) { + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + } + /* FALLTHROUGH */ + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, UDBC_OBUFSIZE, + &buflen) == 0) + break; + if (buflen != 0) { + usbd_xfer_set_frame_len(xfer, 0, buflen); + usbd_transfer_submit(xfer); + } + break; + } +} + +static void +udbc_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udbc_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int buflen; + + DPRINTFN(3, "\n"); + + usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, buflen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static int +udbc_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + DPRINTF("\n"); + + return (0); +} + +static int +udbc_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, + struct thread *td) +{ + return (ENOIOCTL); +} + +static void +udbc_start_read(struct ucom_softc *ucom) +{ + struct udbc_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UDBC_BULK_DT_RD]); +} + +static void +udbc_stop_read(struct ucom_softc *ucom) +{ + struct udbc_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UDBC_BULK_DT_RD]); +} + +static void +udbc_start_write(struct ucom_softc *ucom) +{ + struct udbc_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UDBC_BULK_DT_WR]); +} + +static void +udbc_stop_write(struct ucom_softc *ucom) +{ + struct udbc_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UDBC_BULK_DT_WR]); +} + +static void +udbc_poll(struct ucom_softc *ucom) +{ + struct udbc_softc *sc = ucom->sc_parent; + + usbd_transfer_poll(sc->sc_xfer, UDBC_N_TRANSFER); +} + +static driver_t udbc_driver = { + .name = "udbc", + .methods = udbc_methods, + .size = sizeof(struct udbc_softc), +}; + +DRIVER_MODULE(udbc, uhub, udbc_driver, NULL, NULL); +MODULE_DEPEND(udbc, ucom, 1, 1, 1); +MODULE_DEPEND(udbc, usb, 1, 1, 1); +MODULE_VERSION(udbc, 1); diff --git a/sys/modules/usb/Makefile b/sys/modules/usb/Makefile index 1290b878fa37..d9b1c8635b30 100644 --- a/sys/modules/usb/Makefile +++ b/sys/modules/usb/Makefile @@ -46,10 +46,9 @@ SUBDIR = usb SUBDIR += ${_dwc_otg} ehci ${_musb} ohci uhci xhci ${_uss820dci} \ ${_atmegadci} ${_avr32dci} ${_rsu} ${_rsufw} ${_bcm2838_xhci} SUBDIR += mtw ${_rum} ${_run} ${_runfw} ${_uath} upgt usie ural ${_zyd} ${_urtw} -SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled \ - usbhid -SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \ - umb umct umcs umodem umoscom uplcom uslcom uvisor uvscom +SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled usbhid +SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom udbc ufoma uftdi ugensa uipaq +SUBDIR += ulpt umb umct umcs umodem umoscom uplcom uslcom uvisor uvscom SUBDIR += i2ctinyusb SUBDIR += cp2112 SUBDIR += udl diff --git a/sys/modules/usb/udbc/Makefile b/sys/modules/usb/udbc/Makefile new file mode 100644 index 000000000000..9996b2e391fb --- /dev/null +++ b/sys/modules/usb/udbc/Makefile @@ -0,0 +1,9 @@ +S= ${SRCTOP}/sys + +.PATH: $S/dev/usb/serial + +KMOD= udbc +SRCS= opt_bus.h opt_usb.h device_if.h bus_if.h usb_if.h usbdevs.h \ + udbc.c + +.include <bsd.kmod.mk>