drivers: serial: nrf_uarte: Fix power management transitions

Deadlock may occur when uart device was put to low power state
while uart_poll_out was in progress. Poll out pends until uarte
is ready to send new byte and it is detected by endtx and txstopped
events being set. When uarte was disabled while poll_out was pending
events were never set and that lead to deadlock. Added a step
in going to low power state in which CPU waits for txstopped event
and only after that uarte peripheral is disabled.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
Krzysztof Chruscinski 2021-05-18 10:34:04 +02:00 committed by Kumar Gala
commit 8af5dbc977

View file

@ -549,6 +549,24 @@ static void tx_start(const struct device *dev, const uint8_t *buf, size_t len)
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STARTTX);
}
#if defined(CONFIG_UART_ASYNC_API) || defined(CONFIG_PM_DEVICE)
static void uart_disable(const struct device *dev)
{
#ifdef CONFIG_UART_ASYNC_API
struct uarte_nrfx_data *data = get_dev_data(dev);
if (data->async && hw_rx_counting_enabled(data)) {
nrfx_timer_disable(&get_dev_config(dev)->timer);
/* Timer/counter value is reset when disabled. */
data->async->rx_total_byte_cnt = 0;
data->async->rx_total_user_byte_cnt = 0;
}
#endif
nrf_uarte_disable(get_uarte_instance(dev));
}
#endif
#ifdef CONFIG_UART_ASYNC_API
static void timer_handler(nrf_timer_event_t event_type, void *p_context) { }
@ -1172,20 +1190,6 @@ static uint8_t rx_flush(const struct device *dev, uint8_t *buf, uint32_t len)
return 0;
}
static void async_uart_disable(const struct device *dev)
{
struct uarte_nrfx_data *data = get_dev_data(dev);
if (hw_rx_counting_enabled(data)) {
nrfx_timer_disable(&get_dev_config(dev)->timer);
/* Timer/counter value is reset when disabled. */
data->async->rx_total_byte_cnt = 0;
data->async->rx_total_user_byte_cnt = 0;
}
nrf_uarte_disable(get_uarte_instance(dev));
}
static void async_uart_release(const struct device *dev, uint32_t dir_mask)
{
struct uarte_nrfx_data *data = get_dev_data(dev);
@ -1199,7 +1203,7 @@ static void async_uart_release(const struct device *dev, uint32_t dir_mask)
sizeof(data->async->rx_flush_buffer));
}
async_uart_disable(dev);
uart_disable(dev);
}
irq_unlock(key);
@ -1380,11 +1384,6 @@ static void uarte_nrfx_poll_out(const struct device *dev, unsigned char c)
bool isr_mode = k_is_in_isr() || k_is_pre_kernel();
int key;
#ifdef CONFIG_PM_DEVICE
if (data->pm_state != PM_DEVICE_STATE_ACTIVE) {
return;
}
#endif
if (isr_mode) {
while (1) {
key = irq_lock();
@ -1409,8 +1408,13 @@ static void uarte_nrfx_poll_out(const struct device *dev, unsigned char c)
/* At this point we should have irq locked and any previous transfer
* completed. Transfer can be started, no need to wait for completion.
*/
data->char_out = c;
tx_start(dev, &data->char_out, 1);
#if CONFIG_PM_DEVICE
if (data->pm_state == PM_DEVICE_STATE_ACTIVE)
#endif
{
data->char_out = c;
tx_start(dev, &data->char_out, 1);
}
irq_unlock(key);
}
@ -1773,6 +1777,41 @@ static void uarte_nrfx_pins_enable(const struct device *dev, bool enable)
}
}
/** @brief Pend until TX is stopped.
*
* There are 2 configurations that must be handled:
* - ENDTX->TXSTOPPED PPI enabled - just pend until TXSTOPPED event is set
* - disable ENDTX interrupt and manually trigger STOPTX, pend for TXSTOPPED
*/
static void wait_for_tx_stopped(const struct device *dev)
{
bool ppi_endtx = get_dev_config(dev)->flags & UARTE_CFG_FLAG_PPI_ENDTX;
NRF_UARTE_Type *uarte = get_uarte_instance(dev);
bool res;
if (!ppi_endtx) {
/* We assume here that it can be called from any context,
* including the one that uarte interrupt will not preempt.
* Disable endtx interrupt to ensure that it will not be triggered
* (if in lower priority context) and stop TX if necessary.
*/
nrf_uarte_int_disable(uarte, NRF_UARTE_INT_ENDTX_MASK);
NRFX_WAIT_FOR(is_tx_ready(dev), 1000, 1, res);
if (!nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_TXSTOPPED)) {
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_ENDTX);
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STOPTX);
}
}
NRFX_WAIT_FOR(nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_TXSTOPPED),
1000, 1, res);
if (!ppi_endtx) {
nrf_uarte_int_enable(uarte, NRF_UARTE_INT_ENDTX_MASK);
}
}
static void uarte_nrfx_set_power_state(const struct device *dev,
uint32_t new_state)
{
@ -1793,8 +1832,7 @@ static void uarte_nrfx_set_power_state(const struct device *dev,
return;
}
#endif
if (nrf_uarte_rx_pin_get(uarte) !=
NRF_UARTE_PSEL_DISCONNECTED) {
if (nrf_uarte_rx_pin_get(uarte) != NRF_UARTE_PSEL_DISCONNECTED) {
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_ENDRX);
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STARTRX);
@ -1825,19 +1863,12 @@ static void uarte_nrfx_set_power_state(const struct device *dev,
*/
#ifdef CONFIG_UART_ASYNC_API
if (get_dev_data(dev)->async) {
/* Wait for the transmitter to go idle.
* While the async API can wait for transmission to
* complete before disabling the peripheral, the poll
* API exits before the character is sent on the wire.
* If the transmission is ongoing when the peripheral
* is disabled, the ENDTX and TXSTOPPED events will
* never be generated. This will block the driver in
* any future calls to poll_out.
/* Entering inactive state requires device to be no
* active asynchronous calls.
*/
irq_unlock(wait_tx_ready(dev));
async_uart_disable(dev);
uarte_nrfx_pins_enable(dev, false);
return;
__ASSERT_NO_MSG(!data->async->rx_enabled);
__ASSERT_NO_MSG(!data->async->tx_size);
}
#endif
if (nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXSTARTED)) {
@ -1861,8 +1892,11 @@ static void uarte_nrfx_set_power_state(const struct device *dev,
}
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXSTARTED);
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXTO);
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_ENDRX);
}
nrf_uarte_disable(uarte);
wait_for_tx_stopped(dev);
uart_disable(dev);
uarte_nrfx_pins_enable(dev, false);
}
}