Fix flush hanging issue

Make write to UDR and clearing of TXC bit in flush() atomic
to avoid race condition.

Fixes #3745 (second different issue introduced later but discussed
in the same issue)
This commit is contained in:
John Holman 2017-10-25 11:07:35 +01:00 committed by Martino Facchin
parent 99c294c5a2
commit 58006613a7
1 changed files with 12 additions and 2 deletions

View File

@ -226,8 +226,18 @@ size_t HardwareSerial::write(uint8_t c)
// significantly improve the effective datarate at high (>
// 500kbit/s) bitrates, where interrupt overhead becomes a slowdown.
if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) {
*_udr = c;
*_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0);
// If TXC is cleared before writing UDR and the previous byte
// completes before writing to UDR, TXC will be set but a byte
// is still being transmitted causing flush() to return too soon.
// So writing UDR must happen first.
// Writing UDR and clearing TC must be done atomically, otherwise
// interrupts might delay the TXC clear so the byte written to UDR
// is transmitted (setting TXC) before clearing TXC. Then TXC will
// be cleared when no bytes are left, causing flush() to hang
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
*_udr = c;
*_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0);
}
return 1;
}
tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE;