drivers/usbdev: add UVC gadget class driver

Add USB Video Class 1.1 gadget driver supporting Bulk transport
with uncompressed YUY2 video streaming. Resolution and frame
interval are negotiated dynamically via PROBE/COMMIT control.

- uvc.h: protocol constants, streaming control struct, public API
- uvc.c: class driver with PROBE/COMMIT, bulk EP, /dev/uvc0 chardev
- Kconfig/Make.defs: USBUVC config and build rules
- boardctl.c: BOARDIOC_USBDEV_UVC standalone init path

Hardened against host disconnect:
- Removed nxmutex_lock from USB interrupt context paths
- Added 30s semaphore timeout in uvc_write with EP_CANCEL fallback
- Drain stale wrsem counts in VS_COMMIT before new stream
- Guard uvc_streaming_stop() against double EP_CANCEL race
- Handle EP_SUBMIT returning -ESHUTDOWN gracefully

Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
This commit is contained in:
wangjianyu3 2026-03-22 10:00:00 +08:00 committed by Alan C. Assis
parent 99afb5ee34
commit 4775b36316
9 changed files with 2170 additions and 0 deletions

View file

@ -44,6 +44,7 @@ following section.
syslog.rst
sdio.rst
usbdev.rst
uvc.rst
usbhost.rst
usbmisc.rst
usbmonitor.rst

View file

@ -0,0 +1,127 @@
======================================
USB Video Class (UVC) Gadget Driver
======================================
Overview
========
The UVC gadget driver (``drivers/usbdev/uvc.c``) implements a USB Video Class
1.1 device that makes NuttX appear as a USB webcam to the host. The driver
exposes a character device (``/dev/uvc0``) that applications write video frames
to. It handles all UVC class-specific control requests (PROBE / COMMIT)
internally and uses bulk transfers for video data.
The driver supports two modes:
- **Standalone** the UVC function is the only USB class on the bus.
- **Composite** the UVC function is combined with other USB class drivers
(e.g. CDC/ACM) via the NuttX composite device framework.
Features
========
- UVC 1.1 compliant (uncompressed YUY2 format)
- Bulk transfer mode for video data
- Automatic PROBE / COMMIT negotiation with the host
- ``poll()`` support applications can wait for the host to start streaming
(``POLLOUT``) and detect disconnection (``POLLHUP``)
- Runtime video parameters resolution and frame rate are passed at
initialization time, so USB descriptors always match the actual sensor
- ``boardctl()`` integration for easy application-level bring-up
Configuration
=============
The driver is enabled through the following Kconfig options:
.. code-block:: kconfig
CONFIG_USBUVC=y # Enable UVC gadget support
CONFIG_USBUVC_COMPOSITE=n # Set y for composite device mode
CONFIG_USBUVC_EPBULKIN=1 # Bulk IN endpoint number (standalone)
CONFIG_USBUVC_EP0MAXPACKET=64 # EP0 max packet size (standalone)
CONFIG_USBUVC_EPBULKIN_FSSIZE=64 # Bulk IN full-speed max packet size
CONFIG_USBUVC_NWRREQS=4 # Number of pre-allocated write requests
CONFIG_USBUVC_NPOLLWAITERS=2 # Number of poll waiters
Header Files
============
- ``include/nuttx/usb/uvc.h`` Public API, UVC descriptor constants, and
data structures.
Data Structures
===============
``struct uvc_params_s``
-----------------------
Passed to the initialization function so that USB descriptors reflect the
actual sensor capabilities::
struct uvc_params_s
{
uint16_t width; /* Frame width in pixels */
uint16_t height; /* Frame height in pixels */
uint8_t fps; /* Frames per second */
};
Public Interfaces
=================
Standalone Mode
---------------
``usbdev_uvc_initialize()``
Initialize the UVC gadget and register ``/dev/uvc0``. *params* may be
``NULL`` to use defaults (320 × 240 @ 5 fps). Returns a handle for later
``usbdev_uvc_uninitialize()``.
``usbdev_uvc_uninitialize()``
Tear down the UVC gadget and unregister the character device.
Composite Mode
--------------
``usbdev_uvc_classobject()``
Create a UVC class driver instance for use inside a composite device.
``usbdev_uvc_classuninitialize()``
Destroy a class driver instance created by ``usbdev_uvc_classobject()``.
``usbdev_uvc_get_composite_devdesc()``
Fill a ``composite_devdesc_s`` for the composite framework.
boardctl Integration
--------------------
Applications can manage the UVC gadget through ``boardctl()``::
struct boardioc_usbdev_ctrl_s ctrl;
struct uvc_params_s params = { .width = 320, .height = 240, .fps = 15 };
FAR void *handle = (FAR void *)&params;
ctrl.usbdev = BOARDIOC_USBDEV_UVC;
ctrl.action = BOARDIOC_USBDEV_CONNECT;
ctrl.instance = 0;
ctrl.config = 0;
ctrl.handle = &handle;
boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl);
/* ... use /dev/uvc0 ... */
ctrl.action = BOARDIOC_USBDEV_DISCONNECT;
boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl);
Device Operation
================
1. The application opens ``/dev/uvc0`` for writing.
2. Use ``poll()`` with ``POLLOUT`` to wait for the USB host to start streaming
(i.e. the host sends a VS_COMMIT_CONTROL SET_CUR request).
3. Write complete video frames via ``write()``. The driver prepends a 2-byte
UVC payload header (with FID / EOF bits) automatically.
4. When the host stops streaming, ``write()`` returns ``-EAGAIN``. The
application can ``poll()`` again to wait for the host to restart.
5. ``POLLHUP`` is reported when the host explicitly stops streaming.

