Skip to content

Commit bd0fe17

Browse files
gromerogalak
authored andcommitted
drivers: uart: uart_cmsdk_apb: Fix uart_irq_is_pending
Currently CMSDK uart_irq_is_pending does not use RX and TX interrupt bits found in INTSTATUS register to check for pending interrutps but rather it checks for pending interrupts indirectly by checking if RX and TX buffers are, respectively, full and empty, i.e. it checks bits 0 and 1 in STATE register instead of bits meant for interrupt status found in INTSTATUS register. That is particularly problematic because although a RX interrupt implies a RX buffer full and a TX interrupt implies a TX buffer empty, the converse is not true. For instance, a TX buffer might be empty for all data was processed (sent to serial line) already and no further data was pushed into TX buffer so it remained empty, without generating any additional TX interrupt. In that case the current uart_irq_is_pending implementation reports that there are pending interrupts because of the following logic: /* Return true if rx buffer full or tx buffer empty */ return (UART_STRUCT(dev)->state & (UART_RX_BF | UART_TX_BF)) != UART_TX_BF; which will return 1 (true) if STATE[0] = 0 (TX buffer is empty), since UART_TX_BF = 1, so STATE[0] != UART_TX_BF, which is true (assuming here for the sake of simplicity that UART_RX_BF = 0, i.e. RX buffer is empty too). One of the common uses of uart_irq_is_pending is in ISR in contructs like the following: while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { if (uart_irq_rx_ready(dev) == 0) { // RX buffer is empty continue; } // RX buffer is full, process RX data } So the ISR can be called due to a RX interrupt. Upon finishing processing the RX data uart_irq_is_pending is called to check for any pending IRQs and if it happens that TX buffer is empty (like in the case that TX interrupt is totally disabled) execution gets stuck in the while loop because TX buffer will never transition to full again, i.e. it will never have a chance to have STATE[0] = 1, so STATE[0] != UART_TX_BF is always true. This commit fixes that undesirable and problematic behavior by making uart_irq_is_pending use the proper bits in the interrupt status register (INTSTATUS) to determine if there is indeed any pending interrupts. That, on the other hand, requires that the pending interrupt flags are not clearly automatically when calling the ISR, otherwise uart_irq_is_pending() will return immediatly false on the first call without any data being really processed inside the ISR. Thus, because both RX and TX buffer (FIFO) are only 1 byte long, that commit clears the proper interrupts flags precisely when data is processed (fifo_read and fifo_fill) or when the interrupts are disabled (irq_rx_disable and irq_tx_disable). Finally, that commits also takes the chance to update some comments, specially regarding the need to "prime" when enabling the TX interrupts (in uart_cmsdk_apb_irq_tx_enable()). The need to "prime" can be verified in the CMSDK UART reference implementation in Verilog mentioned in the "Arm Cortex-M System Design Kit" [0], on p. 4-8, section 4.3, in cmsdk_apb_uart.v. In that implementation it's also possible to verify that the FIFO is only 1 byte long, justifying the semantics that if buffers are not full (STATE[0] or STATE[1] = 0) they are _completly_ empty, holding no data at all. [0] https://documentation-service.arm.com/static/5e8f1c777100066a414f770b Signed-off-by: Gustavo Romero <[email protected]>
1 parent fe3f71b commit bd0fe17

File tree

1 file changed

+34
-14
lines changed

1 file changed

+34
-14
lines changed

drivers/serial/uart_cmsdk_apb.c

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016 Linaro Limited
2+
* Copyright (c) 2021, Linaro Limited.
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -217,8 +217,18 @@ static int uart_cmsdk_apb_fifo_fill(const struct device *dev,
217217
{
218218
volatile struct uart_cmsdk_apb *uart = UART_STRUCT(dev);
219219

220-
/* No hardware FIFO present */
220+
/*
221+
* No hardware FIFO present. Only 1 byte
222+
* to write if TX buffer is empty.
223+
*/
221224
if (len && !(uart->state & UART_TX_BF)) {
225+
/*
226+
* Clear TX int. pending flag before pushing byte to "FIFO".
227+
* If TX interrupt is enabled the UART_TX_IN bit will be set
228+
* again automatically by the UART hardware machinery once
229+
* the "FIFO" becomes empty again.
230+
*/
231+
uart->intclear = UART_TX_IN;
222232
uart->data = *tx_data;
223233
return 1;
224234
}
@@ -240,8 +250,18 @@ static int uart_cmsdk_apb_fifo_read(const struct device *dev,
240250
{
241251
volatile struct uart_cmsdk_apb *uart = UART_STRUCT(dev);
242252

243-
/* No hardware FIFO present */
253+
/*
254+
* No hardware FIFO present. Only 1 byte
255+
* to read if RX buffer is full.
256+
*/
244257
if (size && uart->state & UART_RX_BF) {
258+
/*
259+
* Clear RX int. pending flag before popping byte from "FIFO".
260+
* If RX interrupt is enabled the UART_RX_IN bit will be set
261+
* again automatically by the UART hardware machinery once
262+
* the "FIFO" becomes full again.
263+
*/
264+
uart->intclear = UART_RX_IN;
245265
*rx_data = (unsigned char)uart->data;
246266
return 1;
247267
}
@@ -262,10 +282,12 @@ static void uart_cmsdk_apb_irq_tx_enable(const struct device *dev)
262282

263283
UART_STRUCT(dev)->ctrl |= UART_TX_IN_EN;
264284
/* The expectation is that TX is a level interrupt, active for as
265-
* long as TX buffer is empty. But in CMSDK UART, it appears to be
266-
* edge interrupt, firing on a state change of TX buffer. So, we
267-
* need to "prime" it here by calling ISR directly, to get interrupt
268-
* processing going.
285+
* long as TX buffer is empty. But in CMSDK UART it's an edge
286+
* interrupt, firing on a state change of TX buffer from full to
287+
* empty. So, we need to "prime" it here by calling ISR directly,
288+
* to get interrupt processing going, as there is no previous
289+
* full state to allow a transition from full to empty buffer
290+
* that will trigger a TX interrupt.
269291
*/
270292
key = irq_lock();
271293
uart_cmsdk_apb_isr(dev);
@@ -282,6 +304,8 @@ static void uart_cmsdk_apb_irq_tx_enable(const struct device *dev)
282304
static void uart_cmsdk_apb_irq_tx_disable(const struct device *dev)
283305
{
284306
UART_STRUCT(dev)->ctrl &= ~UART_TX_IN_EN;
307+
/* Clear any pending TX interrupt after disabling it */
308+
UART_STRUCT(dev)->intclear = UART_TX_IN;
285309
}
286310

287311
/**
@@ -318,6 +342,8 @@ static void uart_cmsdk_apb_irq_rx_enable(const struct device *dev)
318342
static void uart_cmsdk_apb_irq_rx_disable(const struct device *dev)
319343
{
320344
UART_STRUCT(dev)->ctrl &= ~UART_RX_IN_EN;
345+
/* Clear any pending RX interrupt after disabling it */
346+
UART_STRUCT(dev)->intclear = UART_RX_IN;
321347
}
322348

323349
/**
@@ -377,9 +403,7 @@ static void uart_cmsdk_apb_irq_err_disable(const struct device *dev)
377403
*/
378404
static int uart_cmsdk_apb_irq_is_pending(const struct device *dev)
379405
{
380-
/* Return true if rx buffer full or tx buffer empty */
381-
return (UART_STRUCT(dev)->state & (UART_RX_BF | UART_TX_BF))
382-
!= UART_TX_BF;
406+
return (UART_STRUCT(dev)->intstatus & (UART_RX_IN | UART_TX_IN));
383407
}
384408

385409
/**
@@ -421,12 +445,8 @@ static void uart_cmsdk_apb_irq_callback_set(const struct device *dev,
421445
*/
422446
void uart_cmsdk_apb_isr(const struct device *dev)
423447
{
424-
volatile struct uart_cmsdk_apb *uart = UART_STRUCT(dev);
425448
struct uart_cmsdk_apb_dev_data *data = DEV_DATA(dev);
426449

427-
/* Clear pending interrupts */
428-
uart->intclear = UART_RX_IN | UART_TX_IN;
429-
430450
/* Verify if the callback has been registered */
431451
if (data->irq_cb) {
432452
data->irq_cb(dev, data->irq_cb_data);

0 commit comments

Comments
 (0)