USB host UVC class

This commit is contained in:
Martin Valik 2022-08-01 11:24:56 +02:00
parent a83313a4ca
commit 768f84b2a4
15 changed files with 2573 additions and 2 deletions

View File

@ -15,6 +15,6 @@ jobs:
- name: Upload components to component service
uses: espressif/upload-components-ci-action@v1
with:
directories: "bdc_motor;cbor;jsmn;led_strip;libsodium;pid_ctrl;qrcode;nghttp;sh2lib;expat;esp_encrypted_img;coap;pcap;json_generator;json_parser;usb/usb_host_cdc_acm;usb/usb_host_msc"
directories: "bdc_motor;cbor;jsmn;led_strip;libsodium;pid_ctrl;qrcode;nghttp;sh2lib;expat;esp_encrypted_img;coap;pcap;json_generator;json_parser;usb/usb_host_cdc_acm;usb/usb_host_msc;usb/usb_host_uvc"
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}

3
.gitmodules vendored
View File

@ -13,3 +13,6 @@
[submodule "coap/libcoap"]
path = coap/libcoap
url = https://github.com/obgm/libcoap.git
[submodule "usb/usb_host_uvc/libuvc"]
path = usb/usb_host_uvc/libuvc
url = https://github.com/libuvc/libuvc.git

View File

@ -4,8 +4,10 @@ cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../usb_host_cdc_acm
../usb_host_msc)
../usb_host_uvc
$ENV{IDF_PATH}/examples/peripherals/usb/host/msc/components/)
# Set the components to include the tests for.
set(TEST_COMPONENTS "usb_host_cdc_acm" "usb_host_msc" CACHE STRING "List of components to test")
set(TEST_COMPONENTS "usb_host_cdc_acm" "usb_host_msc" "usb_host_uvc" CACHE STRING "List of components to test")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(usb_test_app)

View File

@ -0,0 +1,28 @@
configure_file(${CMAKE_CURRENT_LIST_DIR}/libuvc/include/libuvc/libuvc_config.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/libuvc/libuvc_config.h
@ONLY)
set(LIBUVC_SOURCES libuvc/src/ctrl.c
libuvc/src/ctrl-gen.c
libuvc/src/device.c
libuvc/src/diag.c
libuvc/src/frame.c
libuvc/src/init.c
libuvc/src/misc.c
libuvc/src/stream.c)
idf_component_register(
SRCS ${LIBUVC_SOURCES} src/descriptor.c src/libusb_adapter.c
INCLUDE_DIRS include libuvc/include
PRIV_INCLUDE_DIRS private_include
REQUIRES usb pthread)
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/libuvc/src/device.c PROPERTIES COMPILE_FLAGS -Wno-implicit-fallthrough)
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/libuvc/src/stream.c PROPERTIES COMPILE_FLAGS -Wno-unused-variable)
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/libuvc/src/diag.c PROPERTIES COMPILE_FLAGS -Wno-format)
target_compile_definitions(${COMPONENT_LIB} PRIVATE LIBUVC_NUM_TRANSFER_BUFS=4)
target_include_directories(${COMPONENT_LIB} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include/)

201
usb/usb_host_uvc/LICENCE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.

View File

@ -0,0 +1,21 @@
# USB Host UVC Driver
This directory contains USB host UVC driver based on [libuvc](https://github.com/libuvc/libuvc) library. Support for `libuvc` is achieved by implementing adapter between [libusb](https://github.com/libusb/libusb) (underling host library used by `libuvc`) and [usb_host](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html) library targeted for ESP SOCs.
## Usage
Reference [uvc_host_example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/uvc) is similar to one found in `libuvc` repository with few additions:
1. Before calling `uvc_init()`, `initialize_usb_host_lib()` has to be called in order to initialize usb host library.
2. Since `libuvc` selects highest possible `dwMaxPayloadTransferSize` by default, user has to manually overwrite obatained value to 512 bytes (maximum transfer size supported by ESP32-S2/S3) before passing it to `uvc_print_stream_ctrl()` function.
3. Optionally, user can configure `libusb adaprer` by passing appropriate parameters to `libuvc_adapter_set_config()`.
## Known limitations
Having only Full Speed USB peripheral and hardware limited MPS (maximum packet size) to 512 bytes, ESP32-S2/S3 is capable of reading about 0.5 MB of data per second. When connected to Full Speed USB host, cameras normally provide resolution no larger than 640x480 pixels.
Following two supported formats are the most common (both encoded in MJPEG):
* 320x240 30 FPS
* 640x480 15 FPS
## Tested cameras
* Logitech C980
* CANYON CNE-CWC2

View File

@ -0,0 +1,10 @@
version: "0.0.2"
description: USB Host UVC driver
url: https://github.com/espressif/idf-extra-components/tree/master
dependencies:
idf: ">=4.4"
targets:
- esp32s2
- esp32s3

View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
UVC_DEVICE_CONNECTED = 1,
UVC_DEVICE_DISCONNECTED = 2,
} libuvc_adapter_event_t;
typedef void (*libuvc_adapter_cb_t)(libuvc_adapter_event_t);
/**
* @brief Configuration structure
*/
typedef struct {
bool create_background_task; /**< Event handling background task is created when set to true.
Otherwise, user has to handle event by calling libuvc_adapter_handle_events */
uint8_t task_priority; /**< Background task priority */
uint32_t stack_size; /**< Background task stack size */
libuvc_adapter_cb_t callback; /**< Callback notifying about connection and disconnection events */
} libuvc_adapter_config_t;
/**
* @brief Sets configuration for libuvc adapter
*
* - This function can be called prior to calling `uvc_init` function,
* otherwise default configuration will be used
*
* @param[in] config Configuration structure
*/
void libuvc_adapter_set_config(libuvc_adapter_config_t *config);
/**
* @brief Prints full configuration descriptor
*
* @param[in] device Device handle obtained from `uvc_open`
* @return esp_err_t
*/
esp_err_t libuvc_adapter_print_descriptors(uvc_device_handle_t *device);
/**
* @brief Handle USB Client events.
*
* - This function has to be called periodically, if configuration
* was provided with `create_background_task` set to `false`.
*
* @param[in] timeout_ms Timeout in miliseconds
* @return esp_err_t
*/
esp_err_t libuvc_adapter_handle_events(uint32_t timeout_ms);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "libuvc/libuvc.h"
#ifdef __cplusplus
extern "C" {
#endif
inline static char *uvc_error_string(uvc_error_t error)
{
switch (error) {
case UVC_SUCCESS: return "UVC_SUCCESS";
case UVC_ERROR_IO: return "UVC_ERROR_IO";
case UVC_ERROR_INVALID_PARAM: return "UVC_ERROR_INVALID_PARAM";
case UVC_ERROR_ACCESS: return "UVC_ERROR_ACCESS";
case UVC_ERROR_NO_DEVICE: return "UVC_ERROR_NO_DEVICE";
case UVC_ERROR_NOT_FOUND: return "UVC_ERROR_NOT_FOUND";
case UVC_ERROR_BUSY: return "UVC_ERROR_BUSY";
case UVC_ERROR_TIMEOUT: return "UVC_ERROR_TIMEOUT";
case UVC_ERROR_OVERFLOW: return "UVC_ERROR_OVERFLOW";
case UVC_ERROR_PIPE: return "UVC_ERROR_PIPE";
case UVC_ERROR_INTERRUPTED: return "UVC_ERROR_INTERRUPTED";
case UVC_ERROR_NO_MEM: return "UVC_ERROR_NO_MEM";
case UVC_ERROR_NOT_SUPPORTED: return "UVC_ERROR_NOT_SUPPORTED";
case UVC_ERROR_INVALID_DEVICE: return "UVC_ERROR_INVALID_DEVICE";
case UVC_ERROR_INVALID_MODE: return "UVC_ERROR_INVALID_MODE";
case UVC_ERROR_CALLBACK_EXISTS: return "UVC_ERROR_CALLBACK_EXISTS";
default: return "Unknown error";
}
}
#ifdef __cplusplus
}
#endif

