Skip to content

Custom device plug with subsystem: usb passes no device node into the container #1046

Open

Description

@MirkoCovizzi

What happened

A custom-device plug with subsystem: usb connects without error, but the container gets no USB device node. Flashing tools that use libusb cannot see the probe.

How to reproduce

I reproduced this with a real SEGGER J-Link (1366:1065) in a Workshop 26.04 container (base [email protected]).

Declare an in-project SDK with a USB-subsystem plug for the probe:

name: jlink-usb
plugs:
  jlink:
    interface: custom-device
    subsystem: usb
    vendorid: "1366"
    productid: "1065"

Add it to the workshop definition, refresh, then connect the plug to the system slot:

$ workshop connect my-workshop/jlink-usb:jlink :custom-device

The connection lists normally, the same as a working tty plug:

$ workshop connections my-workshop
custom-device  my-workshop/jlink-usb:jlink  my-workshop/system:custom-device  manual

Now look inside the container:

$ workshop exec my-workshop -- ls /dev/bus/usb/003/
ls: cannot access '/dev/bus/usb/003/': No such file or directory

$ workshop exec my-workshop -- find /dev/bus
find: '/dev/bus': No such file or directory

Nothing under /dev/bus exists in the container. The host side looks fine: udevadm reports the probe at /dev/bus/usb/003/013 with SUBSYSTEM=usb, ID_VENDOR_ID=1366, ID_MODEL_ID=1065. The only mount the plug produces inside the container is the SDK tree itself.

A tty plug for the same physical board works in the same setup. The raw USB node is the difference.

Why it matters

Debug probes register as raw USB devices. openocd and other flash tools reach them through libusb, which needs the /dev/bus/usb/<bus>/<dev> node. Without it, west flash fails:

Warn : Failed to open device: LIBUSB_ERROR_NO_DEVICE
Error: No J-Link device found
FATAL ERROR: command exited with status 1

A tty plug is no substitute here, because probe flashing is not serial traffic.

What I expected

  • The container receives the /dev/bus/usb/<bus>/<dev> node for the matched device.
  • Hotplug updates the mapping as the device is added or removed.
  • Stop and start, disconnect and reconnect, and replugging keep working.

The interface docs list usb as a supported subsystem value, so I read this plug shape as supported. Am I missing something about how the USB subsystem is meant to be wired, or is the container-side bind for subsystem: usb devices not implemented yet?

Environment

  • Workshop 0.9.7-d1613f5 (snap, classic)
  • LXD 6.9
  • Ubuntu host
  • Workshop base: [email protected]
  • Device: SEGGER J-Link (1366:1065)

Disclosure: Qwen 3.8 Flash Next drafted this report. A human reviewed it before posting.

Activity

  1. jonathan-conder commented on Sep 20, 2026

    @jonathan-conder
    Contributor

    I can reproduce this with a different USB device. It looks like LXD carves out an exception for the usb subsystem here: https://github.com/canonical/lxd/blob/lxd-6.9/lxd/device/unix_hotplug.go#L25, even if you explicitly specify subsystem: usb. Will see what we can do about that.

    In the meantime, a workaround would be to manually add a unix-char or usb device, e.g.:

    $ lxc config device add --project "workshop.$(id -u)" "my-workshop-$(< .workshop.lock)" jlink-usb usb vendorid=1366 productid=1065
  2. jonathan-conder commented on Oct 29, 2026

    @jonathan-conder
    Contributor

    @MirkoCovizzi I talked to @locnnil about this today, it would be good if you could confirm that the workaround I mentioned works properly; if so we can make it part of the fix.

  3. MirkoCovizzi commented on Nov 5, 2026

    @MirkoCovizzi
    Author

    @jonathan-conder I tried the workaround with the real J-Link (1366:1065) on Workshop 0.9.9 and LXD 6.10.1. It creates the node, but needs two corrections, and it doesn't survive a replug.

    Works

    • /dev/bus/usb/003/<dev> appears in the running container straight away.
    • It survives workshop stop/start and workshop refresh.
    • lsusb -v and SEGGER's JLinkExe work through the node.

    Corrections

    1. Use workshop.$(id -un) for the project, not workshop.$(id -u). Workshop names its LXD project after the user name, not the numeric uid. With id -u the command fails with Project not found.
    2. Add uid, gid and mode. Without them the node is created root:root with mode 0664. The container user is workshop (uid 1000), so it can't open the node read-write, and libusb needs read-write. open(O_RDWR) fails with Permission denied until the node is owned by that user.
    lxc config device add --project "workshop.$(id -un)" "my-workshop-$(< .workshop.lock)" jlink-usb usb vendorid=1366 productid=1065 uid=1000 gid=1000 mode=0660
    

    Doesn't work: hotplug

    • I replugged the probe three times. Each time it got a new device number (031, 032, 033, 034).
    • The container kept the old node and never got the new one. This happened with a vendor-only match, a vendor+product match, and with or without uid/gid/mode.
    • Removing and re-adding the LXD device fixes it by hand.

    So the hotplug expectation isn't met by the manual usb device.


    Disclosure: Claude Sonnet 5.5 drafted this comment. A human reviewed it before posting.

  4. jonathan-conder commented on Nov 5, 2026

    @jonathan-conder
    Contributor

    @MirkoCovizzi Thanks! That's really useful.

    For the UID/GID and mode it looks like we'll need the usb devices to support ownership.inherit; I filed a feature request at canonical/lxd#19448. In the meantime we could potentially supply the UID and GID manually like you suggest.

    I noticed, at least on my machine, I can't open the devices in /dev/bus/usb/*/* for writing as a normal user; do you expect to be able to do that? Maybe you can just use sudo inside the workshop?

    I can confirm the hotplugging doesn't work. The LXD docs say it should though!

  5. jonathan-conder commented on Nov 6, 2026

    @jonathan-conder
    Contributor

    I pushed a fix for the hotplugging issue: canonical/lxd#19450. Hopefully that will get the ball rolling a bit.

    Also one for inheriting ownership: canonical/lxd#19451; I don't think it's super relevant for this use case though.

    Once hotplugging is working, it should be possible to add a udev rule inside the workshop that changes the permissions, if that's what you want. It will only work when hotplugging the device though. I have another feature request to fix that here: canonical/lxd#18430.

  6. jonathan-conder commented on Nov 12, 2026

    @jonathan-conder
    Contributor

    A fix is available in the edge/1151 channel, I'll test it tomorrow if I get the chance (CC @locnnil)

  7. jonathan-conder commented on Nov 12, 2026

    @jonathan-conder
    Contributor

    It seems to basically work; no hotplugging or ownership inheritance yet though since we'll have to wait for a LXD fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions