USB Gadget Testing on Debian

This guide describes how to configure a Debian host so that the integration suite can exercise libusb-uvc against a virtual camera exposed through the USB FunctionFS interface. The commands assume root privileges.

Prerequisites

  • Debian 12 (Bookworm) or later with a kernel that provides dummy_hcd and libcomposite.

  • python3 and pytest installed for running the test-suite.

Install helper packages:

sudo apt-get update
sudo apt-get install python3-pip python3-pytest linux-headers-$(uname -r)

Load the gadget modules:

sudo modprobe dummy_hcd
sudo modprobe libcomposite

The dummy_hcd module creates a virtual USB controller (visible under /sys/class/udc) that we can bind the gadget to; libcomposite enables the ConfigFS API used below.

Create the gadget skeleton

  1. Mount configfs (usually done by systemd; no-op if already mounted):

    sudo mount -t configfs none /sys/kernel/config
    
  2. Create a new gadget and populate the mandatory string descriptors:

    sudo mkdir -p /sys/kernel/config/usb_gadget/pyuvc
    cd /sys/kernel/config/usb_gadget/pyuvc
    echo 0x1234 | sudo tee idVendor
    echo 0x5678 | sudo tee idProduct
    sudo mkdir -p strings/0x409
    echo "PyUVC" | sudo tee strings/0x409/manufacturer
    echo "PyUVC Virtual Camera" | sudo tee strings/0x409/product
    echo "0001" | sudo tee strings/0x409/serialnumber
    

    The vendor/product IDs should match the ones defined in the JSON profile used by the emulator (tests/data/sample_camera_profile.json by default).

  3. Define a configuration and attach a FunctionFS instance:

    sudo mkdir -p configs/c.1/strings/0x409
    echo "PyUVC Config" | sudo tee configs/c.1/strings/0x409/configuration
    sudo mkdir -p configs/c.1/ffs.uvc
    sudo mkdir -p functions/ffs.uvc
    sudo ln -s functions/ffs.uvc configs/c.1/
    
  4. Prepare the FunctionFS mount point expected by the test-suite:

    sudo mkdir -p /dev/ffs/uvc
    sudo mount -t functionfs ffs-uvc /dev/ffs/uvc
    

    The user-space daemon (tests/gadget_daemon.py) will push the UVC descriptors to /dev/ffs/uvc/ep0 when it starts.

  5. Bind the gadget to the virtual controller:

    ls /sys/class/udc
    echo dummy_udc.0 | sudo tee UDC
    

    Replace dummy_udc.0 with the actual name printed by ls if it differs.

Running the daemon and tests

  1. In one terminal, launch the gadget daemon (requires write access to /dev/ffs/uvc):

    sudo python3 tests/gadget_daemon.py tests/data/sample_camera_profile.json \
         --mountpoint /dev/ffs/uvc --log-level INFO
    

    The daemon parses the JSON profile, writes the FunctionFS descriptors and responds to all control/streaming requests using the shared tests.uvc_emulator.UvcEmulatorLogic implementation.

  2. In another terminal, enable the integration tests and run them:

    export LIBUSB_UVC_ENABLE_GADGET_TESTS=1
    python3 -m pytest tests/test_integration.py
    

    The fixture waits for a device with the profile’s VID/PID to appear via libusb, enumerates the synthetic controls, then captures an MJPEG frame.

Cleanup

  1. Stop the daemon (Ctrl+C) and unbind the gadget:

    echo "" | sudo tee /sys/kernel/config/usb_gadget/pyuvc/UDC
    
  2. Unmount FunctionFS and remove the gadget directories:

    sudo umount /dev/ffs/uvc
    sudo rm -rf /sys/kernel/config/usb_gadget/pyuvc
    
  3. Optionally unload the gadget modules:

    sudo modprobe -r dummy_hcd libcomposite
    

Troubleshooting

  • If /dev/ffs/uvc fails to mount, ensure the functionfs kernel module is available (sudo modprobe functionfs).

  • Running the daemon without root privileges typically results in Permission denied errors when accessing /dev/ffs/uvc.

  • The integration tests automatically skip when LIBUSB_UVC_ENABLE_GADGET_TESTS is unset or when the gadget fails to appear within 10 seconds.