Skip to content

Commit e1599f3

Browse files
Make all access to the HardwareSerial ringbuffers atomic
This prevents a potential race condition where for example write() would detect that there is room in the tx buffer, an interrupt would trigger where the interrupt handler writes to Serial filling up that room, and write() would then reset the head pointer, effectively throwing away the bytes written by the interrupt handler. References: arduino#1147
1 parent aa584bc commit e1599f3

File tree

1 file changed

+56
-34
lines changed

1 file changed

+56
-34
lines changed

Diff for: hardware/arduino/avr/cores/arduino/HardwareSerial.cpp

+56-34
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include <stdio.h>
2626
#include <string.h>
2727
#include <inttypes.h>
28+
#include <util/atomic.h>
29+
#include <avr/cpufunc.h>
2830
#include "Arduino.h"
2931
#include "wiring_private.h"
3032

@@ -245,27 +247,33 @@ void HardwareSerial::end()
245247

246248
int HardwareSerial::available(void)
247249
{
248-
return (unsigned int)(SERIAL_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail) % SERIAL_BUFFER_SIZE;
250+
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
251+
return (unsigned int)(SERIAL_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail) % SERIAL_BUFFER_SIZE;
252+
}
249253
}
250254

251255
int HardwareSerial::peek(void)
252256
{
253-
if (_rx_buffer_head == _rx_buffer_tail) {
254-
return -1;
255-
} else {
256-
return _rx_buffer[_rx_buffer_tail];
257+
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
258+
if (_rx_buffer_head == _rx_buffer_tail) {
259+
return -1;
260+
} else {
261+
return _rx_buffer[_rx_buffer_tail];
262+
}
257263
}
258264
}
259265

260266
int HardwareSerial::read(void)
261267
{
262-
// if the head isn't ahead of the tail, we don't have any characters
263-
if (_rx_buffer_head == _rx_buffer_tail) {
264-
return -1;
265-
} else {
266-
unsigned char c = _rx_buffer[_rx_buffer_tail];
267-
_rx_buffer_tail = (unsigned int)(_rx_buffer_tail + 1) % SERIAL_BUFFER_SIZE;
268-
return c;
268+
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
269+
// if the head isn't ahead of the tail, we don't have any characters
270+
if (_rx_buffer_head == _rx_buffer_tail) {
271+
return -1;
272+
} else {
273+
unsigned char c = _rx_buffer[_rx_buffer_tail];
274+
_rx_buffer_tail = (unsigned int)(_rx_buffer_tail + 1) % SERIAL_BUFFER_SIZE;
275+
return c;
276+
}
269277
}
270278
}
271279

@@ -291,30 +299,44 @@ void HardwareSerial::flush()
291299

292300
size_t HardwareSerial::write(uint8_t c)
293301
{
294-
int i = (_tx_buffer_head + 1) % SERIAL_BUFFER_SIZE;
295-
296-
// If the output buffer is full, there's nothing for it other than to
297-
// wait for the interrupt handler to empty it a bit
298-
while (i == _tx_buffer_tail) {
299-
if (bit_is_clear(SREG, SREG_I)) {
300-
// Interrupts are disabled, so we'll have to poll the data
301-
// register empty flag ourselves. If it is set, pretend an
302-
// interrupt has happened and call the handler to free up
303-
// space for us.
304-
if(bit_is_set(*_ucsra, UDRE0))
305-
_tx_udr_empty_irq();
306-
} else {
307-
// nop, the interrupt handler will free up space for us
302+
bool interrupts_enabled = bit_is_set(SREG, SREG_I);
303+
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
304+
int i = (_tx_buffer_head + 1) % SERIAL_BUFFER_SIZE;
305+
306+
// If the output buffer is full, there's nothing for it other than to
307+
// wait for the interrupt handler to empty it a bit
308+
while (i == _tx_buffer_tail) {
309+
if (!interrupts_enabled) {
310+
// Interrupts were disabled at the start of this function, so we
311+
// can't just enable them (that would allow other interrupts to
312+
// trigger as well!). We'll have to poll the data register empty
313+
// flag ourselves. If it is set, pretend an interrupt has happened
314+
// and call the handler to free up space for us.
315+
if(bit_is_set(*_ucsra, UDRE0))
316+
_tx_udr_empty_irq();
317+
} else {
318+
// Interrupts were enabled, so we can temporarily enable them so
319+
// our tx interrupt can fire and free up some space. Note that we
320+
// can't just use the UDRE polling approach here instead of
321+
// enabling interrupts, since that would prevent other
322+
// interrupts from triggering while we wait for room.
323+
NONATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
324+
_NOP(); // Any pending interrupts will trigger here
325+
}
326+
// Recalculate i, since other interrupt handlers might have
327+
// written to our buffer
328+
i = (_tx_buffer_head + 1) % SERIAL_BUFFER_SIZE;
329+
}
308330
}
309-
}
310331

311-
_tx_buffer[_tx_buffer_head] = c;
312-
_tx_buffer_head = i;
313-
314-
sbi(*_ucsrb, UDRIE0);
315-
_written = true;
316-
317-
return 1;
332+
_tx_buffer[_tx_buffer_head] = c;
333+
_tx_buffer_head = i;
334+
335+
sbi(*_ucsrb, UDRIE0);
336+
_written = true;
337+
338+
return 1;
339+
}
318340
}
319341

320342
HardwareSerial::operator bool() {

0 commit comments

Comments
 (0)