Files
esphome/usb_callback_contexts.md
J. Nick Koston 32797534a7 propsals
2025-11-07 22:04:58 -06:00

3.3 KiB

USB Host Component - Callback Execution Contexts

Overview

After the refactoring to use a dedicated USB task, all USB callbacks now execute in the USB task context, NOT in the main loop. This prevents race conditions and data corruption.

USB Task Architecture

// USB Task (runs on Core 1, priority 5)
void USBClient::usb_task_loop() {
  while (usb_task_running_) {
    // This handles ALL USB events and triggers callbacks
    usb_host_client_handle_events(handle_, pdMS_TO_TICKS(10));
  }
}

Callback Execution Contexts

1. client_event_cb (Device Connect/Disconnect)

  • Context: USB task
  • Triggered by: USB device connection/disconnection events
  • What it does: Queues events to main loop via FreeRTOS queue
  • Data flow: USB hardware → USB task → Queue → Main loop

2. control_callback (Control Transfers)

  • Context: USB task
  • Triggered by: Completion of USB control transfers
  • What it does: Queues callback execution to main loop
  • Data flow: USB hardware → USB task → Queue → Main loop

3. transfer_callback (Bulk Transfers)

  • Context: USB task
  • Triggered by: Completion of bulk IN/OUT transfers
  • What it does: Queues callback execution to main loop
  • Data flow: USB hardware → USB task → Queue → Main loop

4. USBUartComponent Input Callback (Data Reception)

  • Context: USB task (lambda passed to transfer_in)
  • Called from: transfer_callback in USB task
  • What it does:
    • Copies received data to temporary buffer
    • Queues data processing to main loop via defer()
    • Main loop then safely pushes to ring buffer
  • Data flow: USB hardware → USB task → Copy data → Queue → Main loop → Ring buffer

5. USBUartComponent Output Callback (Data Transmission)

  • Context: USB task (lambda passed to transfer_out)
  • Called from: transfer_callback in USB task
  • What it does:
    • Marks output as not started
    • Queues next transfer start to main loop via defer()
  • Data flow: Main loop reads ring buffer → Start transfer → USB task → Queue restart → Main loop

Thread Safety Summary

Safe Operations (Main Loop Only):

  • Ring buffer push (incoming data)
  • Ring buffer pop (outgoing data)
  • Component state changes
  • Transfer initiation

USB Task Operations:

  • USB event handling
  • Transfer completion detection
  • Event queueing to main loop

Communication Mechanism:

  • FreeRTOS Queue: For USB events and callbacks
  • defer(): For component-specific operations

Why This Architecture?

  1. Prevents data loss: USB events are handled promptly even when main loop is busy
  2. Thread safety: Ring buffer is only accessed from main loop
  3. No race conditions: Data structures aren't shared between tasks
  4. Maintains responsiveness: USB hardware FIFOs don't overflow

Key Changes from Original Implementation

Before (Problematic):

  • usb_host_client_handle_events() called in main loop
  • Callbacks executed in main loop context
  • USB events dropped when main loop was busy
  • Ring buffer corruption when data arrived during slow processing

After (Fixed):

  • usb_host_client_handle_events() runs in dedicated task
  • Callbacks execute in USB task, queue work to main loop
  • USB events always handled promptly
  • Ring buffer only accessed from main loop (thread-safe)