Access usb camera on torizon as a non-root user

Dear Jeremias,

I manage to install drivers and libraries for different USB camera (from NIT New Imaging Technology in France, and IDS Imaging) on Torizon.

I am able to access the camera as root user (user: 0:0 in docker-compose.yml) but not as user torizon.

I would like to access it as non-root user (mainly because I would like to display on weston, and that does not work as root, and xhost in not available on the target).

The docker-compose.yml I use is the following (so here without the user: 0:0 option) :

# docker-compose.yml
services:
  nit-interface_arm64v8-qt5-vivante-no-ssh_bullseye_debug_513050ae-f541-4a8a-baef-1599a9e43911:
    cap_add:
    - ALL
    depends_on:
    - weston
    device_cgroup_rules:
    - c 4:0 rmw
    - c 4:7 rmw
    - c 13:* rmw
    - c 189:* rmw
    - c 81:* rmw
    - c 245:* rmw
    - c 199:* rmw
    - c 226:* rmw
    devices:
    - /dev/bus/usb/002/002
    - /dev/bus/usb/002/001
    - /dev/bus/usb/001/001
    - /dev/bus/usb/003/003
    - /dev/bus/usb/003/002
    - /dev/bus/usb/003/001
    image: nit-interface_arm64v8-qt5-vivante-no-ssh_bullseye_debug_513050ae-f541-4a8a-baef-1599a9e43911
    network_mode: host
    ports:
    - 6502/tcp
    privileged: 'true'
    volumes:
    - /tmp:/tmp:rw
    - /dev/dri:/dev/dri:rw
    - /dev/galcore:/dev/galcore:rw
    - /dev:/dev:rw
    - /dev/bus:/dev/bus:rw
    - /run:/run:rw
    - /home/torizon/nit-interface:/nit-interface:rw
  weston:
    cap_add:
    - CAP_SYS_TTY_CONFIG
    device_cgroup_rules:
    - c 4:0 rmw
    - c 4:7 rmw
    - c 13:* rmw
    - c 199:* rmw
    - c 226:* rmw
    environment:
    - ACCEPT_FSL_EULA=1
    image: torizon/weston-vivante:2
    network_mode: host
    volumes:
    - source: /tmp
      target: /tmp
      type: bind
    - source: /dev
      target: /dev
      type: bind
    - source: /run/udev
      target: /run/udev
      type: bind
version: '2.4'

I gathered various info that might be useful for debugging (here with NIT camera) :

The camera uses Cypress drivers and the following udev rule was added on the target container

## cat /etc/udev/rules.d/88-cyusb.rules 
KERNEL=="*", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04b4", MODE="0666", GROUP="plugdev", TAG="cyusb_dev"

The camera on the target container shows as follows with lsusb:

## lsusb -v -s 002:002
Bus 002 Device 002: ID 04b4:00f1 Cypress Semiconductor Corp. USB3
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               3.00
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         9
  idVendor           0x04b4 Cypress Semiconductor Corp.
  idProduct          0x00f1 
  bcdDevice            0.00
  iManufacturer           1 New Imaging Technologies
  iProduct                2 USB3
  iSerial                 3 v002.000.001
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x001f
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              400mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass      0 
      bInterfaceProtocol      0 
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0400  1x 1024 bytes
        bInterval               0
        bMaxBurst              15

When ran as root, (-u:0:0), lsub show a bit more information :

...
Binary Object Store Descriptor:
  bLength                 5
  bDescriptorType        15
  wTotalLength       0x0016
  bNumDeviceCaps          2
  USB 2.0 Extension Device Capability:
    bLength                 7
    bDescriptorType        16
    bDevCapabilityType      2
    bmAttributes   0x00000002
      HIRD Link Power Management (LPM) Supported
  SuperSpeed USB Device Capability:
    bLength                10
    bDescriptorType        16
    bDevCapabilityType      3
    bmAttributes         0x00
    wSpeedsSupported   0x000e
      Device can operate at Full Speed (12Mbps)
      Device can operate at High Speed (480Mbps)
      Device can operate at SuperSpeed (5Gbps)
    bFunctionalitySupport   3
      Lowest fully-functional device speed is SuperSpeed (5Gbps)
    bU1DevExitLat           0 micro seconds
    bU2DevExitLat           0 micro seconds
can't get debug descriptor: Resource temporarily unavailable
Device Status:     0x0000
  (Bus Powered)

I also added the plugdev to the torizon user on the target container

## groups
torizon dialout audio video plugdev gpio i2cdev spidev pwm input kvm render

When I launch my application with strace on the target container, I got the error that it cannot open /dev/bus/usb/002/... ;

