On-demand start of uart shell and dynamic pin control.

Hi!

We have three different uart applications (two for production use, and the uart shell backend) and three different hardware output paths, but nRF52840 features just two uart controllers. So we would like to be able to pick any two out of the three applications, and run them on the two controllers using dynamic pin control. We would like to be able to do so dynamically during boot.

So far I can re-route our uarts using the pinctrl_update_states and pinctrl_apply_states.

A problem comes with the uart shell, as we need to dynamically either start it or not, where by not starting I mean to not make it even claim the uart controller.

The minimum config that allows me to compile seems to be:
```
CONFIG_SHELL=y
CONFIG_SHELL_AUTOSTART=n
CONFIG_SHELL_BACKEND_SERIAL=y
CONFIG_UART_CONSOLE=n
```

Now when I set `CONFIG_SHELL_BACKEND_SERIAL=y` and reassign the pins using the pinctrl_* functions then: 

-  I can start the shell on demand with `shell_start(shell_backend_uart_get_ptr());`
-  but when I hook a logic analyzer, I don't see any traffic on the line where I had redirected the production app traffic to. This line and controller is also specified as the dts "chosen" zephyr,shell-uart. 

When I set `CONFIG_SHELL_BACKEND_SERIAL=n` and remove all code that relies on it:

-  then redirecting the traffic to the other set of lines works fine - I see there the traffic using a logic analyzer
-  but I loose the ability to start the shell on uart, since `shell_backend_uart_get_ptr()` is not compiled in without CONFIG_SHELL_BACKEND_SERIAL

I assume that it is because while the shell didn't autostart, the shell backend was automatically initialized at startup and claimed the pins.

How do we compile zephyr in such way that uart shell support is compiled in, but doesn't claim any controller unless told to?

Thanks!
Karel

  • Crashlog is here

    ```
    ASSERTION FAIL @ WEST_TOPDIR/zephyr/drivers/serial/uart_nrfx_uarte.c:2124
    [00:00:07.173,767] <err> os: r0/a1:  0x00000004  r1/a2:  0x0000084c  r2/a3:  0x20012030
    [00:00:07.173,797] <err> os: r3/a4:  0x00000004 r12/ip:  0x2000f568 r14/lr:  0x00066dcf
    [00:00:07.173,797] <err> os:  xpsr:  0x01000000
    [00:00:07.173,828] <err> os: r4/v1:  0x40002000  r5/v2:  0x000823fc  r6/v3:  0x00088308
    [00:00:07.173,858] <err> os: r7/v4:  0x20002f70  r8/v5:  0x00000000  r9/v6:  0x00000000
    [00:00:07.173,858] <err> os: r10/v7: 0x00000000  r11/v8: 0x00000000    psp:  0x200376b8
    [00:00:07.173,889] <err> os: EXC_RETURN: 0x0
    [00:00:07.173,889] <err> os: Faulting instruction address (r15/pc): 0x000781e0
    [00:00:07.173,919] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic on CPU 0
    [00:00:07.173,980] <err> os: Current thread: 0x20012030 (main)
    ```

    Or: 

    ```
    static void uarte_pm_suspend(const struct device *dev)
    {
        NRF_UARTE_Type *uarte = get_uarte_instance(dev);
        const struct uarte_nrfx_config *cfg = dev->config;
        struct uarte_nrfx_data *data = dev->data;

        (void)data;
    #ifdef UARTE_ANY_ASYNC
        if (data->async) {
            /* Entering inactive state requires device to be no
             * active asynchronous calls.
             */
            __ASSERT_NO_MSG(!data->async->rx.enabled); // <- here
    ```

    I am not explicitly enabling uart anywhere before this call site (not that I know of anyways).

    The related configs that come to mind are:

    ```
    CONFIG_CONSOLE=y
    CONFIG_UART_CONSOLE=y

    CONFIG_SERIAL=y
    CONFIG_SHELL=y
    CONFIG_SHELL_BACKEND_SERIAL=y
    CONFIG_SHELL_AUTOSTART=n

    # Also tried without success, while also commenting out the zephyr,console in the dts chosen section:
    #CONFIG_UART_CONSOLE=n 
    #CONFIG_CONSOLE=n
    ```

    Any ideas?

  • So as you pointed out, the assert is raised because UART reception is enabled while you try to suspend the driver. 

    kat829 said:
    I am not explicitly enabling uart anywhere before this call site (not that I know of anyways).

    It is enabled by the shell. Is the shell initialized at this point and have you tried calling uart_rx_disable() first?

  • Ah, right. I was calling uart_irq_rx_disable, but not uart_rx_disable Face palm.

    Well. When I call the uart_rx_disable, it returns -14, but indeed, the suspend doesn't fail anymore. 

    But:
    - If I suspend, call pinctrls, resume; then the resumes crash with various errors, all related to the drivers not being initialized.
    - If I suspend, call pinctrls, initialize the drivers, then call resume; then the devices remain dead.
    - If I suspend, call pinctrls, initialize the drivers; then the devices remain dead.


    If I don't suspend or resume the devices at all then everything seems to work fine.

  • I think I would need to reproduce this on my end to understand why it's failing. But do you need the ability to change the pinout more than once after reset? If not, the approach used in the dynamic pin control sample (https://docs.nordicsemi.com/bundle/ncs-3.1.1/page/zephyr/samples/boards/nordic/dynamic_pinctrl/README.html) will likely be better suited for you app. 

  • > I think I would need to reproduce this on my end to understand why it's failing. 

    Well, the code is public. You can see it at https://bit.ly/4n33Vd6 . (The indirection is for privacy reasons, since we usually discuss future products on these forums.)

    The relevant files are device/src/{main.c,pin_wiring.c}. 

    > But do you need the ability to change the pinout more than once after reset?

    No. 

    But I think we would like to do so a bit later during boot, somewhere in the main function. 

    Maybe the question is just whether changing the pinctrl without suspending the devices is actually dangerous in our situation. I.e., the shell has claimed the uart, but with `CONFIG_SHELL_AUTOSTART=n` technically shouldn't be actually used at the time. 

Related