@ -0,0 +1 @@
Subproject commit a4de53e7e265f8c6a64df7ccd289f318104e1916

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Converts raw buffer containing config descriptor into libusb_config_descriptor
*
* @note Call clear_config_descriptor when config descriptor is no longer needed.
*
* @param[in] buf buffer containing config descriptor
* @param[in] size size of buffer
* @param[out] config pointer to allocated libusb compatible config descriptor
* @return libusb_error
*/
int raw_desc_to_libusb_config(const uint8_t *buf, int size, struct libusb_config_descriptor **config);
/**
* @brief Releases memory previously allocated by config raw_desc_to_libusb_config
*
* @param[in] config pointer to allocated config descriptor
*/
void clear_config_descriptor(struct libusb_config_descriptor *config);
/**
* @brief Prints class specific descriptors
*
* @param[in] desc pointer to usb_standard_desc_t
*/
void print_usb_class_descriptors(const usb_standard_desc_t *desc);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,307 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#define LIBUSB_CALL static
#define LIBUSB_DT_DEVICE_SIZE 18
#define LIBUSB_DT_CONFIG_SIZE 9
#define LIBUSB_DT_INTERFACE_SIZE 9
#define LIBUSB_DT_ENDPOINT_SIZE 7
#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9
enum libusb_error {
LIBUSB_SUCCESS = 0,
LIBUSB_ERROR_IO = -1,
LIBUSB_ERROR_INVALID_PARAM = -2,
LIBUSB_ERROR_ACCESS = -3,
LIBUSB_ERROR_NO_DEVICE = -4,
LIBUSB_ERROR_NOT_FOUND = -5,
LIBUSB_ERROR_BUSY = -6,
LIBUSB_ERROR_TIMEOUT = -7,
LIBUSB_ERROR_OVERFLOW = -8,
LIBUSB_ERROR_PIPE = -9,
LIBUSB_ERROR_INTERRUPTED = -10,
LIBUSB_ERROR_NO_MEM = -11,
LIBUSB_ERROR_NOT_SUPPORTED = -12,
LIBUSB_ERROR_OTHER = -99
};
enum libusb_descriptor_type {
LIBUSB_DT_DEVICE = 0x01,
LIBUSB_DT_CONFIG = 0x02,
LIBUSB_DT_STRING = 0x03,
LIBUSB_DT_INTERFACE = 0x04,
LIBUSB_DT_ENDPOINT = 0x05,
LIBUSB_DT_BOS = 0x0f,
LIBUSB_DT_DEVICE_CAPABILITY = 0x10,
LIBUSB_DT_HID = 0x21,
LIBUSB_DT_REPORT = 0x22,
LIBUSB_DT_PHYSICAL = 0x23,
LIBUSB_DT_HUB = 0x29,
LIBUSB_DT_SUPERSPEED_HUB = 0x2a,
LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30
};
struct libusb_device_descriptor {
uint8_t bLength; /**< Size of the descriptor in bytes */
uint8_t bDescriptorType; /**< DEVICE Descriptor Type */
uint16_t bcdUSB; /**< USB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H) */
uint8_t bDeviceClass; /**< Class code (assigned by the USB-IF) */
uint8_t bDeviceSubClass; /**< Subclass code (assigned by the USB-IF) */
uint8_t bDeviceProtocol; /**< Protocol code (assigned by the USB-IF) */
uint8_t bMaxPacketSize0; /**< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid) */
uint16_t idVendor; /**< Vendor ID (assigned by the USB-IF) */
uint16_t idProduct; /**< Product ID (assigned by the manufacturer) */
uint16_t bcdDevice; /**< Device release number in binary-coded decimal */
uint8_t iManufacturer; /**< Index of string descriptor describing manufacturer */
uint8_t iProduct; /**< Index of string descriptor describing product */
uint8_t iSerialNumber; /**< Index of string descriptor describing the devices serial number */
uint8_t bNumConfigurations; /**< Number of possible configurations */
};
struct libusb_endpoint_descriptor {
uint8_t bLength; /**< Size of the descriptor in bytes */
uint8_t bDescriptorType; /**< ENDPOINT Descriptor Type */
uint8_t bEndpointAddress; /**< The address of the endpoint on the USB device described by this descriptor */
uint8_t bmAttributes; /**< This field describes the endpoints attributes when it is configured using the bConfigurationValue. */
uint16_t wMaxPacketSize; /**< Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected. */
uint8_t bInterval; /**< Interval for polling Isochronous and Interrupt endpoints. Expressed in frames or microframes depending on the device operating speed (1 ms for Low-Speed and Full-Speed or 125 us for USB High-Speed and above). */
uint8_t *extra;
size_t extra_length;
};
struct libusb_interface_descriptor {
uint8_t bLength; /**< Size of the descriptor in bytes */
uint8_t bDescriptorType; /**< INTERFACE Descriptor Type */
uint8_t bInterfaceNumber; /**< Number of this interface. */
uint8_t bAlternateSetting; /**< Value used to select this alternate setting for the interface identified in the prior field */
uint8_t bNumEndpoints; /**< Number of endpoints used by this interface (excluding endpoint zero). */
uint8_t bInterfaceClass; /**< Class code (assigned by the USB-IF) */
uint8_t bInterfaceSubClass; /**< Subclass code (assigned by the USB-IF) */
uint8_t bInterfaceProtocol; /**< Protocol code (assigned by the USB) */
uint8_t iInterface; /**< Index of string descriptor describing this interface */
uint8_t *extra;
size_t extra_length;
struct libusb_endpoint_descriptor *endpoint;
};
struct libusb_interface {
size_t num_altsetting;
struct libusb_interface_descriptor *altsetting;
};
struct libusb_config_descriptor {
uint8_t bLength; /**< Size of the descriptor in bytes */
uint8_t bDescriptorType; /**< CONFIGURATION Descriptor Type */
uint16_t wTotalLength; /**< Total length of data returned for this configuration */
uint8_t bNumInterfaces; /**< Number of interfaces supported by this configuration */
uint8_t bConfigurationValue; /**< Value to use as an argument to the SetConfiguration() request to select this configuration */
uint8_t iConfiguration; /**< Index of string descriptor describing this configuration */
uint8_t bmAttributes; /**< Configuration characteristics */
uint8_t bMaxPower; /**< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. */
uint8_t *extra;
size_t extra_length;
struct libusb_interface *interface;
};
typedef struct libusb_config_descriptor libusb_config_descriptor_t;
typedef struct libusb_interface_descriptor libusb_interface_descriptor_t;
typedef struct libusb_endpoint_descriptor libusb_endpoint_descriptor_t;
typedef struct libusb_interface libusb_interface_t;
struct libusb_ss_endpoint_companion_descriptor {
uint32_t wBytesPerInterval;
};
struct libusb_device;
typedef struct libusb_device libusb_device;
struct libusb_device_handle;
typedef struct libusb_device_handle libusb_device_handle;
struct libusb_context;
typedef enum libusb_transfer_status {
LIBUSB_TRANSFER_COMPLETED,
LIBUSB_TRANSFER_CANCELLED,
LIBUSB_TRANSFER_ERROR,
LIBUSB_TRANSFER_NO_DEVICE,
LIBUSB_TRANSFER_TIMED_OUT,
LIBUSB_TRANSFER_STALL,
LIBUSB_TRANSFER_OVERFLOW,
} libusb_status_t;
typedef struct libusb_iso_packet_descriptor {
size_t length;
size_t actual_length;
libusb_status_t status;
} libusb_iso_packet_t;
struct libusb_transfer {
libusb_device_handle *dev_handle;
libusb_status_t status;
uint8_t endpoint;
uint8_t *buffer;
size_t length;
size_t actual_length;
void *user_data;
void (*callback)(struct libusb_transfer *);
size_t timeout;
size_t num_iso_packets;
libusb_iso_packet_t iso_packet_desc[0];
};
typedef void (*libusb_transfer_cb)(struct libusb_transfer *transfer);
int libusb_init(struct libusb_context **ctx);
void libusb_exit(struct libusb_context *ctx);
int libusb_open(libusb_device *dev, libusb_device_handle **dev_handle);
void libusb_close(libusb_device_handle *dev_handle);
int32_t libusb_get_device_list(struct libusb_context *ctx, libusb_device ***list);
void libusb_free_device_list(libusb_device **list, int unref_devices);
int libusb_handle_events_completed(struct libusb_context *ctx, int *completed);
int libusb_control_transfer(libusb_device_handle *dev_handle,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
unsigned char *data,
uint16_t wLength,
unsigned int timeout);
void libusb_free_transfer(struct libusb_transfer *transfer);
int libusb_submit_transfer(struct libusb_transfer *transfer);
int libusb_cancel_transfer(struct libusb_transfer *transfer);
inline uint8_t *libusb_get_iso_packet_buffer_simple(struct libusb_transfer *transfer, uint32_t packet_id)
{
if (packet_id >= transfer->num_iso_packets) {
return NULL;
}
return &transfer->buffer[transfer->iso_packet_desc[0].length * packet_id];
}
struct libusb_transfer *libusb_alloc_transfer(int iso_packets);
inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer,
libusb_device_handle *dev,
uint8_t bEndpointAddress,
uint8_t *buffer,
size_t total_transfer_size,
size_t packets_per_transfer,
libusb_transfer_cb callback,
void *user_data,
size_t timeout)
{
transfer->dev_handle = dev;
transfer->endpoint = bEndpointAddress;
transfer->timeout = timeout;
transfer->buffer = buffer;
transfer->length = total_transfer_size;
transfer->num_iso_packets = packets_per_transfer;
transfer->user_data = user_data;
transfer->callback = callback;
}
inline void libusb_fill_bulk_transfer (struct libusb_transfer *transfer,
libusb_device_handle *dev,
uint8_t bEndpointAddress,
uint8_t *buffer,
size_t length,
libusb_transfer_cb callback,
void *user_data,
size_t timeout)
{
transfer->dev_handle = dev;
transfer->endpoint = bEndpointAddress;
transfer->buffer = buffer;
transfer->length = length;
transfer->callback = callback;
transfer->user_data = user_data;
transfer->timeout = timeout;
transfer->num_iso_packets = 0;
}
inline void libusb_fill_interrupt_transfer (struct libusb_transfer *transfer,
libusb_device_handle *dev,
uint8_t bEndpointAddress,
uint8_t *buffer,
size_t length,
libusb_transfer_cb callback,
void *user_data,
size_t timeout)
{
transfer->dev_handle = dev;
transfer->endpoint = bEndpointAddress;
transfer->buffer = buffer;
transfer->length = length;
transfer->callback = callback;
transfer->user_data = user_data;
transfer->timeout = timeout;
transfer->num_iso_packets = 0;
}
inline void libusb_set_iso_packet_lengths(struct libusb_transfer *transfer, size_t length)
{
for (uint32_t i = 0; i < transfer->num_iso_packets; i++) {
transfer->iso_packet_desc[i].length = length;
}
}
int libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, int32_t inferface, int32_t alt_settings);
int libusb_get_ss_endpoint_companion_descriptor(struct libusb_context *ctx,
const struct libusb_endpoint_descriptor *endpoint,
struct libusb_ss_endpoint_companion_descriptor **ep_comp);
int libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc);
int libusb_get_config_descriptor(libusb_device *dev, uint8_t config_index, struct libusb_config_descriptor **config);
void libusb_free_config_descriptor(struct libusb_config_descriptor *config);
int libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle, uint8_t desc_index, unsigned char *data, int length);
void libusb_free_ss_endpoint_companion_descriptor(struct libusb_ss_endpoint_companion_descriptor *desc);
int8_t libusb_get_bus_number(libusb_device *device);
int8_t libusb_get_device_address(libusb_device *device);
libusb_device *libusb_ref_device(libusb_device *dev);
void libusb_unref_device(libusb_device *dev);
int libusb_claim_interface(libusb_device_handle *dev_handle, int interface);
int libusb_release_interface(libusb_device_handle *dev_handle, int interface);
int libusb_attach_kernel_driver(libusb_device_handle *dev_handle, int interface_number);
int libusb_detach_kernel_driver(libusb_device_handle *dev_handle, int interface_number);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,821 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "libusb.h"
#include <stdio.h>
#include <wchar.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/queue.h>
#include "esp_pthread.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_check.h"
#include "usb/usb_host.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_types_stack.h"
#include "usb/usb_helpers.h"
#include "descriptor.h"
#include "sdkconfig.h"
#include "libuvc/libuvc.h"
#include "libuvc/libuvc_internal.h"
#include "libuvc_adapter.h"
#define TAG "libusb adapter"
#define GOTO_ON_FALSE(exp) ESP_GOTO_ON_FALSE(exp, ESP_ERR_NO_MEM, fail, TAG, "")
#define RETURN_ON_ERROR(exp) ESP_RETURN_ON_ERROR(exp, TAG, "err: %s", esp_err_to_name(err_rc_))
#define GOTO_ON_ERROR(exp) ESP_GOTO_ON_ERROR(exp, fail, TAG, "")
#define RETURN_ON_ERROR_LIBUSB(exp) do { \
esp_err_t _err_ = (exp); \
if(_err_ != ESP_OK) { \
return esp_to_libusb_error(_err_); \
} \
} while(0)
#define UVC_ENTER_CRITICAL() portENTER_CRITICAL(&s_uvc_lock)
#define UVC_EXIT_CRITICAL() portEXIT_CRITICAL(&s_uvc_lock)
#define COUNT_OF(array) (sizeof(array) / sizeof(array[0]))
typedef struct {
usb_transfer_t *xfer;
struct libusb_transfer libusb_xfer;
} uvc_transfer_t;
typedef struct opened_camera {
uint8_t address;
uint8_t open_count;
uint16_t endpoint_mps; // interrupt endpoint
uint8_t active_alt_setting;
usb_device_handle_t handle;
usb_transfer_t *control_xfer;
SemaphoreHandle_t transfer_done;
usb_transfer_status_t transfer_status;
STAILQ_ENTRY(opened_camera) tailq_entry;
} uvc_camera_t;
typedef struct {
usb_host_client_handle_t client;
volatile bool delete_client_task;
SemaphoreHandle_t client_task_deleted;
STAILQ_HEAD(opened_devs, opened_camera) opened_devices_tailq;
} uvc_driver_t;
static portMUX_TYPE s_uvc_lock = portMUX_INITIALIZER_UNLOCKED;
static uvc_driver_t *s_uvc_driver;
static libuvc_adapter_config_t s_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.callback = NULL,
};
static const usb_standard_desc_t *next_interface_desc(const usb_standard_desc_t *desc, size_t len, int *offset)
{
return usb_parse_next_descriptor_of_type(desc, len, USB_W_VALUE_DT_INTERFACE, offset);
}
static const usb_standard_desc_t *next_endpoint_desc(const usb_standard_desc_t *desc, size_t len, int *offset)
{
return usb_parse_next_descriptor_of_type(desc, len, USB_B_DESCRIPTOR_TYPE_ENDPOINT, (int *)offset);
}
// Find endpoint number under specified interface.
static esp_err_t find_endpoint_of_interface(const usb_config_desc_t *config_desc, uint8_t interface, uint8_t *endpoint)
{
int offset = 0;
size_t total_length = config_desc->wTotalLength;
const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc;
next_desc = next_interface_desc(next_desc, total_length, &offset);
while ( next_desc ) {
const usb_intf_desc_t *ifc_desc = (const usb_intf_desc_t *)next_desc;
if ( ifc_desc->bInterfaceNumber == interface && ifc_desc->bNumEndpoints != 0) {
next_desc = next_endpoint_desc(next_desc, total_length, &offset);
if (next_desc == NULL) {
return ESP_ERR_NOT_SUPPORTED;
}
*endpoint = ((const usb_ep_desc_t *)next_desc)->bEndpointAddress;
return ESP_OK;
}
next_desc = next_interface_desc(next_desc, total_length, &offset);
};
return ESP_ERR_NOT_SUPPORTED;
}
static uint16_t get_interupt_endpoint_mps(const usb_config_desc_t *config_desc)
{
int offset = 0;
size_t total_length = config_desc->wTotalLength;
const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc;
while ( (next_desc = next_endpoint_desc(next_desc, total_length, &offset)) ) {
const usb_ep_desc_t *ep_desc = (const usb_ep_desc_t *)next_desc;
if (USB_EP_DESC_GET_XFERTYPE(ep_desc) == USB_BM_ATTRIBUTES_XFER_INT) {
return ep_desc->wMaxPacketSize;
}
};
return 32;
}
void libuvc_adapter_set_config(libuvc_adapter_config_t *config)
{
if (config == NULL) {
return;
}
s_config = *config;
}
static void print_str_desc(const usb_str_desc_t *desc, const char *name)
{
wchar_t str[32];
size_t str_len = MIN((desc->bLength - USB_STANDARD_DESC_SIZE) / 2, COUNT_OF(str) - 1);
// Copy utf-16 to wchar_t array
for (size_t i = 0; i < str_len; i++) {
str[i] = desc->wData[i];
}
str[str_len] = '\0';
wprintf(L"%s: %S \n", name, str);
}
static void print_string_descriptors(usb_device_info_t *dev_info)
{
printf("*** String Descriptors ***\n");
if (dev_info->str_desc_product) {
print_str_desc(dev_info->str_desc_product, "iProduct");
}
if (dev_info->str_desc_manufacturer) {
print_str_desc(dev_info->str_desc_manufacturer, "iManufacturer");
}
if (dev_info->str_desc_serial_num) {
print_str_desc(dev_info->str_desc_serial_num, "iSerialNumber");
}
}
esp_err_t libuvc_adapter_print_descriptors(uvc_device_handle_t *device)
{
uvc_camera_t *camera = (uvc_camera_t *)(device->usb_devh);
const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc;
usb_device_info_t dev_info;
RETURN_ON_ERROR( usb_host_get_device_descriptor(camera->handle, &device_desc) );
RETURN_ON_ERROR( usb_host_get_active_config_descriptor(camera->handle, &config_desc) );
RETURN_ON_ERROR( usb_host_device_info(camera->handle, &dev_info) );
usb_print_device_descriptor(device_desc);
usb_print_config_descriptor(config_desc, print_usb_class_descriptors);
print_string_descriptors(&dev_info);
return ESP_OK;
}
esp_err_t libuvc_adapter_handle_events(uint32_t timeout_ms)
{
if (s_uvc_driver == NULL) {
return ESP_ERR_INVALID_STATE;
}
return usb_host_client_handle_events(s_uvc_driver->client, pdMS_TO_TICKS(timeout_ms));
}
static int esp_to_libusb_error(esp_err_t err)
{
switch (err) {
case ESP_ERR_TIMEOUT: return LIBUSB_ERROR_TIMEOUT;
case ESP_ERR_NO_MEM: return LIBUSB_ERROR_NO_MEM;
case ESP_FAIL: return LIBUSB_ERROR_PIPE;
case ESP_OK: return LIBUSB_SUCCESS;
default: return LIBUSB_ERROR_OTHER;
}
}
static enum libusb_transfer_status eps_to_libusb_status(usb_transfer_status_t esp_status)
{
switch (esp_status) {
case USB_TRANSFER_STATUS_COMPLETED: return LIBUSB_TRANSFER_COMPLETED;
case USB_TRANSFER_STATUS_TIMED_OUT: return LIBUSB_TRANSFER_TIMED_OUT;
case USB_TRANSFER_STATUS_CANCELED: return LIBUSB_TRANSFER_CANCELLED;
case USB_TRANSFER_STATUS_NO_DEVICE: return LIBUSB_TRANSFER_NO_DEVICE;
case USB_TRANSFER_STATUS_OVERFLOW: return LIBUSB_TRANSFER_OVERFLOW;
case USB_TRANSFER_STATUS_STALL: return LIBUSB_TRANSFER_STALL;
default: return LIBUSB_TRANSFER_ERROR;
}
}
static void usb_client_event_handler(void *arg)
{
ulTaskNotifyTake(false, pdMS_TO_TICKS(1000));
do {
usb_host_client_handle_events(s_uvc_driver->client, pdMS_TO_TICKS(50));
} while (!s_uvc_driver->delete_client_task);
xSemaphoreGive(s_uvc_driver->client_task_deleted);
vTaskDelete(NULL);
}
static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg)
{
if (s_config.callback) {
switch (event->event) {
case USB_HOST_CLIENT_EVENT_NEW_DEV:
ESP_LOGD(TAG, "USB device connected");
s_config.callback(UVC_DEVICE_CONNECTED);
break;
case USB_HOST_CLIENT_EVENT_DEV_GONE:
ESP_LOGD(TAG, "USB device disconnected");
s_config.callback(UVC_DEVICE_DISCONNECTED);
break;
default:
break;
}
}
}
int libusb_init(struct libusb_context **ctx)
{
uvc_driver_t *driver = NULL;
TaskHandle_t client_task_handle = NULL;
esp_err_t ret = ESP_ERR_NO_MEM;
usb_host_client_config_t client_config = {
.async.client_event_callback = client_event_cb,
.async.callback_arg = NULL,
.max_num_event_msg = 5,
};
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
esp_pthread_set_cfg(&cfg);
GOTO_ON_FALSE( driver = calloc(1, sizeof(uvc_driver_t)) );
GOTO_ON_ERROR( usb_host_client_register(&client_config, &driver->client) );
GOTO_ON_FALSE( driver->client_task_deleted = xSemaphoreCreateBinary() );
STAILQ_INIT(&driver->opened_devices_tailq);
if (s_config.create_background_task) {
GOTO_ON_FALSE( xTaskCreate(usb_client_event_handler, "uvc_events", s_config.stack_size,
NULL, s_config.task_priority, &client_task_handle) );
}
UVC_ENTER_CRITICAL();
if (s_uvc_driver != NULL) {
UVC_EXIT_CRITICAL();
ret = ESP_ERR_TIMEOUT;
goto fail;
}
s_uvc_driver = driver;
UVC_EXIT_CRITICAL();
if (client_task_handle) {
xTaskNotifyGive(client_task_handle);
}
*ctx = (struct libusb_context *)driver;
return LIBUSB_SUCCESS;
fail:
if (driver) {
if (driver->client) {
usb_host_client_deregister(driver->client);
};
if (driver->client_task_deleted) {
vSemaphoreDelete(driver->client_task_deleted);
}
free(driver);
}
if (client_task_handle) {
vTaskDelete(client_task_handle);
}
return esp_to_libusb_error(ret);
}
void libusb_exit(struct libusb_context *ctx)
{
uvc_driver_t *driver = (uvc_driver_t *)ctx;
UVC_ENTER_CRITICAL();
if (driver == NULL) {
UVC_EXIT_CRITICAL();
return;
}
UVC_EXIT_CRITICAL();
if (s_config.create_background_task) {
driver->delete_client_task = true;
}
usb_host_client_unblock(driver->client);
xSemaphoreTake(s_uvc_driver->client_task_deleted, portMAX_DELAY);
if (usb_host_client_deregister(driver->client) != ESP_OK) {
ESP_LOGE(TAG, "Failed to deregister USB client");
}
vSemaphoreDelete(s_uvc_driver->client_task_deleted);
s_uvc_driver = NULL;
free(driver);
}
int32_t libusb_get_device_list(struct libusb_context *ctx, libusb_device ***list)
{
static const size_t DEV_LIST_SIZE = 5;
int actual_count;
uint8_t dev_addr_list[DEV_LIST_SIZE];
usb_host_device_addr_list_fill(DEV_LIST_SIZE, dev_addr_list, &actual_count);
libusb_device **dev_list = calloc(actual_count + 1, sizeof(libusb_device *));
if (dev_list == NULL) {
return 0;
}
for (size_t i = 0; i < actual_count; i++) {
dev_list[i] = (libusb_device *)(uint32_t)dev_addr_list[i];
}
*list = (libusb_device **)dev_list;
return actual_count;
}
void libusb_free_device_list(libusb_device **list, int unref_devices)
{
free(list);
}
// As opposed to LIBUSB, USB_HOST library does not allows to open devices recursively and get descriptors without opening device.
// Thus, libusb_adapter keeps track of how many times the device is opened and closes it only when the count reaches zero.
static esp_err_t open_device_if_closed(uint8_t device_addr, uvc_camera_t **handle)
{
uvc_camera_t *device;
esp_err_t ret = ESP_ERR_NO_MEM;
uvc_camera_t *new_device = calloc(1, sizeof(uvc_camera_t));
if (new_device == NULL) {
return ESP_ERR_NO_MEM;
}
UVC_ENTER_CRITICAL();
STAILQ_FOREACH(device, &s_uvc_driver->opened_devices_tailq, tailq_entry) {
if (device_addr == device->address) {
*handle = device;
device->open_count++;
UVC_EXIT_CRITICAL();
free(new_device);
return ESP_OK;
}
}
new_device->open_count++;
new_device->address = device_addr;
STAILQ_INSERT_TAIL(&s_uvc_driver->opened_devices_tailq, new_device, tailq_entry);
UVC_EXIT_CRITICAL();
GOTO_ON_ERROR( usb_host_device_open(s_uvc_driver->client, device_addr, &new_device->handle) );
GOTO_ON_ERROR( usb_host_transfer_alloc(128, 0, &new_device->control_xfer) );
GOTO_ON_FALSE( new_device->transfer_done = xSemaphoreCreateBinary() );
*handle = new_device;
return ESP_OK;
fail:
UVC_ENTER_CRITICAL();
STAILQ_REMOVE(&s_uvc_driver->opened_devices_tailq, new_device, opened_camera, tailq_entry);
UVC_EXIT_CRITICAL();
free(new_device);
return ret;
}
static esp_err_t close_device(uvc_camera_t *device)
{
bool close = false;
UVC_ENTER_CRITICAL();
if (--device->open_count == 0) {
STAILQ_REMOVE(&s_uvc_driver->opened_devices_tailq, device, opened_camera, tailq_entry);
close = true;
}
UVC_EXIT_CRITICAL();
if (close) {
RETURN_ON_ERROR( usb_host_device_close(s_uvc_driver->client, device->handle) );
RETURN_ON_ERROR( usb_host_transfer_free(device->control_xfer) );
vSemaphoreDelete(device->transfer_done);
free(device);
}
return LIBUSB_SUCCESS;
}
int libusb_open(libusb_device *dev, libusb_device_handle **dev_handle)
{
uint8_t device_addr = (uint8_t)(uint32_t)dev;
uvc_camera_t *device;
RETURN_ON_ERROR_LIBUSB( open_device_if_closed(device_addr, &device) );
*dev_handle = (libusb_device_handle *)device;
return LIBUSB_SUCCESS;
}
void libusb_close(libusb_device_handle *dev_handle)
{
esp_err_t err = close_device((uvc_camera_t *)dev_handle);
if (err) {
ESP_LOGE(TAG, "Failed to close device");
}
}
void libusb_free_transfer(struct libusb_transfer *transfer)
{
uvc_transfer_t *trans = __containerof(transfer, uvc_transfer_t, libusb_xfer);
usb_host_transfer_free(trans->xfer);
free(trans);
}
struct libusb_transfer *libusb_alloc_transfer(int iso_packets)
{
size_t alloc_size = sizeof(uvc_transfer_t) +
sizeof(struct libusb_iso_packet_descriptor) * iso_packets;
uvc_transfer_t *xfer = calloc(1, alloc_size);
if (xfer == NULL) {
return NULL;
}
return &xfer->libusb_xfer;
}
static inline bool is_in_endpoint(uint8_t endpoint)
{
return endpoint & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK ? true : false;
}
// Copies data from usb_transfer_t back to libusb_transfer and invokes user provided callback
void transfer_cb(usb_transfer_t *xfer)
{
uvc_transfer_t *trans = xfer->context;
struct libusb_transfer *libusb_trans = &trans->libusb_xfer;
size_t isoc_actual_length = 0;
for (int i = 0; i < xfer->num_isoc_packets; i++) {
libusb_trans->iso_packet_desc[i].actual_length = xfer->isoc_packet_desc[i].actual_num_bytes;
libusb_trans->iso_packet_desc[i].status = eps_to_libusb_status(xfer->isoc_packet_desc[i].status);
if (libusb_trans->iso_packet_desc[i].status == LIBUSB_TRANSFER_COMPLETED) {
isoc_actual_length += xfer->isoc_packet_desc[i].actual_num_bytes;
}
}
libusb_trans->status = eps_to_libusb_status(xfer->status);
libusb_trans->actual_length = xfer->num_isoc_packets ? isoc_actual_length : xfer->actual_num_bytes;
if (is_in_endpoint(libusb_trans->endpoint)) {
memcpy(libusb_trans->buffer, xfer->data_buffer, libusb_trans->length);
}
libusb_trans->callback(libusb_trans);
}
// This function copies libusb_transfer data into usb_transfer_t structure
int libusb_submit_transfer(struct libusb_transfer *libusb_trans)
{
uvc_transfer_t *trans = __containerof(libusb_trans, uvc_transfer_t, libusb_xfer);
esp_err_t err;
int length = libusb_trans->length;
int num_iso_packets = libusb_trans->num_iso_packets;
uvc_camera_t *device = (uvc_camera_t *)libusb_trans->dev_handle;
// Workaround: libuvc submits interrupt INTR transfers with transfer size
// of 32 bytes, event though MSP of the endpoint might be 64.
// Make in transfer rounded up to MSP of interrupt endpoint.
// ISO transfers should be effected by this, as there are supposed to be 512 bytes long
if (is_in_endpoint(libusb_trans->endpoint)) {
length = usb_round_up_to_mps(length, device->endpoint_mps);
}
// Transfers are allocated/reallocated based on transfer size, as libusb
// doesn't store buffers in DMA capable region
if (!trans->xfer || trans->xfer->data_buffer_size < libusb_trans->length) {
if (trans->xfer) {
usb_host_transfer_free(trans->xfer);
}
err = usb_host_transfer_alloc(length, num_iso_packets, &trans->xfer);
if (err) {
ESP_LOGE(TAG, "Failed to allocate transfer with length: %u", length);
return esp_to_libusb_error(err);
}
}
if (!is_in_endpoint(libusb_trans->endpoint)) {
memcpy(trans->xfer->data_buffer, libusb_trans->buffer, libusb_trans->length);
}
trans->xfer->device_handle = device->handle;
trans->xfer->bEndpointAddress = libusb_trans->endpoint;
trans->xfer->timeout_ms = libusb_trans->timeout;
trans->xfer->callback = transfer_cb;
trans->xfer->num_bytes = length;
trans->xfer->context = trans;
for (int i = 0; i < num_iso_packets; i++) {
trans->xfer->isoc_packet_desc[i].num_bytes = libusb_trans->iso_packet_desc[i].length;
}
err = usb_host_transfer_submit(trans->xfer);
return esp_to_libusb_error(err);
}
int libusb_cancel_transfer(struct libusb_transfer *transfer)
{
return 0;
}
static bool is_in_request(uint8_t bmRequestType)
{
return (bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN) != 0 ? true : false;
}
static bool is_out_request(uint8_t bmRequestType)
{
return (bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN) == 0 ? true : false;
}
static void common_xfer_cb(usb_transfer_t *transfer)
{
uvc_camera_t *device = (uvc_camera_t *)transfer->context;
if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_EARLY_LOGE("Transfer failed", "Status %d", transfer->status);
}
device->transfer_status = transfer->status;
xSemaphoreGive(device->transfer_done);
}
static esp_err_t wait_for_transmition_done(usb_transfer_t *xfer)
{
uvc_camera_t *device = (uvc_camera_t *)xfer->context;
BaseType_t received = xSemaphoreTake(device->transfer_done, pdMS_TO_TICKS(xfer->timeout_ms));
if (received != pdTRUE) {
usb_host_endpoint_halt(xfer->device_handle, xfer->bEndpointAddress);
usb_host_endpoint_flush(xfer->device_handle, xfer->bEndpointAddress);
xSemaphoreTake(device->transfer_done, portMAX_DELAY);
return ESP_ERR_TIMEOUT;
}
if (device->transfer_status != USB_TRANSFER_STATUS_COMPLETED) {
printf("transfer_status: %d", device->transfer_status);
return ESP_FAIL;
}
return ESP_OK;
}
static int control_transfer(libusb_device_handle *dev_handle,
usb_setup_packet_t *request,
unsigned char *data,
unsigned int timeout)
{
return libusb_control_transfer(dev_handle, request->bmRequestType, request->bRequest,
request->wValue, request->wIndex, data,
request->wLength, timeout);
}
int libusb_control_transfer(libusb_device_handle *dev_handle,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
unsigned char *data,
uint16_t wLength,
unsigned int timeout)
{
uvc_camera_t *device = (uvc_camera_t *)dev_handle;
usb_transfer_t *xfer = device->control_xfer;
usb_setup_packet_t *ctrl_req = (usb_setup_packet_t *)xfer->data_buffer;
ctrl_req->bmRequestType = bmRequestType;
ctrl_req->bRequest = bRequest;
ctrl_req->wValue = wValue;
ctrl_req->wIndex = wIndex;
ctrl_req->wLength = wLength;
xfer->device_handle = device->handle;
xfer->bEndpointAddress = 0;
xfer->callback = common_xfer_cb;
xfer->timeout_ms = MAX(timeout, 100);
xfer->num_bytes = USB_SETUP_PACKET_SIZE + wLength;
xfer->context = device;
if (is_out_request(bmRequestType)) {
memcpy(xfer->data_buffer + sizeof(usb_setup_packet_t), data, wLength);
}
RETURN_ON_ERROR_LIBUSB( usb_host_transfer_submit_control(s_uvc_driver->client, xfer) );
RETURN_ON_ERROR_LIBUSB( wait_for_transmition_done(xfer) );
if (is_in_request(bmRequestType)) {
memcpy(data, xfer->data_buffer + sizeof(usb_setup_packet_t), wLength);
}
return xfer->actual_num_bytes;
}
int libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc)
{
uint8_t device_addr = (uint8_t)(uint32_t)dev;
const usb_device_desc_t *device_desc;
uvc_camera_t *device;
// Open device if closed, as USB host doesn't allow to get descriptor without opening device
RETURN_ON_ERROR_LIBUSB( open_device_if_closed(device_addr, &device) );
RETURN_ON_ERROR_LIBUSB( usb_host_get_device_descriptor(device->handle, &device_desc) );
desc->bLength = device_desc->bLength;
desc->bDescriptorType = device_desc->bDescriptorType;
desc->bcdUSB = device_desc->bcdUSB;
desc->bDeviceClass = device_desc->bDeviceClass;
desc->bDeviceSubClass = device_desc->bDeviceSubClass;
desc->bDeviceProtocol = device_desc->bDeviceProtocol;
desc->bMaxPacketSize0 = device_desc->bMaxPacketSize0;
desc->idVendor = device_desc->idVendor;
desc->idProduct = device_desc->idProduct;
desc->bcdDevice = device_desc->bcdDevice;
desc->iManufacturer = device_desc->iManufacturer;
desc->iProduct = device_desc->iProduct;
desc->iSerialNumber = device_desc->iSerialNumber;
desc->bNumConfigurations = device_desc->bNumConfigurations;
RETURN_ON_ERROR_LIBUSB( close_device(device) );
return LIBUSB_SUCCESS;
}
int libusb_get_config_descriptor(libusb_device *dev,
uint8_t config_index,
struct libusb_config_descriptor **config)
{
uint8_t device_addr = (uint8_t)(uint32_t)dev;
const usb_config_desc_t *config_desc;
uvc_camera_t *device;
// Open device if closed, as USB host doesn't allow to get descriptor without opening device
RETURN_ON_ERROR_LIBUSB( open_device_if_closed(device_addr, &device) );
RETURN_ON_ERROR_LIBUSB( usb_host_get_active_config_descriptor(device->handle, &config_desc) );
int res = raw_desc_to_libusb_config(&config_desc->val[0], config_desc->wTotalLength, config);
device->endpoint_mps = get_interupt_endpoint_mps(config_desc);
RETURN_ON_ERROR_LIBUSB( close_device(device) );
return res;
}
void libusb_free_config_descriptor(struct libusb_config_descriptor *config)
{
clear_config_descriptor(config);
free(config);
}
int libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle,
uint8_t desc_index,
unsigned char *data,
int length)
{
#define US_LANG_ID 0x409
usb_setup_packet_t ctrl_req;
USB_SETUP_PACKET_INIT_GET_STR_DESC(&ctrl_req, desc_index, US_LANG_ID, length);
return control_transfer(dev_handle, &ctrl_req, data, 1000);
}
int libusb_get_ss_endpoint_companion_descriptor(struct libusb_context *ctx,
const struct libusb_endpoint_descriptor *endpoint,
struct libusb_ss_endpoint_companion_descriptor **ep_comp)
{
return 0;
}
void libusb_free_ss_endpoint_companion_descriptor(struct libusb_ss_endpoint_companion_descriptor *ep_comp)
{
}
libusb_device *libusb_ref_device(libusb_device *dev)
{
return dev;
}
void libusb_unref_device(libusb_device *dev)
{
}
int libusb_claim_interface(libusb_device_handle *dev_handle, int interface)
{
uvc_camera_t *device = (uvc_camera_t *)dev_handle;
// Alternate interface will be claimed in libusb_set_interface_alt_setting function,
// as libusb only support claming interface without alternate settings.
return esp_to_libusb_error( usb_host_interface_claim(s_uvc_driver->client, device->handle, interface, 0) );
}
int libusb_release_interface(libusb_device_handle *dev_handle, int interface)
{
uvc_camera_t *device = (uvc_camera_t *)dev_handle;
const usb_config_desc_t *config_desc;
uint8_t endpoint;
RETURN_ON_ERROR_LIBUSB( usb_host_get_active_config_descriptor(device->handle, &config_desc) );
RETURN_ON_ERROR_LIBUSB( find_endpoint_of_interface(config_desc, interface, &endpoint) );
// Cancel any ongoing transfers before releasing interface
usb_host_endpoint_halt(device->handle, endpoint);
usb_host_endpoint_flush(device->handle, endpoint);
usb_host_endpoint_clear(device->handle, endpoint);
return esp_to_libusb_error( usb_host_interface_release(s_uvc_driver->client, device->handle, interface) );
}
int libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, int32_t inferface, int32_t alt_settings)
{
uvc_camera_t *device = (uvc_camera_t *)dev_handle;
usb_host_client_handle_t client = s_uvc_driver->client;
uint8_t data[sizeof(usb_setup_packet_t)];
usb_setup_packet_t request;
// Setting alternate interface 0.0 is special case in UVC specs.
// No interface is to be released, just send control transfer.
if (inferface != 0 || alt_settings != 0) {
RETURN_ON_ERROR_LIBUSB( usb_host_interface_release(client, device->handle, inferface) );
RETURN_ON_ERROR_LIBUSB( usb_host_interface_claim(client, device->handle, inferface, alt_settings) );
}
USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, inferface, alt_settings);
int result = control_transfer(dev_handle, &request, data, 2000);
return result > 0 ? LIBUSB_SUCCESS : result;
}
int libusb_attach_kernel_driver(libusb_device_handle *dev_handle, int interface_number)
{
return 0;
}
int libusb_detach_kernel_driver(libusb_device_handle *dev_handle, int interface_number)
{
return 0;
}
int libusb_handle_events_completed(struct libusb_context *ctx, int *completed)
{
// USB events are handled either in client task or by user invoking libuvc_adapter_handle_events,
// as LIBUVC calls this handler only after opening device. USB Host requires to call client handler
// prior to opening device in order to receive USB_HOST_CLIENT_EVENT_NEW_DEV event.
vTaskDelay(pdMS_TO_TICKS(1000));
return 0;
}
int8_t libusb_get_bus_number(libusb_device *device)
{
return 0;
}
int8_t libusb_get_device_address(libusb_device *device)
{
// Device addres is stored directly in libusb_device
return (uint8_t)(uint32_t)device;
}

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS
INCLUDE_DIRS "."
REQUIRES usb_host_uvc unity)