USB xHCI DbC Support on FreeBSD
Most xHCI controllers support USB Debug Capability (DbC) on their USB3 ports. The USB DbC turns one of the USB ports on a machine into the USB device mode, which supports two bulk transfer endpoints for input and output. It can also be used for the serial console.
Call for Testing
Please try the work-in-progress patch on your machine and add an entry to the following table.
Model |
Reporter |
xHCI Controller |
Confirmed Debug Ports (number, type, location) |
Cable (see list) |
Status |
Last Update |
ThinkPad X250 |
hrs |
Wildcat Point-LP USB xHCI Controller (vendor=0x8086 device=0x9cb1) |
12 (A, right), 13 (A, left) |
1 |
works on loader and kernel |
2024-07-16 |
ThinkPad X1 Nano Gen 1 |
hrs |
Tiger Lake-LP USB 3.2 Gen 2x1 xHCI Host Controller (vendor=0x8086 device=0xa0ed) |
? |
1 |
The device is working, but physical connection has not been recognized yet. |
2024-07-27 |
ThinkPad X1 Carbon 6th |
lwhsu |
Sunrise Point-LP USB 3.0 xHCI Controller (vendor=0x8086 device=0x9d2f) |
13 (A, right), 14 (A, left) |
1 |
works on loader and kernel |
2024-09-19 |
ThinkPad X1 Carbon 6th |
lwhsu |
JHL6540 Thunderbolt 3 USB Controller (C step) [Alpine Ridge 4C 2016] (vendor=0x8086 device=0x15d4) |
? |
1 |
The device is working, but physical connection has not been recognized yet. |
2024-09-19 |
Minisforum MS-01 |
hrs |
Intel Alder Lake PCH USB 3.2 controller (vendor=0x8086 device=0x51ed) |
15 (A, front), 14 (A, rear left), 13 (A, rear right) |
1 |
works on loader and kernel |
2024-08-07 |
Rapsberry Pi 4B |
hrs |
VL805 (vendor=? device=?) |
? |
1 |
Not confirmed yet but VL805 supports xHCI DbC |
2024-07-27 |
PCIe card |
hrs |
Renesas uPD720201 USB 3.0 Host Controller (vendor=0x1912 device=0x0014) |
? |
1 |
The device was recognized in kernel but not in loader |
2024-07-27 |
The debug port numbers can be find in sysctl dev.udbcons.N.portnum after connecting a cable.
How To Use
Physical Configuration
- Machine A: The debug target. The machine you want console access to.
- Machine B: The debug host. You initiate a connection to Machine A from Machine B.
- Cable: USB3 A-to-A cross cable.
Machine B needs to have a client driver. The udbc(4) driver is available. It is not necessary to be a FreeBSD machine if Machine B has a USB Debug Class driver.
Machine A needs USB DbC support in the loader and the kernel.
You need to connect A and B together using the cable.
- The cable must be connected directly to Machine A. Any ports on the root hub should work.
- The other end of the cable can be connected to Machine B directly or indirectly (i.e., via a USB hub).
- This works only on USB3 ports (blue color). If you are using a USB hub between A and B, ensure it supports USB3.
The following USB3 cables have been confirmed to work:
https://www.amazon.co.jp/dp/B00O4VPY7U (available in Japan, and distributed by HirokiSato at conferences)
At this moment, machine A needs a type-A port. A simple A-to-C adapter does not seem to work, and USB role control must probably be implemented on the OS side.
Source Files
A work-in-progress tree can be found at https://github.com/hrs-allbsd/freebsd-src. Two branches, main-hrs-usbdbc (not yet pushed) and stable-14-hrs-usbdbc (already pushed) are available for each branch. Build a kernel and world, then install the kernel and the UEFI loader. The differences are:
- loader.efi (for debug target, /boot/efi/efi/boot/bootx64.efi and /boot/efi/efi/freebsd/loader.efi)
- kernel (for debug target)
- udbc(4) driver (for client)
Bootable Images
You can use the following images for both machine A and machine B. These are release images that invoke the installer just after boot. Choose "Live System" and login as root (empty password is configured).
stable/14: https://people.allbsd.org/~hrs/FreeBSD/udbc/20240809/usbdbc-14.1-STABLE-amd64-memstick-20240809.img.xz (mirror)
Check whether it works
The udbcons console is enabled by default. First, boot the kernel and check the dmesg. You should have messages from the xhci(4) driver like this:
xhci2: <Intel Alder Lake PCH USB 3.2 controller> mem 0x6127180000-0x612718ffff at device 20.0 on pci0 xhci2: 32 bytes context size, 64-bit DMA udbcons2: <USB xHCI DbC Console> on xhci2 udbcons2: DbC xECP found at 0x8700 udbcons2: Creating /dev/udbcons2 udbcons2: state: CONFIGURED(0x1d) -> OFF(0x00) udbcons2: state: OFF(0x00) -> DISCONNECTED(0x10) udbcons2: waiting for a cable udbcons2: state: DISCONNECTED(0x10) -> ENABLED(0x1c) udbcons2: state: ENABLED(0x1c) -> CONFIGURED(0x1d) udbcons2: DbC cable detected
If you get "DbC xECP found," one of your xhci(4) chips supports USB DbC. If not, please report the values listed in the xECP lines when specifying hw.usb.xhci.dbc.debug=1 in loader.conf. With this debug option, you will get some additional lines like this:
xhci_debug_get_xecp: Looking for xECP: expected=0a, found=02, next=0020 xhci_debug_get_xecp: Looking for xECP: expected=0a, found=02, next=0050 xhci_debug_get_xecp: Looking for xECP: expected=0a, found=c0, next=03fc xhci_debug_get_xecp: Looking for xECP: expected=0a, found=01, next=0088 xhci_debug_get_xecp: Looking for xECP: expected=0a, found=c6, next=000c xhci_debug_get_xecp: Looking for xECP: expected=0a, found=c7, next=0100 xhci_debug_get_xecp: Looking for xECP: expected=0a, found=c2, next=0100 xhci_debug_get_xecp: Looking for xECP: expected=0a, found=0a, next=0040 xhci_debug_get_xecp: DbC was found at 00008700
"xECP" stands for xHCI Extended CaPabilities, which shows the availability of optional features. If your environment has USB DbC, xECP includes 0x0a. In the above example, the eighth entry is 0x0a, and "02, 02, c0, 01, c6, c7, c2, 0a" is the xECP list. USB DbC does not work if there was no 0x0a entry, unfortunately.
If you are lucky enough to find 0x0a entry, you can use USB DbC on your machine.
- On machine A and B, connect to them using a USB3 A-to-A cross cable.
On machine A, check if you can see /dev/udbconsN device node. The unit number N depends on your environment.
On machine A, check if you will see CONFIGURED(0x1d) in the dev.udbcons.N.state sysctl variable after inserting the cable. N is the unit number. If it is not CONFIGURED, the cable was not detected or something went wrong.
On machine B, prepare the udbc(4) client driver. The udbc(4) driver is not automatically loaded, so you need kldload udbc. After connecting the cable, you will see /dev/cuaU0 (the unit number depends on the number of USB-serial devices).
Please note that if machine A has a Type-C port, machine B will likely not find the cable connection. I am still investigating how to activate it.
If everything seems working, try to connect from machine B to machine A using cu(1) like this:
machine-A # cu -l /dev/udbcons2
machine-B # cu -l /dev/cuaU0
You can check if the bidirectional communication works by entering characters on both sides.
If it worked, the udbcons works as a console. To get a login prompt after boot, add the following lines into /etc/ttys:
# USB DbC console udbcons2 "/usr/libexec/getty 3wire" xterm on secure
and kill -HUP 1 on machine A. On machine B, you can see a prompt like this:
machine-B # cu -l /dev/cuaU0 Connected FreeBSD/amd64 (bb7.allbsd.org) (udbcons2) login:
This is a communication between the getty process on machine A and tip(1) on machine B, not the kernel console on machine A. To activate the udbcons console for the kernel, use conscontrol(8) on machine A:
# conscontrol add udbcons
Just like a serial console via uart(4), you can use the key sequences to reset the machine or enter DDB from machine B if your kernel is built with KDB and DDB options after this configuration using conscontrol(8). Setting debug.kdb.alt_break_to_debugger=1 is recommended for testing. You can get a ddb(8) access by a key sequence CR, ~, and CTRL + b if options DDB and options KDB in the kernel configuration like this:
hrs@bb7:~ % KDB: enter: Break to debugger [ thread pid 2 tid 100047 ] Stopped at kdb_alt_break_internal+0x14d: movq $0,0x103d488(%rip) db>
The udbcons kernel console is connected to one of the /dev/udbconsN instances. Currently, the first entry will be automatically used. You can check which /dev/udbconsN is associated with the kernel console via hw.usb.xhci.dbc.udbcons sysctl variable:
% sysctl hw.usb.xhci.dbc.udbcons hw.usb.xhci.dbc.udbcons: udbcons1
In the above case, only /dev/udbcons1 works as the kernel console or ddb(8). If you want to change it, use hw.usb.xhci.dbc.pci_rid to specify the xhci(4) device. You can see PCI RID via dev.udbcons.N.pci_rid sysctl variables.
% sysctl dev.udbcons.2.pci_rid dev.udbcons.2.pci_rid: 0x00a0(0:20:0)
Setting this hexadecimal value in /boot/loader.conf, you can change the udbcons instance used for the kernel console:
hw.usb.xhci.dbc.pci_rid="0xa0"
In the loader before booting the kernel, you can also get console access by setting "console" variable like this:
OK set console="udbc efi"
or you can simply set console=udbc. Note that after a kernel loaded, the USB connection is lost once because the USB controller is reset. The connection should be re-established shortly.
CAVEATS and TODO
- The kernel and loader work only on amd64 machines because of dependencies on the UEFI loader and 1:1 direct mapping. The udbc(4) client driver does not have this limitation. More separation in terms of MI/MD and loaders is ongoing to support i386 on legacy BIOS and aarch64 platforms.
- Configuration to get early console output is still missing. The boot_multicons loader option does not work, or the kernel does not recognize the udbcons console as a console device at boot time. These will be fixed soon.
- Flow control is still incomplete. Feeding data into the console rapidly, some data will be lost. And the udbc(4) driver may be unresponsive due to a stall condition of the USB connection. Re-opening the /dev/cuaU* device should clear it.
- The udbcons console does not work as a GDB debug port. This will be fixed soon.
- If multiple xhci(4) chips are available on the machine, the first instance will be used for the kernel console at this moment. You can also use hw.usb.xhci.dbc.pci_rid to specify the PCI RID for the console.
- Type-C ports seem not to work even if a supported xhci(4) device is detected because signal routing on the port is not properly initialized. I am working on this problem.
- In addition to xHCI DbC, Intel 12th-, 13th-, and 14th-gen processors have integrated xDCI (USB3 device controller). PCH also has an xHCI controller, so you will see at least two xhci(4) devices on FreeBSD. The xHCI controller on the processor side supports only Type-C ports. The current implementation does not recognize a A-to-A DbC cable on these models. I am working on this problem.
Technical Details of the Current Implementation
DbC is an embedded USB device controller inside an xHCI host controller. It is an optional feature in the xHCI specification, and controlled by a dedicated register set located in the xHCI Extended Capability region found in PCI configuration space. The implementation offers a limited version of a USB device with only two bulk endpoints for input and output.
The patchset implements "udbcons" driver as a child of the xhci(4) driver. DbC is probed and attached in xhci(4) PCI-specific initialization routine just like attachment of usbus(4). Currently an xHCI controller has only one DbC instance. The attached udbcon(4) driver configures the xHCI controller and activates the functionality. It creates /dev/udbconsN device nodes as entry points from the userland, and registers "udbcons" as a kernel console. Note that this kernel console is connected to one of the udbcons driver instances, not all of them.
Communication via the udbcons driver is totally independent of the xhci(4) driver. It does not support interrupts upon data arrival on the Rx endpoint, so a callout is configured for polling. Data for Tx and Rx are maintained by a simple 32KB ring buffer.
Data structures used in the udbcons driver are non-conventional to realize the same configuration on loader and kernel. The loader configures the parameters, and then the kernel will reuse them. Due to this structure, the loader must allocate a softc and pass the address to the kernel. hw.usb.xhci.dbc.softc* kernel environment variables are used for that. Memory allocation and PCI access are done using UEFI Boot Services. In kernel, the udbcons driver maps the softc address and use the memory region. Currently the driver assumes 1:1 direct mapping and does not use busdma(9) or resource management framework in the kernel. This needs to be fixed in terms of MI/MD separation.