[tinyusb] Reject tinyusb: configured without a USB class companion (#16413)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Keith Burzinski
2026-05-14 15:07:38 -05:00
committed by Jesse Hills
parent 84b5931299
commit ab273a1f8f
3 changed files with 40 additions and 0 deletions
+20
View File
@@ -1,3 +1,4 @@
from esphome import final_validate as fv
import esphome.codegen as cg
from esphome.components import esp32
from esphome.components.esp32 import (
@@ -20,6 +21,13 @@ CONF_USB_PRODUCT_STR = "usb_product_str"
CONF_USB_SERIAL_STR = "usb_serial_str"
CONF_USB_VENDOR_ID = "usb_vendor_id"
# Components that provide a USB device class (CDC, HID, MSC, ...) on top of
# tinyusb. Configuring `tinyusb:` without any of these triggers a 5s hang in
# esp_tinyusb's driver install (descriptors_set fails with no class and no
# user-provided full_speed_config), which trips the task watchdog before
# loop() ever runs.
_USB_CLASS_COMPONENTS = ("usb_cdc_acm",)
tinyusb_ns = cg.esphome_ns.namespace("tinyusb")
TinyUSB = tinyusb_ns.class_("TinyUSB", cg.Component)
@@ -41,6 +49,18 @@ CONFIG_SCHEMA = cv.All(
)
def _final_validate(config):
full_config = fv.full_config.get()
if not any(name in full_config for name in _USB_CLASS_COMPONENTS):
raise cv.Invalid(
"The 'tinyusb' component requires at least one USB class component"
)
return config
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -26,6 +26,21 @@ void TinyUSB::setup() {
.string_count = SIZE,
};
// Defense-in-depth: esp_tinyusb's tinyusb_descriptors_set() fails with
// ESP_ERR_INVALID_ARG when no configuration descriptor is provided and
// no class that has a built-in default (CDC/MSC/NCM) is compiled in. In
// that case the internal task exits without notifying us, and
// tinyusb_driver_install() blocks 5s on the notify-take -- long enough
// to trip the task watchdog. Bail early so the rest of the device can
// still boot.
#if !(CFG_TUD_CDC > 0 || CFG_TUD_MSC > 0 || CFG_TUD_NCM > 0)
if (this->tusb_cfg_.descriptor.full_speed_config == nullptr) {
ESP_LOGE(TAG, "No USB class configured");
this->mark_failed();
return;
}
#endif
esp_err_t result = tinyusb_driver_install(&this->tusb_cfg_);
if (result != ESP_OK) {
ESP_LOGE(TAG, "tinyusb_driver_install failed: %s", esp_err_to_name(result));
+5
View File
@@ -6,3 +6,8 @@ tinyusb:
usb_product_str: ESPHomeTestProduct
usb_serial_str: ESPHomeTestSerialNumber
usb_vendor_id: 0x2345
# tinyusb requires at least one USB class companion; usb_cdc_acm satisfies that.
usb_cdc_acm:
interfaces:
- id: tinyusb_test_cdc