diff --git a/subsys/usb/device_next/class/usbd_cdc_acm.c b/subsys/usb/device_next/class/usbd_cdc_acm.c index b20115a60f38..0d3051af66e1 100644 --- a/subsys/usb/device_next/class/usbd_cdc_acm.c +++ b/subsys/usb/device_next/class/usbd_cdc_acm.c @@ -1009,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); @@ -1019,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; }