I have a USB GPIO device with some 3rd party drivers that I want to interface with in a container. Compiles, works fine on host. Also compiles, works fine in the container if I pass --device=/dev/bus/usb/001/$NUM
where $NUM is the autogenerated path when the device is plugged in; I presume udev is assigning this. However I want a deterministic bind point, so I modified the udev rules to assign a symlink:
SUBSYSTEM=="usb", ATTR{idVendor}=="09db",
ATTR{idProduct}=="0075", MODE="0666",
TAG+="uaccess", TAG+="udev-acl",
SYMLINK+="mcc_daq"
And that gives me a symlink at /dev/mcc_daq
to /dev/bus/usb/whatever
. Still works fine on host.
But, if I run with:
docker run --rm -it \
--device=/dev/mcc_daq \
mcc_daq1
I get
usb_device_find_USB_MCC: libusb_open failed.: No such file or directory
Failure, did not find a USB 2408 or 2408_2AO!
which is what the test program spits out when it can't find the device (uses libusb_get_device_descriptor).
When I run in the container udevadm info -q all /dev/mcc_daq
, I get the same exact output as udevadm info -q all /dev/bus/usb/001/004
when I start the container with --device=/.../004
. However, if I pass in the symlink path to docker, I can't query the absolute path.
The udev output is more verbose on the host system, I'm not sure if this is part of the problem or expected behavior. My intuition is the lack of those extra entries means that libusb can find the device, but it can't find the vendor ID of the product. However, the fact that the udev output is the same in the container (regardless whether symlink or hard path) makes me question that.
Update: New fun fact: when I run strace on the test program, I find:
open("/dev/bus/usb/001/004", O_RDWR) = -1 ENOENT (No such file or directory)
So that means the driver is looking for ../004
, even though I mounted ---device=/dev/mcc_daq
. I'm not sure where it's getting this information.
In short, I think using docker run --device=/dev/symlink
does not introduce the same udev context as using the hard path, even though output of udevadm info
is the same.
Host: udevadm info -q all /dev/mcc_daq
P: /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
N: bus/usb/001/004
S: mcc_daq
E: BUSNUM=001
E: DEVLINKS=/dev/mcc_daq
E: DEVNAME=/dev/bus/usb/001/004
E: DEVNUM=004
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
E: DEVTYPE=usb_device
E: DRIVER=usb
E: ID_BUS=usb
E: ID_FOR_SEAT=usb-pci-0000_00_14_0-usb-0_8_2
E: ID_MODEL=USB-2408-2AO
E: ID_MODEL_ENC=USB-2408-2AO
E: ID_MODEL_ID=00fe
E: ID_PATH=pci-0000:00:14.0-usb-0:8.2
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_8_2
E: ID_REVISION=0101
E: ID_SERIAL=MCC_USB-2408-2AO_01DA523C
E: ID_SERIAL_SHORT=01DA523C
E: ID_USB_INTERFACES=:ffff00:
E: ID_VENDOR=MCC
E: ID_VENDOR_ENC=MCC
E: ID_VENDOR_FROM_DATABASE=Measurement Computing Corp.
E: ID_VENDOR_ID=09db
E: MAJOR=189
E: MINOR=3
E: PRODUCT=9db/fe/101
E: SUBSYSTEM=usb
E: TAGS=:uaccess:seat:udev-acl:
E: TYPE=255/255/0
E: USEC_INITIALIZED=19197013
Docker: udevadm info -q all /dev/mcc_daq
P: /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
N: bus/usb/001/004
E: BUSNUM=001
E: DEVNAME=/dev/bus/usb/001/004
E: DEVNUM=004
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
E: DEVTYPE=usb_device
E: DRIVER=usb
E: MAJOR=189
E: MINOR=3
E: PRODUCT=9db/fe/101
E: SUBSYSTEM=usb
E: TYPE=255/255/0
Docker: udevadm info -q all /dev/bus/usb/001/004
P: /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
N: bus/usb/001/004
E: BUSNUM=001
E: DEVNAME=/dev/bus/usb/001/004
E: DEVNUM=004
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8.2
E: DEVTYPE=usb_device
E: DRIVER=usb
E: MAJOR=189
E: MINOR=3
E: PRODUCT=9db/fe/101
E: SUBSYSTEM=usb
E: TYPE=255/255/0
From this discussion I concluded that if your docker run
command is how you actually use it, you can do a workaround using the bash subsition below helped by the tool 'readlink':
docker run --rm -it \
--device=$(readlink -f /dev/mcc_daq) \
mcc_daq1
However if you use docker-compose like me, this workaround get's slightly more awkward because you would need to do some preparatory juggling with environment variables I guess.
One of the problems with this workaround is of course that if you replug your device the path will be invalid. So you would have to restart the container I guess..