Proper use of xEventGroupSetBitsFromISR() & portYield_From_ISR()

I am experiencing a puzzling “seemingly” freeze issue in our application built on NXP iMXRT1052 using FreeRTOS.
NXP provides a UART driver and a FreeRTOS version of interrupt driven UART driver that is built on topic of it.
The FreeRTOS version of UART driver provides a callback that can be called from a task context as well as an UART ISR context; this driver utilizes a software ring buffer.
If there is already requested UART bytes in the ring buffer, it calls the callback from a task context as well.

At any rate, the callback invokes xEventGroupSetBitsFromISR() and portYield_From_ISR() to wake up a task waiting for either Tx or Rx to complete.

  1. Is is completely safe to use those two APIs from non-ISR context?

  2. Does xEventGroupSetBitsFromISR() wake up xEventGroupWaitBits() even if xEventGroupSetBitsFromISR() gets called before xEventGroupWaitBits() is called?
    I ask because I see a scenario where a task calls xEventGroupSetBitsFromISR() because requested UART bytes are already in the ring buffer.

/* Non-blocking call */
LPUART_TransferReceiveNonBlocking(handle->base, handle->t_state, &handlerxTransfer, &n);

ev = xEventGroupWaitBits(
    handle->rxEvent, RTOS_LPUART_COMPLETE | RTOS_LPUART_RING_BUFFER_OVERRUN | RTOS_LPUART_HARDWARE_BUFFER_OVERRUN,
    pdTRUE, pdFALSE, ticksToWait);

where LPUART_TransferReceiveNonBlocking() is defined as

status_t LPUART_TransferReceiveNonBlocking(LPUART_Type *base,
                                           lpuart_handle_t *handle,
                                           lpuart_transfer_t *xfer,
                                           size_t *receivedBytes)
{
 .
 .
 .
          /* Call user callback since all data are received. */
          if (0U == bytesToReceive)
          {
              if (NULL != handle->callback)
              {
                  handle->callback(base, handle, kStatus_LPUART_RxIdle, handle->userData);
              }
          }
 .
 .
 }

where the callback is defined as

    static void LPUART_RTOS_Callback(LPUART_Type *base, lpuart_handle_t *state, status_t status, void *param)
    {
        lpuart_rtos_handle_t *handle = (lpuart_rtos_handle_t *)param;
        BaseType_t xHigherPriorityTaskWoken, xResult;

        xHigherPriorityTaskWoken = pdFALSE;
        xResult                  = pdFAIL;

        if (status == kStatus_LPUART_RxIdle)
        {
            xResult = xEventGroupSetBitsFromISR(handle->rxEvent, RTOS_LPUART_COMPLETE, &xHigherPriorityTaskWoken);
        }
        else if (status == kStatus_LPUART_TxIdle)
        {
            xResult = xEventGroupSetBitsFromISR(handle->txEvent, RTOS_LPUART_COMPLETE, &xHigherPriorityTaskWoken);
        }
        else if (status == kStatus_LPUART_RxRingBufferOverrun)
        {
            xResult =
                xEventGroupSetBitsFromISR(handle->rxEvent, RTOS_LPUART_RING_BUFFER_OVERRUN, &xHigherPriorityTaskWoken);
        }
        else if (status == kStatus_LPUART_RxHardwareOverrun)
        {
            /* Clear Overrun flag (OR) in LPUART STAT register */
            LPUART_ClearStatusFlags(base, kLPUART_RxOverrunFlag);
            xResult =
                xEventGroupSetBitsFromISR(handle->rxEvent, RTOS_LPUART_HARDWARE_BUFFER_OVERRUN, &xHigherPriorityTaskWoken);
        }

        if (xResult != pdFAIL)
        {
            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        }
    }

The puzzling “freeze” seems to be caused by the code around these.
When the freeze occurs, no other tasks seem to be running with the exception of UART interrupts. UART Rx ISR continuously gets triggered over and over and nothing else appears to be “allowed” to run.
Any experience with this kind of stuff?

Hello James,

Doesn’t NXP provide UART examples with FreeRTOS?
Does NXP say that ‘ring buffer’ and ‘FreeRTOS version of interrupt driven UART driver’ can be used simultaneously? Or doesn’t NXP say that there are some rules if both are used simultaneously?