View file

@ -56,6 +56,7 @@
# include <nuttx/usb/cdcacm.h>
# include <nuttx/usb/pl2303.h>
# include <nuttx/usb/usbmsc.h>
# include <nuttx/usb/uvc.h>
# include <nuttx/usb/composite.h>
#endif
@ -206,6 +207,44 @@ static inline int
break;
#endif
#if defined(CONFIG_USBUVC) && !defined(CONFIG_USBUVC_COMPOSITE)
case BOARDIOC_USBDEV_UVC: /* UVC class */
switch (ctrl->action)
{
case BOARDIOC_USBDEV_INITIALIZE: /* Initialize UVC device */
break;
case BOARDIOC_USBDEV_CONNECT: /* Connect the UVC device */
{
FAR const struct uvc_params_s *params;
DEBUGASSERT(ctrl->handle != NULL);
/* Application passes video params via *handle */
params = (FAR const struct uvc_params_s *)*ctrl->handle;
*ctrl->handle = usbdev_uvc_initialize(params);
if (*ctrl->handle == NULL)
{
ret = -EIO;
}
}
break;
case BOARDIOC_USBDEV_DISCONNECT: /* Disconnect the UVC device */
{
DEBUGASSERT(ctrl->handle != NULL && *ctrl->handle != NULL);
usbdev_uvc_uninitialize(*ctrl->handle);
}
break;
default:
ret = -EINVAL;
break;
}
break;
#endif
#ifdef CONFIG_USBDEV_COMPOSITE
case BOARDIOC_USBDEV_COMPOSITE: /* Composite device */
switch (ctrl->action)

View file

@ -61,6 +61,10 @@ if(CONFIG_USBDEV)
list(APPEND SRCS mtp.c)
endif()
if(CONFIG_USBUVC)
list(APPEND SRCS uvc.c)
endif()
if(CONFIG_NET_CDCECM)
list(APPEND SRCS cdcecm.c)
endif()

View file

@ -948,6 +948,51 @@ config USBADB_INTERFACESTR
endif # USBADB
menuconfig USBUVC
bool "USB Video Class (UVC) gadget support"
default n
depends on USBDEV
---help---
Enables USB Video Class (UVC) gadget device support.
The device appears as a USB webcam to the host.
if USBUVC
config USBUVC_COMPOSITE
bool "UVC composite support"
default n
depends on USBDEV_COMPOSITE
---help---
Configure the UVC driver as part of a composite USB device.
When enabled, the UVC gadget can be combined with other USB
class drivers (e.g., CDC/ACM) in a single composite device.
if !USBUVC_COMPOSITE
config USBUVC_EPBULKIN
int "Bulk IN endpoint number"
default 1
config USBUVC_EP0MAXPACKET
int "Max packet size for EP0"
default 64
endif # !USBUVC_COMPOSITE
config USBUVC_EPBULKIN_FSSIZE
int "Bulk IN full-speed max packet size"
default 64
config USBUVC_NWRREQS
int "Number of write requests"
default 4
config USBUVC_NPOLLWAITERS
int "Number of poll waiters"
default 2
endif # USBUVC
menuconfig USBMSC
bool "USB Mass storage class device"
default n

View file

@ -56,6 +56,10 @@ ifeq ($(CONFIG_USBADB),y)
CSRCS += adb.c
endif
ifeq ($(CONFIG_USBUVC),y)
CSRCS += uvc.c
endif
ifeq ($(CONFIG_USBMTP),y)
CSRCS += mtp.c
endif

1665
drivers/usbdev/uvc.c Normal file

File diff suppressed because it is too large Load diff

282
include/nuttx/usb/uvc.h Normal file
View file

