Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion subsys/usb/device_next/class/usbd_cdc_acm.c
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ static void usbd_cdc_acm_disable(struct usbd_class_data *const c_data)

atomic_clear_bit(&data->state, CDC_ACM_CLASS_ENABLED);
atomic_clear_bit(&data->state, CDC_ACM_CLASS_SUSPENDED);
/* Clear TX_FIFO_BUSY on disable so a stuck IN transfer does not block
* the TX path after the host reconnects and re-enables the class.
*/
atomic_clear_bit(&data->state, CDC_ACM_TX_FIFO_BUSY);
LOG_INF("Configuration disabled");
}

Expand Down Expand Up @@ -653,6 +657,13 @@ static void cdc_acm_tx_fifo_handler(struct k_work *work)

if (atomic_test_and_set_bit(&data->state, CDC_ACM_TX_FIFO_BUSY)) {
LOG_DBG("TX transfer already in progress");
/* Reschedule if data is waiting - guards against a stuck IN transfer
* where the host stops issuing IN tokens (e.g. macOS flow-control).
* The retry is a no-op once the completion fires and clears BUSY.
*/
if (!ring_buf_is_empty(data->tx_fifo.rb)) {
cdc_acm_work_schedule(&data->tx_fifo_work, K_MSEC(10));
}
return;
}

Expand Down Expand Up @@ -998,6 +1009,7 @@ static void cdc_acm_poll_out(const struct device *dev, const unsigned char c)
struct cdc_acm_uart_data *const data = dev->data;
k_spinlock_key_t key;
uint32_t wrote;
int retries = 20;

while (true) {
key = k_spin_lock(&data->lock);
Expand All @@ -1008,7 +1020,15 @@ static void cdc_acm_poll_out(const struct device *dev, const unsigned char c)
break;
}

if (k_is_in_isr() || !data->flow_ctrl) {
/* Bounded wait: with an attached-but-stalled host session (macOS
* ceases IN polling for minutes at a time), an unbounded sleep-retry
* here makes every console write take the full stall duration. Any
* thread that logs (event text loggers, assert reporting) then backs
* up its own queues and cascades into a system-wide com livelock.
* After ~20 ms of backpressure, treat the console as best-effort and
* discard, exactly like the detached (!flow_ctrl) case below.
*/
if (k_is_in_isr() || !data->flow_ctrl || retries-- <= 0) {
LOG_WRN_ONCE("Ring buffer full, discard data");
break;
}
Expand Down
Loading