Not only you but also I and maybe other people think that it is strange that LPUART_RTOS_Callback() which calls FreeRTOS API ending with ‘FromISR’ is called from a task context.

Best regards,
NoMaY

It is ok to call the “FromISR” versions from a task (I think), but in the case of setting an event bit it is not very efficient as it defers the actual manipulation of the event group to the daemon (timer) task. That is done because setting bits in an event group is not a deterministic operation and FreeRTOS doesn’t allow non deterministic behaviour in interrupts. It is not deterministic because you don’t know how many tasks setting a bit will unblock.

It sounds like you are going to need to do some debugging - when this situation occurs, if you stop the code on the debugger, what is it executing?

Thanks @NoMaY-jp for your reply. We always build our stuff based on NXP’s examples and NXP provides an example with FreeRTOS version of UART driver. As I mentioned, this FreeRTOS version UART driver uses NXP’s UART driver( fsl_lpuart), which already provides use of ring buffer.
I already contacted NXP but as always, they say they don’t any problem.

My further reading on FreeRTOS manual hints me that the answers to my question 1) & 2) are yes.
It appears to be safe to call ISR based FreeRTOS APIs and the calling task will still get woken up if it waits for an event of interest even if the event is already set as long as xWaitForAllBits is false…

If anyone understands it otherwise, please let me know. :slight_smile:

My further investigation into the “freeze” issue I described above tells me that when it happens, msp(main stack pointer) is reasonable but psp(processor stack pointer) is NOT.
I learned that NXP MCUXpresso shows the ISR stack on the task stack that was running at the time of ISR (instead of putting it on its own stack, like ISR stack(?)).

I do not see a sign of stack overflow in any of the task stacks.
nest_rcv_task was a running task and the UART ISR shows up on nest_rcv_task stack in MCUXpresso IDE. And therefore, nest_rcv_task stask is not shown.
I understand that msp is referred to as main stack where main() and ISR uses
and psp is referred to as processor stack and is loaded with a stack pointer of currently running stack(i.e. stack pointer to return to when ISR exits).

(Edited. Forgot to mention.
Main stack is on DTC_SRAM which ranges from 0x2000 0000 to 0x2002 0000 and therefore,
msp value of 0x2001 ff88 makes sense since the stack grows downward from 0x2002 0000.
psp value of 0x2000 5af8 is strange because all of our task stacks are on external SDRAM located at 0x8000 0000. The psp address of 0x2000 5af8 is where our “main” heap is located along with program data. I understand the main heap is used for newlib mallocs.
)

Any sharing of information you have is appreciated.
BTW as I mentioned, it’s not actually “freeze” because somehow UART ISR continues to be triggered while all other tasks appear to be “frozen”.

Thanks @rtel . It may not be very efficient but not unsafe, right?
We have no extra resource and time to rewrite peripheral drivers provided by NXP and we are pretty much “trusting” that NXP has done good enough job in writing the drivers. :frowning:

If I stop the debugger, (I know that the issue occurred because console logging stops all of a sudden while running for some time)
I can step through the UART ISR code but when it exits from the UART ISR, the debugger seems to resume. If I pause to step again, I am always at UART ISR.
I tried providing more information in my post above.

Hello James,

Now I can see fsl_uart.c and fsl_lpuart.c (but what I can see are source code for K66F).

https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/TARGET_K66F/drivers/fsl_uart.c

https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/TARGET_K66F/drivers/fsl_lpuart.c

[Added]

And fsl_lpuart_freertos.c (for MKW41Z4).

https://github.com/rlubos/openthread-upstream/blob/master/third_party/nxp/MKW41Z4/drivers/fsl_lpuart_freertos.c

[end]

By the way, when I meet a strange behavior of source line step execution, I try to use ‘Instruction Stepping Mode’ and ‘Disassembly view’ of IDE (but it isn’t MCUXpresso). Did you try it?

Unfortunately, if ‘Instruction Stepping Mode’ and ‘Disassembly view’ don’t work expectedly, I try to set a breakpoint at the return address of ‘return from interrupt’ instruction (I’m sorry that I’m not familiar with ARM instruction set) which is obtained in Memory Browser view and then I resume my program and wait for a stop of the program.

Best regards,
NoMaY

Have you defined configASSERT in your FreeRTOSConfig.h?

Regarding the PSP value, is it possible that stack is allocated on heap for some task? How do you ensure that all the task stacks are on SDRAM?

Thanks.