## strace ./nit-cam-imx8-bullseye
...
openat(AT_FDCWD, "/sys/bus/usb/devices/usb2/busnum", O_RDONLY|O_CLOEXEC) = 5
read(5, "2\n", 20)                      = 2
close(5)                                = 0
openat(AT_FDCWD, "/sys/bus/usb/devices/usb2/devnum", O_RDONLY|O_CLOEXEC) = 5
read(5, "1\n", 20)                      = 2
close(5)                                = 0
openat(AT_FDCWD, "/sys/bus/usb/devices/usb2/speed", O_RDONLY|O_CLOEXEC) = 5
read(5, "5000\n", 20)                   = 5
close(5)                                = 0
openat(AT_FDCWD, "/sys/bus/usb/devices/usb2/descriptors", O_RDONLY|O_CLOEXEC) = 5
read(5, "\22\1\0\3\t\0\3\tk\35\3\0\4\5\3\2\1\1\t\2\37\0\1\1\0\340\0\t\4\0\0\1"..., 256) = 49
close(5)                                = 0
openat(AT_FDCWD, "/", O_RDONLY|O_CLOEXEC|O_PATH|O_DIRECTORY) = 5
openat(5, "sys", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 6
fstat(6, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
close(5)                                = 0
openat(6, "devices", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 5
fstat(5, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(6)                                = 0
openat(5, "platform", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 6
fstat(6, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(5)                                = 0
openat(6, "bus@5b000000", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 5
fstat(5, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(6)                                = 0
openat(5, "5b110000.usb3", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 6
fstat(6, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(5)                                = 0
openat(6, "xhci-cdns3", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 5
fstat(5, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(6)                                = 0
openat(5, "usb2", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 6
fstat(6, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(5)                                = 0
openat(6, "2-1", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH) = 5
fstat(5, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
close(6)                                = 0
close(5)                                = 0
faccessat(AT_FDCWD, "/sys/devices/platform/bus@5b000000/5b110000.usb3/xhci-cdns3/usb2/2-1/uevent", F_OK) = 0
openat(AT_FDCWD, "/sys/devices/platform/bus@5b000000/5b110000.usb3/xhci-cdns3/usb2/2-1/uevent", O_RDONLY|O_CLOEXEC) = 5
fstat(5, {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0
fstat(5, {st_mode=S_IFREG|0644, st_size=4096, ...}) = 0
read(5, "MAJOR=189\nMINOR=129\nDEVNAME=bus/"..., 4096) = 124
read(5, "", 4096)                       = 0
close(5)                                = 0
getrandom(0xffffb39b6938, 16, GRND_NONBLOCK|GRND_INSECURE) = -1 EINVAL (Invalid argument)
getrandom("\x9e\xc1\x80\x6f\x53\x5a\xee\x21\x37\x0c\x4e\x80\xf2\xd5\xd7\x3a", 16, GRND_NONBLOCK) = 16
getrandom(0xffffb39b6938, 16, GRND_NONBLOCK|GRND_INSECURE) = -1 EINVAL (Invalid argument)
getrandom("\x6a\x5c\xe1\xd7\x59\x6d\x4c\xa1\x10\x0a\x5a\xa4\xbf\x85\x3f\x53", 16, GRND_NONBLOCK) = 16
openat(AT_FDCWD, "/sys/bus/usb/devices/2-1/busnum", O_RDONLY|O_CLOEXEC) = 5
read(5, "2\n", 20)                      = 2
close(5)                                = 0
openat(AT_FDCWD, "/sys/bus/usb/devices/2-1/devnum", O_RDONLY|O_CLOEXEC) = 5
read(5, "2\n", 20)                      = 2
close(5)                                = 0
openat(AT_FDCWD, "/sys/bus/usb/devices/2-1/speed", O_RDONLY|O_CLOEXEC) = 5
read(5, "5000\n", 20)                   = 5
close(5)                                = 0
openat(AT_FDCWD, "/sys/bus/usb/devices/2-1/descriptors", O_RDONLY|O_CLOEXEC) = 5
read(5, "\22\1\0\3\0\0\0\t\264\4\361\0\0\0\1\2\3\1\t\2\37\0\1\1\0\2002\t\4\0\0\1"..., 256) = 49
close(5)                                = 0
eventfd2(0, EFD_CLOEXEC|EFD_NONBLOCK)   = 5
write(5, "\1\0\0\0\0\0\0\0", 8)         = 8
timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC|TFD_NONBLOCK) = 6
recvmsg(3, {msg_namelen=128}, 0)        = -1 EAGAIN (Resource temporarily unavailable)
openat(AT_FDCWD, "/dev/bus/usb/002/002", O_RDWR|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/002/001", O_RDWR|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/001/001", O_RDWR|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/003/003", O_RDWR|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/003/002", O_RDWR|O_CLOEXEC) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/dev/bus/usb/003/001", O_RDWR|O_CLOEXEC) = -1 EACCES (Permission denied)
write(1, "No NIT camera was discovered\n", 29No NIT camera was discovered

So this is a permission issue but I am puzzled because I tried many possibilities. To summarize :

  • adding various options to docker-compose.yml
    • cap_add: - ALL
    • device_cgroup_rules:- c 189:* rmw - etc …
    • devices: - /dev/bus/usb/002/002 - etc…
    • volumes: /dev/, /run, /tmp
    • privileged : 'true'
    • volumes: - /dev:/dev rw - etc ...
  • adding plugdev group to torizon

As I mentioned, it works fine as root user, but if I run as root, then I cannot display anything on weston any more.

Any idea to solve this issue ?

Best regards,
Fabian

Greetings @fdortu,

I have a basic USB webcam so I was able to perform some basic tests. Though of course this won’t be a 1 to 1 recreation of your setup, it will allow me to try some things at least.

With my simple Logitech USB camera I observed that it creates a /dev/video2 and /dev/video3 entries. Though I did not need any special drivers/libraries, or udev rules for this camera.

Next so I spun up a Weston + Chromium Kiosk container as described here: Web Browser / Kiosk Mode with Torizon OS | Toradex Developer Center

The only changes I did was I added bind-mounts for /dev/video2 and /dev/video3 to the Chromium Kiosk container. I then directed the web browser to: https://webcamtests.com/

From there it was able to access my USB camera without issues. I also double-checked to make sure that the Chromium process was being ran by the torizon user and not the root user.

With this experiment it appears to be possible to generally access a USB camera as a non-root user. However, the question now is why this doesn’t just work on your setup.

The camera uses Cypress drivers and the following udev rule was added on the target container

Is this udev rule required? Also you said you’re adding this “on the target container”. Do you mean this is inside the container filesystem and not on the host? Is there a reason for that?

Without knowing much else about your setup, some suggestions I have are to try forcibly changing the /dev/bus/usb/* permissions inside the container. Though this may not be foolproof. Another idea would be to modify your udev rule to only have GROUP but not MODE. I’ve seen scenarios in the past where having both specified can cause weird permission issues where even if a user is part of the appropriate group they can’t access the device.

Best Regards,
Jeremias

Hi Jeremias,

Thanks for your suggestion, which have helped a lot (see at the end of the message which fails or succeed).

But first for completeness of information, the udev rule I added is actually documented by the camera manufacturer, which provides the udev rule and a libNITlibrary.so (+ headers) as a deb 9 package. I had to strip the deb archive to known exactly how to install it on Debian 11 and build some specific versions of the dependencies.

The debian postinst script is as follows :

#!/bin/sh

if [ ! -f /etc/udev/rules.d/88-cyusb.rules ]; then
        echo 'KERNEL=="*", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04b4", MODE="0666", GROUP="plugdev", TAG="cyusb_dev"' > /e
tc/udev/rules.d/88-cyusb.rules
fi
udevadm control --reload-rules && udevadm trigger
ln -sf /usr/local/lib/libNITLibrary.so.3.0.1 /usr/local/lib/libNITLibrary.so.3
ln -sf /usr/local/lib/libNITLibrary.so.3.0.1 /usr/local/lib/libNITLibrary.so
echo /usr/local/lib > /etc/ld.so.conf.d/local.conf
/sbin/ldconfig

I did not use udevadm control --reload-rules && udevadm trigger, because it does not seem to be needed (udev seems to detect the camera and trigger the rule, it is just the device access in user space that does not work).

An indeed I added the udev rule at the Dockerfile level (in torizon.buildcommand). in order to store the commands permanently in the philosophy of infrastructure as code, and because I did not dive yet in customizing TorizonCore. This is clearly not the way to go.

So I did try your different suggestions with some failures and successes :

/dev/video* :o:

My camera does not seem to create any /dev/video (/dev/video0, /dev/video1, /dev/video12, /dev/video13 pre-exists), but I did try to add them as --volume /dev/video0:/dev/video0, etc. but that still does not help to detect the camera.

chown :white_check_mark:

Changing the permission of /deb/bus/usb/* to torizon.torizon works but the effect disappears after reboot.

udev rule at container level :o:

I did try :

  • removing MODE="0666" in the udev rule, does not help
  • adding OWNER="torizon", since I guess it should have the same effect as chown but that does not seem to work

udev rule at TorizonCore level :white_check_mark:

Adding the udev rule at the TorizonCore level instead of the container works !

Then the usb device authomatically shows as torizon.plugdev

ls -l /dev/bus/usb/002/002
crw-rw-rw- 1 torizon plugdev 189, 129 Jun  3 09:10 /dev/bus/usb/002/002

So clearly this solution is the way to go. Thanks.

Best regards,
Fabian

Glad to see you were able to find a way around this issue. For future reference, when it comes to hardware/peripheral configuration I would usually recommend these to live outside of the container. Like your udev rule for example.

While these can work inside a container the behavior can be strange and hard to debug in such situations.

Best Regards,
Jeremias