Stack Crash when pm_device_action_run(uart_dev, PM_DEVICE_ACTION_SUSPEND) used for UART

Hi,

SoC: nRF54L15, SDK 3.1, battery powered application.

Our product  uses UART.  When power management is used (pm_device_action_run(uart_dev, PM_DEVICE_ACTION_SUSPEND)), the stack crashes.  We used Matter door lock samples to  try this. Please see the configuration and code used.

Thanks.


Subu

CONFIG_USE_SEGGER_RTT=n
CONFIG_SHELL=n
CONFIG_OPENTHREAD_SHELL=n
CONFIG_CONSOLE=n
CONFIG_UART_CONSOLE=n

#********** Enable UART ********************#
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y

CONFIG_UART_20_ASYNC=n
CONFIG_UART_20_INTERRUPT_DRIVEN=n
CONFIG_UART_20_NRF_ASYNC_LOW_POWER=n

CONFIG_NRFX_UARTE30=y
CONFIG_UART_30_ASYNC=y
CONFIG_UART_30_INTERRUPT_DRIVEN=n
CONFIG_UART_30_NRF_ASYNC_LOW_POWER=y

CONFIG_LOG=n
CONFIG_LOG_MODE_MINIMAL=n
CONFIG_ASSERT_VERBOSE=n
CONFIG_ASSERT_NO_FILE_INFO=n
CONFIG_PRINTK=n
CONFIG_PRINTK_SYNC=n
CONFIG_THREAD_NAME=n
CONFIG_BOOT_BANNER=n

#include "user_console.h"

UserConsole::UserConsole(const struct device *uart_dev)
    : uart_dev(uart_dev)
{
    atomic_set(&suspended, 0);
}

UserConsole::~UserConsole() {
    suspend();
}

bool UserConsole::init() {
    if (!device_is_ready(uart_dev)) {
        printk("UART not ready\n");
        return false;
    }

    uart_callback_set(uart_dev, uart_event_cb, this);

    // Request HFCLK before enabling RX
    request_hfclk();

    int ret = uart_rx_enable(uart_dev, rx_dma_buf, sizeof(rx_dma_buf), SYS_FOREVER_US);
    if (ret) {
        printk("uart_rx_enable failed: %d\n", ret);
        release_hfclk();
        return false;
    }

    printk("UART Initialized -> Suspending URAT to sleep \n");
//    UserConsole::suspend();

    return true;
}

void UserConsole::sendChar(uint8_t c) {
    resume();
    uart_tx(uart_dev, &c, 1, SYS_FOREVER_US);
}

void UserConsole::sendBuffer(const uint8_t *buf, size_t len) {
    resume();
    uart_tx(uart_dev, buf, len, SYS_FOREVER_US);
}

bool UserConsole::available() {
    return rx_head != rx_tail;
}

uint8_t UserConsole::readChar() {
    if (rx_head == rx_tail) {
        return 0;
    }
    uint8_t c = rx_ring[rx_tail];
    rx_tail = (rx_tail + 1) % RX_BUF_SIZE;
    return c;
}

void UserConsole::suspend() {
    int err;
    if (atomic_cas(&suspended, 0, 1)) {
         err = uart_rx_disable(uart_dev);
         if(err){
            printk("UART RX is not disabled: error=%d \n", err);
            return;
        }
        err = uart_tx_abort(uart_dev);
        if(err){
            printk("UART TX is not aborted: error=%d \n", err);
            return;
        }
        
        k_msleep(100);
        printk("Call UART Suspend \n");
        pm_device_action_run(uart_dev, PM_DEVICE_ACTION_SUSPEND);
        if (err) {
		    printk("Failed to suspend uart (err: %d)", err);
		    return;
        }
        printk("Release clock");
        release_hfclk();

	    printk("UART suspended..\n");

    }
}

void UserConsole::resume() {
    if (atomic_cas(&suspended, 1, 0)) {
        pm_device_action_run(uart_dev, PM_DEVICE_ACTION_RESUME);
        request_hfclk();
    }
}

void UserConsole::uart_event_cb(const struct device *dev,
                                struct uart_event *evt,
                                void *user_data) {
    auto *self = static_cast<UserConsole *>(user_data);

    switch (evt->type) {
    case UART_RX_RDY:
        for (size_t i = 0; i < evt->data.rx.len; i++) {
            uint8_t c = evt->data.rx.buf[evt->data.rx.offset + i];
            size_t next = (self->rx_head + 1) % RX_BUF_SIZE;
            if (next != self->rx_tail) {
                self->rx_ring[self->rx_head] = c;
                self->rx_head = next;
            }
        }
        break;

    case UART_RX_DISABLED:
        // Re-enable RX when disabled
        uart_rx_enable(dev, self->rx_dma_buf, sizeof(self->rx_dma_buf), SYS_FOREVER_US);
        break;

    case UART_TX_DONE:
        self->suspend();
        break;

    default:
        break;
    }
}

/* ===== HFCLK control ===== */

void UserConsole::rx_hfclk_callback() {
    // Nothing needed here; just ensures HFCLK is running before UART RX
}

void UserConsole::request_hfclk() {
    struct onoff_manager *mgr =
        z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);

    sys_notify_init_callback(&clk_client.notify, rx_hfclk_callback);
    int err = onoff_request(mgr, &clk_client);
    __ASSERT_NO_MSG(err >= 0);
}

void UserConsole::release_hfclk() {
    struct onoff_manager *mgr =
        z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);

    (void)onoff_cancel_or_release(mgr, &clk_client);
}

&uart30 {
    status = "okay";
    current-speed = <115200>;
    pinctrl-0 = <&uart30_default>;
    pinctrl-1 = <&uart30_sleep>;
    pinctrl-names = "default", "sleep";

    //    interrupts = <10>, <NRF_DEFAULT_IRQ_PRIORITY>;
};

&uart30_sleep {
    group1 {
        psels = <NRF_PSEL(UART_TX, 0, 0)>, /* Port P0.0 */
                <NRF_PSEL_DISCONNECTED(UART_RTS)>;
        low-power-enable;
    };
    group2 {
        psels = <NRF_PSEL(UART_RX, 0, 1)>, /* Port P0.1  */
                <NRF_PSEL_DISCONNECTED(UART_CTS)>;
        low-power-enable;
        bias-pull-up;
    };
};

&uart30_default {
    group1 {
        psels = <NRF_PSEL(UART_TX, 0, 0)>,
                <NRF_PSEL_DISCONNECTED(UART_RTS)>;
    };
    group2 {
        psels = <NRF_PSEL(UART_RX, 0, 1)>,
                <NRF_PSEL_DISCONNECTED(UART_CTS)>;
        bias-pull-up;
    };
};

&button0 {
    gpios = <&gpio2 10 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
    label = "Push button 0";
    zephyr,code = <INPUT_KEY_0>;
};

&button1 {
    status = "disabled";
};

&button2 {
    status = "disabled";
};

Related