From 955c68bcf06b2926153d34eba6aeebe8adc83cd2 Mon Sep 17 00:00:00 2001 From: saikumar-mandaji Date: Tue, 8 Sep 2026 00:02:53 +0530 Subject: [PATCH] fix(Serial): Uart::write() must not spin when begin() failed If Serial.begin(baud) fails during hardware init (e.g. an LPUART clocked from LSE can't reach the requested baud rate), uart_init() returns false and _ready is set to false -- but Uart::write() never checks _ready before entering its transmit path. Uart::write(const uint8_t*, size_t) contains: while (!availableForWrite()) { // nop, the interrupt handler will free up space for us } Since the hardware was never actually brought up, the TX interrupt this loop waits on never fires, so once the 63-byte TX ring buffer fills up (a few Serial.print() calls after a failed begin()), this spins forever and the MCU deadlocks permanently. Fix: return 0 immediately if !_ready, before touching the buffer or entering the wait loop, using the same _ready accessor already used elsewhere in this file (see Uart::begin(), which sets it, and the existing operator bool()-style accessor in Serial.h). write(uint8_t) needs no separate guard since it already delegates to this overload. Fixes #3071 --- cores/arduino/Serial.cpp | 105 +++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/cores/arduino/Serial.cpp b/cores/arduino/Serial.cpp index 6b022d22c6..6699ce2e03 100644 --- a/cores/arduino/Serial.cpp +++ b/cores/arduino/Serial.cpp @@ -583,62 +583,69 @@ void Uart::flush(uint32_t timeout) size_t Uart::write(const uint8_t *buffer, size_t size) { - size_t size_intermediate; size_t ret = size; - size_t available = availableForWrite(); - size_t available_till_buffer_end = SERIAL_TX_BUFFER_SIZE - _serial.tx_head; - - _written = true; - if (isHalfDuplex()) { - if (_rx_enabled) { - _rx_enabled = false; - uart_enable_tx(&_serial); + // If begin() never brought the hardware up (e.g. the requested baud + // rate can't be reached from the configured kernel clock), the TX + // interrupt this function waits on below will never fire. Bail out + // instead of spinning forever once the TX buffer fills up. + if (!_ready) { + ret = 0; + } else { + size_t size_intermediate; + size_t available = availableForWrite(); + size_t available_till_buffer_end = SERIAL_TX_BUFFER_SIZE - _serial.tx_head; + + _written = true; + if (isHalfDuplex()) { + if (_rx_enabled) { + _rx_enabled = false; + uart_enable_tx(&_serial); + } } - } - // If the output buffer is full, there's nothing for it other than to - // wait for the interrupt handler to free space - while (!availableForWrite()) { - // nop, the interrupt handler will free up space for us - } + // If the output buffer is full, there's nothing for it other than to + // wait for the interrupt handler to free space + while (!availableForWrite()) { + // nop, the interrupt handler will free up space for us + } - // HAL doesn't manage rollover, so split transfer till end of TX buffer - // Also, split transfer according to available space in buffer - while ((size > available_till_buffer_end) || (size > available)) { - size_intermediate = min(available, available_till_buffer_end); - write(buffer, size_intermediate); - size -= size_intermediate; - buffer += size_intermediate; - available = availableForWrite(); - available_till_buffer_end = SERIAL_TX_BUFFER_SIZE - _serial.tx_head; - } + // HAL doesn't manage rollover, so split transfer till end of TX buffer + // Also, split transfer according to available space in buffer + while ((size > available_till_buffer_end) || (size > available)) { + size_intermediate = min(available, available_till_buffer_end); + write(buffer, size_intermediate); + size -= size_intermediate; + buffer += size_intermediate; + available = availableForWrite(); + available_till_buffer_end = SERIAL_TX_BUFFER_SIZE - _serial.tx_head; + } - // Copy data to buffer. Take into account rollover if necessary. - if (_serial.tx_head + size <= SERIAL_TX_BUFFER_SIZE) { - memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size); - size_intermediate = size; - } else { - // memcpy till end of buffer then continue memcpy from beginning of buffer - size_intermediate = SERIAL_TX_BUFFER_SIZE - _serial.tx_head; - memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size_intermediate); - memcpy(&_serial.tx_buff[0], buffer + size_intermediate, - size - size_intermediate); - } + // Copy data to buffer. Take into account rollover if necessary. + if (_serial.tx_head + size <= SERIAL_TX_BUFFER_SIZE) { + memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size); + size_intermediate = size; + } else { + // memcpy till end of buffer then continue memcpy from beginning of buffer + size_intermediate = SERIAL_TX_BUFFER_SIZE - _serial.tx_head; + memcpy(&_serial.tx_buff[_serial.tx_head], buffer, size_intermediate); + memcpy(&_serial.tx_buff[0], buffer + size_intermediate, + size - size_intermediate); + } - // Data are copied to buffer, move head pointer accordingly - _serial.tx_head = (_serial.tx_head + size) % SERIAL_TX_BUFFER_SIZE; - - // Transfer data with HAL only is there is no TX data transfer ongoing - // otherwise, data transfer will be done asynchronously from callback - if (!serial_tx_active(&_serial)) { - // note: tx_size correspond to size of HAL data transfer, - // not the total amount of data in the buffer. - // To compute size of data in buffer compare head and tail - _serial.tx_size = size_intermediate; - uart_attach_tx_callback(&_serial, _tx_complete_irq, size_intermediate); + // Data are copied to buffer, move head pointer accordingly + _serial.tx_head = (_serial.tx_head + size) % SERIAL_TX_BUFFER_SIZE; + + // Transfer data with HAL only is there is no TX data transfer ongoing + // otherwise, data transfer will be done asynchronously from callback + if (!serial_tx_active(&_serial)) { + // note: tx_size correspond to size of HAL data transfer, + // not the total amount of data in the buffer. + // To compute size of data in buffer compare head and tail + _serial.tx_size = size_intermediate; + uart_attach_tx_callback(&_serial, _tx_complete_irq, size_intermediate); + } } - - /* There is no real error management so just return transfer size requested*/ + /* Return 0 if not ready else requested transfer size */ return ret; }