Files
2025-12-13 11:59:11 +02:00

295 lines
13 KiB
C

/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_err.h"
#include "descriptors_control.h"
#include "usb_descriptors.h"
#define MAX_DESC_BUF_SIZE 32 // Max length of string descriptor (can be extended, USB supports lengths up to 255 bytes)
static const char *TAG = "tusb_desc";
// =============================================================================
// STRUCTS
// =============================================================================
/**
* @brief Descriptor pointers for tinyusb descriptor requests callbacks
*
*/
typedef struct {
const tusb_desc_device_t *dev; /*!< Pointer to device descriptor */
union {
const uint8_t *cfg; /*!< Pointer to FullSpeed configuration descriptor when device one-speed only */
const uint8_t *fs_cfg; /*!< Pointer to FullSpeed configuration descriptor when device support HighSpeed */
};
#if (TUD_OPT_HIGH_SPEED)
const uint8_t *hs_cfg; /*!< Pointer to HighSpeed configuration descriptor */
const tusb_desc_device_qualifier_t *qualifier; /*!< Pointer to Qualifier descriptor */
uint8_t *other_speed; /*!< Pointer for other speed configuration descriptor */
#endif // TUD_OPT_HIGH_SPEED
const char *str[USB_STRING_DESCRIPTOR_ARRAY_SIZE]; /*!< Pointer to array of UTF-8 strings */
int str_count; /*!< Number of descriptors in str */
} tinyusb_descriptor_config_t;
static tinyusb_descriptor_config_t s_desc_cfg;
// =============================================================================
// CALLBACKS
// =============================================================================
/**
* @brief Invoked when received GET DEVICE DESCRIPTOR.
* Descriptor contents must exist long enough for transfer to complete
*
* @return Pointer to device descriptor
*/
uint8_t const *tud_descriptor_device_cb(void)
{
assert(s_desc_cfg.dev);
return (uint8_t const *)s_desc_cfg.dev;
}
/**
* @brief Invoked when received GET CONFIGURATION DESCRIPTOR.
* Descriptor contents must exist long enough for transfer to complete
*
* @param[in] index Index of required configuration
* @return Pointer to configuration descriptor
*/
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
(void)index; // Unused, this driver supports only 1 configuration
assert(s_desc_cfg.cfg);
#if (TUD_OPT_HIGH_SPEED)
// HINT: cfg and fs_cfg are union, no need to assert(fs_cfg)
assert(s_desc_cfg.hs_cfg);
// Return configuration descriptor based on Host speed
return (TUSB_SPEED_HIGH == tud_speed_get())
? s_desc_cfg.hs_cfg
: s_desc_cfg.fs_cfg;
#else
return s_desc_cfg.cfg;
#endif // TUD_OPT_HIGH_SPEED
}
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
* Descriptor contents must exist long enough for transfer to complete
* If not highspeed capable stall this request
*/
uint8_t const *tud_descriptor_device_qualifier_cb(void)
{
assert(s_desc_cfg.qualifier);
return (uint8_t const *)s_desc_cfg.qualifier;
}
/**
* @brief Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request
* Descriptor contents must exist long enough for transfer to complete
* Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
*/
uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index)
{
assert(s_desc_cfg.other_speed);
const uint8_t *other_speed = (TUSB_SPEED_HIGH == tud_speed_get())
? s_desc_cfg.fs_cfg
: s_desc_cfg.hs_cfg;
memcpy(s_desc_cfg.other_speed,
other_speed,
((tusb_desc_configuration_t *)other_speed)->wTotalLength);
((tusb_desc_configuration_t *)s_desc_cfg.other_speed)->bDescriptorType = TUSB_DESC_OTHER_SPEED_CONFIG;
return s_desc_cfg.other_speed;
}
#endif // TUD_OPT_HIGH_SPEED
/**
* @brief Invoked when received GET STRING DESCRIPTOR request
*
* @param[in] index Index of required descriptor
* @param[in] langid Language of the descriptor
* @return Pointer to UTF-16 string descriptor
*/
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void) langid; // Unused, this driver supports only one language in string descriptors
assert(s_desc_cfg.str);
uint8_t chr_count;
static uint16_t _desc_str[MAX_DESC_BUF_SIZE];
if (index == 0) {
memcpy(&_desc_str[1], s_desc_cfg.str[0], 2);
chr_count = 1;
} else {
if (index >= USB_STRING_DESCRIPTOR_ARRAY_SIZE) {
ESP_LOGW(TAG, "String index (%u) is out of bounds, check your string descriptor", index);
return NULL;
}
if (s_desc_cfg.str[index] == NULL) {
ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index);
return NULL;
}
const char *str = s_desc_cfg.str[index];
chr_count = strnlen(str, MAX_DESC_BUF_SIZE - 1); // Buffer len - header
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// First byte is length in bytes (including header), second byte is descriptor type (TUSB_DESC_STRING)
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2);
return _desc_str;
}
// =============================================================================
// Driver functions
// =============================================================================
esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config)
{
esp_err_t ret = ESP_FAIL;
assert(config);
const char **pstr_desc;
// Flush descriptors control struct
memset(&s_desc_cfg, 0x00, sizeof(tinyusb_descriptor_config_t));
// Parse configuration and save descriptors's pointer
// Select Device Descriptor
if (config->device_descriptor == NULL) {
ESP_LOGW(TAG, "No Device descriptor provided, using default.");
s_desc_cfg.dev = &descriptor_dev_default;
} else {
s_desc_cfg.dev = config->device_descriptor;
}
// Select FullSpeed configuration descriptor
if (config->configuration_descriptor == NULL) {
// Default configuration descriptor must be provided for the following classes
#if (CFG_TUD_HID > 0 || CFG_TUD_MIDI > 0 || CFG_TUD_ECM_RNDIS > 0 || CFG_TUD_DFU > 0 || CFG_TUD_DFU_RUNTIME > 0 || CFG_TUD_BTH > 0)
ESP_GOTO_ON_FALSE(config->configuration_descriptor, ESP_ERR_INVALID_ARG, fail, TAG, "Configuration descriptor must be provided for this device");
#else
ESP_LOGW(TAG, "No FullSpeed configuration descriptor provided, using default.");
s_desc_cfg.cfg = descriptor_fs_cfg_default;
#endif
} else {
s_desc_cfg.cfg = config->configuration_descriptor;
}
#if (TUD_OPT_HIGH_SPEED)
// High Speed
if (config->hs_configuration_descriptor == NULL) {
// Default configuration descriptor must be provided for the following classes
#if (CFG_TUD_HID > 0 || CFG_TUD_MIDI > 0 || CFG_TUD_ECM_RNDIS > 0 || CFG_TUD_DFU > 0 || CFG_TUD_DFU_RUNTIME > 0 || CFG_TUD_BTH > 0)
ESP_GOTO_ON_FALSE(config->hs_configuration_descriptor, ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed configuration descriptor must be provided for this device");
#else
ESP_LOGW(TAG, "No HighSpeed configuration descriptor provided, using default.");
s_desc_cfg.hs_cfg = descriptor_hs_cfg_default;
#endif
} else {
s_desc_cfg.hs_cfg = config->hs_configuration_descriptor;
}
// HS and FS cfg desc should be equal length
ESP_GOTO_ON_FALSE(((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength ==
((tusb_desc_configuration_t *)s_desc_cfg.fs_cfg)->wTotalLength,
ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed and FullSpeed configuration descriptors must be same length");
// Qualifier Descriptor
if (config->qualifier_descriptor == NULL) {
ESP_GOTO_ON_FALSE((s_desc_cfg.dev == &descriptor_dev_default), ESP_ERR_INVALID_ARG, fail, TAG, "Qualifier descriptor must be present (Device Descriptor not default).");
// Get default qualifier if device descriptor is default
ESP_LOGW(TAG, "No Qulifier descriptor provided, using default.");
s_desc_cfg.qualifier = &descriptor_qualifier_default;
} else {
s_desc_cfg.qualifier = config->qualifier_descriptor;
}
// Other Speed buffer allocate
s_desc_cfg.other_speed = calloc(1, ((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength);
ESP_GOTO_ON_FALSE(s_desc_cfg.other_speed, ESP_ERR_NO_MEM, fail, TAG, "Other speed memory allocation error");
#endif // TUD_OPT_HIGH_SPEED
// Select String Descriptors and count them
if (config->string_descriptor == NULL) {
ESP_LOGW(TAG, "No String descriptors provided, using default.");
pstr_desc = descriptor_str_default;
while (descriptor_str_default[++s_desc_cfg.str_count] != NULL);
} else {
pstr_desc = config->string_descriptor;
s_desc_cfg.str_count = (config->string_descriptor_count != 0)
? config->string_descriptor_count
: 8; // '8' is for backward compatibility with esp_tinyusb v1.0.0. Do NOT remove!
}
ESP_GOTO_ON_FALSE(s_desc_cfg.str_count <= USB_STRING_DESCRIPTOR_ARRAY_SIZE, ESP_ERR_NOT_SUPPORTED, fail, TAG, "String descriptors exceed limit");
memcpy(s_desc_cfg.str, pstr_desc, s_desc_cfg.str_count * sizeof(pstr_desc[0]));
ESP_LOGI(TAG, "\n"
"┌─────────────────────────────────┐\n"
"│ USB Device Descriptor Summary │\n"
"├───────────────────┬─────────────┤\n"
"│bDeviceClass │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bDeviceSubClass │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bDeviceProtocol │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bMaxPacketSize0 │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│idVendor │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│idProduct │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│bcdDevice │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iManufacturer │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iProduct │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iSerialNumber │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│bNumConfigurations │ %-#10x │\n"
"└───────────────────┴─────────────┘",
s_desc_cfg.dev->bDeviceClass, s_desc_cfg.dev->bDeviceSubClass,
s_desc_cfg.dev->bDeviceProtocol, s_desc_cfg.dev->bMaxPacketSize0,
s_desc_cfg.dev->idVendor, s_desc_cfg.dev->idProduct, s_desc_cfg.dev->bcdDevice,
s_desc_cfg.dev->iManufacturer, s_desc_cfg.dev->iProduct, s_desc_cfg.dev->iSerialNumber,
s_desc_cfg.dev->bNumConfigurations);
return ESP_OK;
fail:
#if (TUD_OPT_HIGH_SPEED)
free(s_desc_cfg.other_speed);
#endif // TUD_OPT_HIGH_SPEED
return ret;
}
void tinyusb_set_str_descriptor(const char *str, int str_idx)
{
assert(str_idx < USB_STRING_DESCRIPTOR_ARRAY_SIZE);
s_desc_cfg.str[str_idx] = str;
}
void tinyusb_free_descriptors(void)
{
#if (TUD_OPT_HIGH_SPEED)
assert(s_desc_cfg.other_speed);
free(s_desc_cfg.other_speed);
#endif // TUD_OPT_HIGH_SPEED
}