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

How To Use

Physical Configuration

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 following USB3 cables have been confirmed to work:

  1. 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:

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.

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

  1. 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.
  2. 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.
  3. 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.
  4. The udbcons console does not work as a GDB debug port. This will be fixed soon.
  5. 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.
  6. 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.
  7. 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.

HirokiSato/xhci (last edited 2024-09-19T09:47:36+0000 by HirokiSato)