mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +00:00
merge
This commit is contained in:
@@ -169,98 +169,6 @@ bool USBUartChannel::read_array(uint8_t *data, size_t len) {
|
|||||||
this->parent_->start_input(this);
|
this->parent_->start_input(this);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
void USBUartComponent::reset_input_state_(USBUartChannel *channel) {
|
|
||||||
channel->input_retry_count_.store(0);
|
|
||||||
channel->input_started_.store(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void USBUartComponent::restart_input_(USBUartChannel *channel) {
|
|
||||||
// Atomically verify it's still started (true) and keep it started
|
|
||||||
// This prevents the race window of toggling true->false->true
|
|
||||||
bool expected = true;
|
|
||||||
if (channel->input_started_.compare_exchange_strong(expected, true)) {
|
|
||||||
// Still started - do the actual restart work without toggling the flag
|
|
||||||
this->do_start_input_(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void USBUartComponent::input_transfer_callback_(USBUartChannel *channel, const usb_host::TransferStatus &status) {
|
|
||||||
// CALLBACK CONTEXT: This function is executed in USB task via transfer_callback
|
|
||||||
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
|
|
||||||
|
|
||||||
if (!status.success) {
|
|
||||||
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
|
|
||||||
// Transfer failed, slot already released
|
|
||||||
// Reset state so normal operations can restart later
|
|
||||||
this->reset_input_state_(channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!channel->dummy_receiver_ && status.data_len > 0) {
|
|
||||||
// Allocate a chunk from the pool
|
|
||||||
UsbDataChunk *chunk = this->chunk_pool_.allocate();
|
|
||||||
if (chunk == nullptr) {
|
|
||||||
// No chunks available - queue is full, data dropped, slot already released
|
|
||||||
this->usb_data_queue_.increment_dropped_count();
|
|
||||||
// Reset state so normal operations can restart later
|
|
||||||
this->reset_input_state_(channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy data to chunk (this is fast, happens in USB task)
|
|
||||||
memcpy(chunk->data, status.data, status.data_len);
|
|
||||||
chunk->length = status.data_len;
|
|
||||||
chunk->channel = channel;
|
|
||||||
|
|
||||||
// Push to lock-free queue for main loop processing
|
|
||||||
// Push always succeeds because pool size == queue size
|
|
||||||
this->usb_data_queue_.push(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// On success, reset retry count and restart input immediately from USB task for performance
|
|
||||||
// The lock-free queue will handle backpressure
|
|
||||||
channel->input_retry_count_.store(0);
|
|
||||||
channel->input_started_.store(false);
|
|
||||||
this->start_input(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void USBUartComponent::do_start_input_(USBUartChannel *channel) {
|
|
||||||
// This function does the actual work of starting input
|
|
||||||
// Caller must ensure input_started_ is already set to true
|
|
||||||
const auto *ep = channel->cdc_dev_.in_ep;
|
|
||||||
|
|
||||||
// input_started_ already set to true by caller
|
|
||||||
auto result = this->transfer_in(
|
|
||||||
ep->bEndpointAddress,
|
|
||||||
[this, channel](const usb_host::TransferStatus &status) { this->input_transfer_callback_(channel, status); },
|
|
||||||
ep->wMaxPacketSize);
|
|
||||||
|
|
||||||
if (result == usb_host::TRANSFER_ERROR_NO_SLOTS) {
|
|
||||||
// No slots available - defer retry to main loop
|
|
||||||
this->defer_input_retry_(channel);
|
|
||||||
} else if (result != usb_host::TRANSFER_OK) {
|
|
||||||
// Other error (submit failed) - don't retry, just reset state
|
|
||||||
// Error already logged by transfer_in()
|
|
||||||
this->reset_input_state_(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void USBUartComponent::defer_input_retry_(USBUartChannel *channel) {
|
|
||||||
static constexpr uint8_t MAX_INPUT_RETRIES = 10;
|
|
||||||
|
|
||||||
// Atomically increment and get the NEW value (previous + 1)
|
|
||||||
uint8_t new_retry_count = channel->input_retry_count_.fetch_add(1) + 1;
|
|
||||||
if (new_retry_count > MAX_INPUT_RETRIES) {
|
|
||||||
ESP_LOGE(TAG, "Input retry limit reached for channel %d, stopping retries", channel->index_);
|
|
||||||
this->reset_input_state_(channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep input_started_ as true during defer to prevent multiple retries from queueing
|
|
||||||
// The deferred lambda will atomically restart
|
|
||||||
this->defer([this, channel] { this->restart_input_(channel); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void USBUartComponent::setup() { USBClient::setup(); }
|
void USBUartComponent::setup() { USBClient::setup(); }
|
||||||
void USBUartComponent::loop() {
|
void USBUartComponent::loop() {
|
||||||
USBClient::loop();
|
USBClient::loop();
|
||||||
@@ -308,12 +216,6 @@ void USBUartComponent::dump_config() {
|
|||||||
void USBUartComponent::start_input(USBUartChannel *channel) {
|
void USBUartComponent::start_input(USBUartChannel *channel) {
|
||||||
if (!channel->initialised_.load())
|
if (!channel->initialised_.load())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Atomically check if not started and set to started in one operation
|
|
||||||
bool expected = false;
|
|
||||||
if (!channel->input_started_.compare_exchange_strong(expected, true))
|
|
||||||
return; // Already started - prevents duplicate transfers from concurrent threads
|
|
||||||
|
|
||||||
// THREAD CONTEXT: Called from both USB task and main loop threads
|
// THREAD CONTEXT: Called from both USB task and main loop threads
|
||||||
// - USB task: Immediate restart after successful transfer for continuous data flow
|
// - USB task: Immediate restart after successful transfer for continuous data flow
|
||||||
// - Main loop: Controlled restart after consuming data (backpressure mechanism)
|
// - Main loop: Controlled restart after consuming data (backpressure mechanism)
|
||||||
@@ -324,11 +226,6 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
|
|||||||
//
|
//
|
||||||
// The underlying transfer_in() uses lock-free atomic allocation from the
|
// The underlying transfer_in() uses lock-free atomic allocation from the
|
||||||
// TransferRequest pool, making this multi-threaded access safe
|
// TransferRequest pool, making this multi-threaded access safe
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
// Do the actual work (input_started_ already set to true by CAS above)
|
|
||||||
this->do_start_input_(channel);
|
|
||||||
=======
|
|
||||||
|
|
||||||
// if already started, don't restart. A spurious failure in compare_exchange_weak
|
// if already started, don't restart. A spurious failure in compare_exchange_weak
|
||||||
// is not a problem, as it will be retried on the next read_array()
|
// is not a problem, as it will be retried on the next read_array()
|
||||||
@@ -375,7 +272,6 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
|
|||||||
if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
|
if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
|
||||||
channel->input_started_.store(false);
|
channel->input_started_.store(false);
|
||||||
}
|
}
|
||||||
>>>>>>> clydebarrow/usb-uart
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void USBUartComponent::start_output(USBUartChannel *channel) {
|
void USBUartComponent::start_output(USBUartChannel *channel) {
|
||||||
@@ -482,7 +378,7 @@ void USBUartTypeCdcAcm::enable_channels() {
|
|||||||
for (auto *channel : this->channels_) {
|
for (auto *channel : this->channels_) {
|
||||||
if (!channel->initialised_.load())
|
if (!channel->initialised_.load())
|
||||||
continue;
|
continue;
|
||||||
this->reset_input_state_(channel);
|
channel->input_started_.store(false);
|
||||||
channel->output_started_.store(false);
|
channel->output_started_.store(false);
|
||||||
this->start_input(channel);
|
this->start_input(channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,11 +111,10 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
|
|||||||
CdcEps cdc_dev_{};
|
CdcEps cdc_dev_{};
|
||||||
// Enum (likely 4 bytes)
|
// Enum (likely 4 bytes)
|
||||||
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
|
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
|
||||||
// Group atomics together
|
// Group atomics together (each 1 byte)
|
||||||
std::atomic<bool> input_started_{true};
|
std::atomic<bool> input_started_{true};
|
||||||
std::atomic<bool> output_started_{true};
|
std::atomic<bool> output_started_{true};
|
||||||
std::atomic<bool> initialised_{false};
|
std::atomic<bool> initialised_{false};
|
||||||
std::atomic<uint8_t> input_retry_count_{0};
|
|
||||||
// Group regular bytes together to minimize padding
|
// Group regular bytes together to minimize padding
|
||||||
const uint8_t index_;
|
const uint8_t index_;
|
||||||
bool debug_{};
|
bool debug_{};
|
||||||
@@ -141,11 +140,6 @@ class USBUartComponent : public usb_host::USBClient {
|
|||||||
EventPool<UsbDataChunk, USB_DATA_QUEUE_SIZE> chunk_pool_;
|
EventPool<UsbDataChunk, USB_DATA_QUEUE_SIZE> chunk_pool_;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void defer_input_retry_(USBUartChannel *channel);
|
|
||||||
void reset_input_state_(USBUartChannel *channel);
|
|
||||||
void restart_input_(USBUartChannel *channel);
|
|
||||||
void do_start_input_(USBUartChannel *channel);
|
|
||||||
void input_transfer_callback_(USBUartChannel *channel, const usb_host::TransferStatus &status);
|
|
||||||
std::vector<USBUartChannel *> channels_{};
|
std::vector<USBUartChannel *> channels_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user