Description
I am working with a MKRNB1500 with MKRNB. Sometimes I see what appears to be an incomplete or no response from the SARA modem. The interface to the SARA modem is a UART serial interface.
I started looking at the UART interface code and I think I have identified a possible race condition between the Uart::write(data) method and the Uart::IrqHandler(). Here is the sequence:
- Upon completion of a string write operation to the UART interface that uses the transmit RingBuffer the UDRE bit is set after the write of the last char removed from the transmit RingBuffer an written to the UART interface. Also, the UDRE interrupts are disabled in the Uart::IrqHandler() when there are no more characters in the transmit RingBuffer (see line 117 in Uart.cpp). This is normal operation.
- The next time Uart::write(data) is called the if logic at line 160 of Uart.cpp is executed because URDE is set and the transmit RingBuffer is empty. The write of the char to the UDR register clears the UDRE bit.
- If Uart::write(data) is called again before the UDRE bit has been set (i.e. before the char has been moved to the Transmit Shift Register), the else logic is executed.
- Assume that BEFORE the else logic can store the character in the RingBuffer (line 185 in Uart.cpp) the UDRE bit is set AND the Uart: IrqHandler() is driven by the TXC interrupt. Note that UDRE interrupts are still disabled so we don’t get a UDRE interrupt.
- The Uart::IrqHandler() suspends the Uart::write() processing and even though the UDRE bit is set it doesn’t find anything on the transmit RingBuffer because of step 4.
- The Uart::write() else logic resumes after the Uart::IrqHandler has exited and adds the char to the transmit RingBuffer and enables UDRE interrupts. Unfortunately, this char and all subsequent write chars are stuck on the transmit RingBuffer until another interrupt is generated for the Uart::IrqHandler(), e.g. an RXC.
I am not sure how the SARA modem would react to an incomplete AT command but it probably isn’t good.
In order to avoid a problem like this it seems like it would be better to first add the write data to the transmit RingBuffer and then use the Uart::flush() method to initiate the data transfer over the UART interface. The proposed code would require modifications to the Uart::write(data) and Uart::flush() methods:
///RTN Implementation Uart of flush() and write(data) methods that address race condition between
/// write(data) and IrqHandler which can leave part of a Uart write operation stranded in the
/// RingBuffer.
void Uart::flush()
{
bool firstWriteChar = true;
// Only initiate write to UART interface for first char in transmit RingBuffer. Let the Uart::IrqHandler()
// take over the write operation and empty the transmit RingBuffer.
while(txBuffer.available())
{
if (firstWriteChar && sercom->isDataRegisterEmptyUART())
{
uint8_t data = txBuffer.read_char();
sercom->writeDataUART(data);
sercom->enableDataRegisterEmptyInterruptUART();
firstWriteChar = false;
}
}
sercom->flushUART();
}
///RTN Queue all write data to the RingBuffer until either RingBuffer is full or flush() is called to
/// complete the Uart write operation.
size_t Uart::write(const uint8_t data)
{
if (txBuffer.isFull())
{
flush();
}
txBuffer.store_char(data);
return 1;
}
Note that the Uart::flush() method must always be invoked to push the data out the UART interface. This doesn't seem to be much of a problem for the MKRNB1500 using the MKRNB library because the Modem.send() method in the Modem.cpp source file of the MKRNB library always does this.
I am now running a modified Uart.cpp with the above code changes to see if I still get my infrequent hangs. Note that the Uart::write(data) method above does not have the logic to address Issue #262. That issue only appears to happen when the Uart::write(data) is invoked with interrupts disabled or inside a higher priority ISR. Specifically, it appears that the Issue #262 reproduction test used println() commands inside an ISR to reproduce the problem and I am not doing anything like that. The Uart::write(data) issued on the SARA modem UART interface is only invoked in the background (non ISR) task priority.
I am not sure if I am missing something, and I would appreciate it if someone would comment on this hang scenario and possible solution.