Skip to content

Commit 5304383

Browse files
Avoid "chunkiness" of UART FIFO availability (#511)
* Avoid "chunkiness" of UART FIFO availability The UART FIFO will generate an IRQ to transfer data into the SerialUART FIFOs either every 4 received bytes, or every 4 idle byte times. This causes the ::available count to report "0" until either of those two cases happen, causing a potentially delay in data becoming available to the app. Change the code to pull data from the HW FIFO on a read/available/peek. Use a non-blocking mutex and IRQ disabling to safely empty the FIFO from user space. The mutex added to the IRQ is non-blocking and will be a single CAS the vast majority of the time, so it should not impact the Serial performance. Fixes #464 and others where `setPollingMode()` was needed as a workaround. Make sure we have all mutexes locked before we disable the port and free the queue to avoid evil cases. Only init the mutexes once, on object creation. In polled mode, don't bother acquiring/releasing the fifo mutex. When begin() is called on an already running port, call end() to clean up the old data/etc. before making a new queue/config. This avoids a memory leak and potential write-after-free case.
1 parent 8deb47f commit 5304383

File tree

2 files changed

+54
-11
lines changed

2 files changed

+54
-11
lines changed

cores/rp2040/SerialUART.cpp

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,16 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) {
121121
_rts = UART_PIN_NOT_DEFINED;
122122
_cts = UART_PIN_NOT_DEFINED;
123123
mutex_init(&_mutex);
124+
mutex_init(&_fifoMutex);
124125
}
125126

126127
static void _uart0IRQ();
127128
static void _uart1IRQ();
128129

129130
void SerialUART::begin(unsigned long baud, uint16_t config) {
131+
if (_running) {
132+
end();
133+
}
130134
_queue = new uint8_t[_fifoSize];
131135
_baud = baud;
132136
uart_init(_uart, baud);
@@ -198,16 +202,36 @@ void SerialUART::end() {
198202
if (!_running) {
199203
return;
200204
}
205+
_running = false;
201206
if (!_polling) {
202207
if (_uart == uart0) {
203208
irq_set_enabled(UART0_IRQ, false);
204209
} else {
205210
irq_set_enabled(UART1_IRQ, false);
206211
}
207212
}
213+
// Paranoia - ensure nobody else is using anything here at the same time
214+
mutex_enter_blocking(&_mutex);
215+
mutex_enter_blocking(&_fifoMutex);
208216
uart_deinit(_uart);
209217
delete[] _queue;
210-
_running = false;
218+
// Reset the mutexes once all is off/cleaned up
219+
mutex_exit(&_fifoMutex);
220+
mutex_exit(&_mutex);
221+
}
222+
223+
void SerialUART::_pumpFIFO() {
224+
// Use the _fifoMutex to guard against the other core potentially
225+
// running the IRQ (since we can't disable their IRQ handler).
226+
// We guard against this core by disabling the IRQ handler and
227+
// re-enabling if it was previously enabled at the end.
228+
auto irqno = (_uart == uart0) ? UART0_IRQ : UART1_IRQ;
229+
bool enabled = irq_is_enabled(irqno);
230+
irq_set_enabled(irqno, false);
231+
mutex_enter_blocking(&_fifoMutex);
232+
_handleIRQ(false);
233+
mutex_exit(&_fifoMutex);
234+
irq_set_enabled(irqno, enabled);
211235
}
212236

213237
int SerialUART::peek() {
@@ -216,7 +240,9 @@ int SerialUART::peek() {
216240
return -1;
217241
}
218242
if (_polling) {
219-
_handleIRQ();
243+
_handleIRQ(false);
244+
} else {
245+
_pumpFIFO();
220246
}
221247
if (_writer != _reader) {
222248
return _queue[_reader];
@@ -230,7 +256,9 @@ int SerialUART::read() {
230256
return -1;
231257
}
232258
if (_polling) {
233-
_handleIRQ();
259+
_handleIRQ(false);
260+
} else {
261+
_pumpFIFO();
234262
}
235263
if (_writer != _reader) {
236264
auto ret = _queue[_reader];
@@ -249,7 +277,9 @@ int SerialUART::available() {
249277
return 0;
250278
}
251279
if (_polling) {
252-
_handleIRQ();
280+
_handleIRQ(false);
281+
} else {
282+
_pumpFIFO();
253283
}
254284
return (_writer - _reader) % _fifoSize;
255285
}
@@ -260,7 +290,7 @@ int SerialUART::availableForWrite() {
260290
return 0;
261291
}
262292
if (_polling) {
263-
_handleIRQ();
293+
_handleIRQ(false);
264294
}
265295
return (uart_is_writable(_uart)) ? 1 : 0;
266296
}
@@ -271,7 +301,7 @@ void SerialUART::flush() {
271301
return;
272302
}
273303
if (_polling) {
274-
_handleIRQ();
304+
_handleIRQ(false);
275305
}
276306
uart_tx_wait_blocking(_uart);
277307
}
@@ -282,7 +312,7 @@ size_t SerialUART::write(uint8_t c) {
282312
return 0;
283313
}
284314
if (_polling) {
285-
_handleIRQ();
315+
_handleIRQ(false);
286316
}
287317
uart_putc_raw(_uart, c);
288318
return 1;
@@ -294,7 +324,7 @@ size_t SerialUART::write(const uint8_t *p, size_t len) {
294324
return 0;
295325
}
296326
if (_polling) {
297-
_handleIRQ();
327+
_handleIRQ(false);
298328
}
299329
size_t cnt = len;
300330
while (cnt) {
@@ -325,7 +355,15 @@ void arduino::serialEvent2Run(void) {
325355
}
326356

327357
// IRQ handler, called when FIFO > 1/8 full or when it had held unread data for >32 bit times
328-
void __not_in_flash_func(SerialUART::_handleIRQ)() {
358+
void __not_in_flash_func(SerialUART::_handleIRQ)(bool inIRQ) {
359+
if (inIRQ) {
360+
uint32_t owner;
361+
if (!mutex_try_enter(&_fifoMutex, &owner)) {
362+
// Main app on the other core has the mutex so it is
363+
// in the process of pulling data out of the HW FIFO
364+
return;
365+
}
366+
}
329367
// ICR is write-to-clear
330368
uart_get_hw(_uart)->icr = UART_UARTICR_RTIC_BITS | UART_UARTICR_RXIC_BITS;
331369
while (uart_is_readable(_uart)) {
@@ -343,6 +381,9 @@ void __not_in_flash_func(SerialUART::_handleIRQ)() {
343381
// TODO: Overflow
344382
}
345383
}
384+
if (inIRQ) {
385+
mutex_exit(&_fifoMutex);
386+
}
346387
}
347388

348389
static void __not_in_flash_func(_uart0IRQ)() {

cores/rp2040/SerialUART.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class SerialUART : public HardwareSerial {
6363
operator bool() override;
6464

6565
// Not to be called by users, only from the IRQ handler. In public so that the C-language IQR callback can access it
66-
void _handleIRQ();
66+
void _handleIRQ(bool inIRQ = true);
6767

6868
private:
6969
bool _running = false;
@@ -78,7 +78,9 @@ class SerialUART : public HardwareSerial {
7878
uint32_t _writer;
7979
uint32_t _reader;
8080
size_t _fifoSize = 32;
81-
uint8_t *_queue;
81+
uint8_t *_queue;
82+
mutex_t _fifoMutex; // Only needed when non-IRQ updates _writer
83+
void _pumpFIFO(); // User space FIFO transfer
8284
};
8385

8486
extern SerialUART Serial1; // HW UART 0

0 commit comments

Comments
 (0)