mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'integration' into memory_api
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