@ -0,0 +1,282 @@
/****************************************************************************
* include/nuttx/usb/uvc.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_USB_UVC_H
#define __INCLUDE_NUTTX_USB_UVC_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/usb/usbdev.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* UVC device descriptor counts - used by both standalone and composite */
#define USBUVC_NINTERFACES 2 /* VC + VS */
#define USBUVC_NUM_EPS 1 /* Bulk IN only */
#define USBUVC_EP_BULKIN_IDX 0
#define USBUVC_NSTRIDS 2 /* VC iInterface + VS iInterface */
#define USBUVC_NCONFIGS 1
/* UVC Class-Specific Descriptor Types (USB Video Class 1.1, Table A-4) */
#define UVC_CS_UNDEFINED 0x20
#define UVC_CS_DEVICE 0x21
#define UVC_CS_CONFIGURATION 0x22
#define UVC_CS_STRING 0x23
#define UVC_CS_INTERFACE 0x24
#define UVC_CS_ENDPOINT 0x25
/* UVC Interface Subclass Codes (Table A-2) */
#define UVC_SC_UNDEFINED 0x00
#define UVC_SC_VIDEOCONTROL 0x01
#define UVC_SC_VIDEOSTREAMING 0x02
#define UVC_SC_VIDEO_INTERFACE_COLLECTION 0x03
/* UVC Interface Protocol Codes (Table A-3) */
#define UVC_PC_PROTOCOL_UNDEFINED 0x00
#define UVC_PC_PROTOCOL_15 0x01
/* UVC VideoControl Interface Descriptor Subtypes (Table A-5) */
#define UVC_VC_DESCRIPTOR_UNDEFINED 0x00
#define UVC_VC_HEADER 0x01
#define UVC_VC_INPUT_TERMINAL 0x02
#define UVC_VC_OUTPUT_TERMINAL 0x03
#define UVC_VC_SELECTOR_UNIT 0x04
#define UVC_VC_PROCESSING_UNIT 0x05
#define UVC_VC_EXTENSION_UNIT 0x06
/* UVC VideoStreaming Interface Descriptor Subtypes (Table A-6) */
#define UVC_VS_UNDEFINED 0x00
#define UVC_VS_INPUT_HEADER 0x01
#define UVC_VS_OUTPUT_HEADER 0x02
#define UVC_VS_STILL_IMAGE_FRAME 0x03
#define UVC_VS_FORMAT_UNCOMPRESSED 0x04
#define UVC_VS_FRAME_UNCOMPRESSED 0x05
#define UVC_VS_FORMAT_MJPEG 0x06
#define UVC_VS_FRAME_MJPEG 0x07
#define UVC_VS_COLOR_FORMAT 0x0d
/* UVC Terminal Types (Table B-1, B-2) */
#define UVC_TT_VENDOR_SPECIFIC 0x0100
#define UVC_TT_STREAMING 0x0101
#define UVC_ITT_VENDOR_SPECIFIC 0x0200
#define UVC_ITT_CAMERA 0x0201
#define UVC_ITT_MEDIA_TRANSPORT_INPUT 0x0202
#define UVC_OTT_VENDOR_SPECIFIC 0x0300
#define UVC_OTT_DISPLAY 0x0301
#define UVC_OTT_MEDIA_TRANSPORT_OUTPUT 0x0302
/* UVC Class-Specific Request Codes (Table A-8) */
#define UVC_RC_UNDEFINED 0x00
#define UVC_SET_CUR 0x01
#define UVC_GET_CUR 0x81
#define UVC_GET_MIN 0x82
#define UVC_GET_MAX 0x83
#define UVC_GET_RES 0x84
#define UVC_GET_LEN 0x85
#define UVC_GET_INFO 0x86
#define UVC_GET_DEF 0x87
/* UVC VideoStreaming Interface Control Selectors (Table A-11) */
#define UVC_VS_CONTROL_UNDEFINED 0x00
#define UVC_VS_PROBE_CONTROL 0x01
#define UVC_VS_COMMIT_CONTROL 0x02
/* UVC Payload Header bits (Table 2-6) */
#define UVC_STREAM_FID (1 << 0)
#define UVC_STREAM_EOF (1 << 1)
#define UVC_STREAM_PTS (1 << 2)
#define UVC_STREAM_SCR (1 << 3)
#define UVC_STREAM_RES (1 << 4)
#define UVC_STREAM_STI (1 << 5)
#define UVC_STREAM_ERR (1 << 6)
#define UVC_STREAM_EOH (1 << 7)
/* UVC Payload Header Length (minimum, no PTS/SCR) */
#define UVC_PAYLOAD_HEADER_LEN 2
/* GUID for YUY2 (MEDIASUBTYPE_YUY2) — we use this for uncompressed
* Note: For RGB565 there is no standard UVC GUID. We advertise YUY2
* and do a trivial RGB565YUY2 conversion in the app, OR we define
* a custom GUID. For Phase 1, we use YUY2 which is universally supported.
*/
#define UVC_GUID_FORMAT_YUY2 \
{ 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
/****************************************************************************
* Public Types
****************************************************************************/
/* UVC Video Probe and Commit Controls (Table 4-75) */
begin_packed_struct struct uvc_streaming_control_s
{
uint16_t bmhint;
uint8_t bformatindex;
uint8_t bframeindex;
uint32_t dwframeinterval;
uint16_t wkeyframerate;
uint16_t wpframerate;
uint16_t wcompquality;
uint16_t wcompwindowsize;
uint16_t wdelay;
uint32_t dwmaxvideoframesize;
uint32_t dwmaxpayloadtransfersize;
uint32_t dwclockfrequency;
uint8_t bmframinginfo;
uint8_t bpreferedversion;
uint8_t bminversion;
uint8_t bmaxversion;
} end_packed_struct;
#define UVC_PROBE_COMMIT_SIZE sizeof(struct uvc_streaming_control_s)
/* UVC gadget initialization parameters.
* The application queries the video device at runtime and passes
* these to usbdev_uvc_initialize() so that USB descriptors match
* the actual sensor capabilities.
*/
struct uvc_params_s
{
uint16_t width; /* Frame width in pixels */
uint16_t height; /* Frame height in pixels */
uint8_t fps; /* Frames per second */
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#undef EXTERN
#if defined(__cplusplus)
# define EXTERN extern "C"
extern "C"
{
#else
# define EXTERN extern
#endif
/****************************************************************************
* Name: usbdev_uvc_initialize
*
* Description:
* Initialize the USB Video Class device driver.
*
* Input Parameters:
* params - Video parameters (width, height, fps) queried from the
* camera sensor. If NULL, defaults to 320x240 @ 5fps.
*
* Returned Value:
* A non-NULL "handle" is returned on success.
*
****************************************************************************/
FAR void *usbdev_uvc_initialize(FAR const struct uvc_params_s *params);
/****************************************************************************
* Name: usbdev_uvc_uninitialize
*
* Description:
* Uninitialize the USB Video Class device driver.
*
****************************************************************************/
void usbdev_uvc_uninitialize(FAR void *handle);
/****************************************************************************
* Name: usbdev_uvc_classobject
*
* Description:
* Create a UVC class driver instance for composite device usage.
* Called by the composite framework (or board code) to instantiate
* the UVC class driver with assigned interface/string/endpoint bases.
*
* Input Parameters:
* minor - Device minor number (unused, pass 0).
* devinfo - USB device info with ifnobase, strbase, epno[] assigned
* by the composite framework.
* params - Video parameters (width, height, fps). NULL = defaults.
* classdev - Location to return the class driver instance.
*
* Returned Value:
* OK on success; negative errno on failure.
*
****************************************************************************/
int usbdev_uvc_classobject(int minor,
FAR struct usbdev_devinfo_s *devinfo,
FAR const struct uvc_params_s *params,
FAR struct usbdevclass_driver_s **classdev);
/****************************************************************************
* Name: usbdev_uvc_classuninitialize
*
* Description:
* Uninitialize a UVC class driver instance created by classobject.
*
****************************************************************************/
void usbdev_uvc_classuninitialize(
FAR struct usbdevclass_driver_s *classdev);
/****************************************************************************
* Name: usbdev_uvc_get_composite_devdesc
*
* Description:
* Fill in a composite_devdesc_s structure for the UVC gadget.
* Board code calls this, then sets ifnobase/strbase/epno[] before
* passing to composite_initialize().
*
* Note: classobject/uninitialize are left NULL because UVC needs
* extra params (uvc_params_s). Board code must set them.
*
****************************************************************************/
#ifdef CONFIG_USBDEV_COMPOSITE
void usbdev_uvc_get_composite_devdesc(
FAR struct composite_devdesc_s *dev);
#endif
#undef EXTERN
#if defined(__cplusplus)
}
#endif
#endif /* __INCLUDE_NUTTX_USB_UVC_H */

View file

@ -373,6 +373,9 @@ enum boardioc_usbdev_identifier_e
#ifdef CONFIG_USBMSC
, BOARDIOC_USBDEV_MSC /* Mass storage class */
#endif
#ifdef CONFIG_USBUVC
, BOARDIOC_USBDEV_UVC /* USB Video Class */
#endif
#ifdef CONFIG_USBDEV_COMPOSITE
, BOARDIOC_USBDEV_COMPOSITE /* Composite device */
#endif