ARM-software / CMSIS_5

CMSIS Version 5 Development Repository
http://arm-software.github.io/CMSIS_5/index.html
Apache License 2.0
1.31k stars 1.08k forks source link

osEventFlagsSet() after osKernelSuspend() in RTOS2 tick-less operation #1467

Closed juliobc closed 1 year ago

juliobc commented 2 years ago

Hi all,

I have a tick-less operation like this:

while (true) {
    tc_wakeup = osKernelSuspend();

    if (tc_wakeup > 0) {

        init_time = Get_time_in_msec();

        flag_WakeUpTimerEventCallback = false;
        SetWakeUpTimer_IT(tc_wakeup);

        EnterSTOPMode();

        if (flag_WakeUpTimerEventCallback == false) {

            tc_wakeup = Get_time_in_msec() - init_time;
        }
    }
    osKernelResume(tc_wakeup);
}

When an interrupt is fired while stop mode I can wakeup and continue working in my app. But the problem is when the ISR is fired between osKernelSuspend() and EnterSTOPMode(). What is the behavior in that case?

I have this ISR:

void test_ISR (void) {
    osEventFlagsSet(evt_id, EVT_FLAG_1);
}

Theorically, when the test_ISR() is fired Thread_test is resumed from blocked state. If test_ISR() is fired between osKernelSuspend() and EnterSTOPMode(), how can I to know if there is any thread right now in ready state to avoid enter in stop mode?

Best Regards

JonatanAntoni commented 2 years ago

Hi @juliobc,

But the problem is when the ISR is fired between osKernelSuspend() and EnterSTOPMode(). What is the behavior in that case?

This depends on your implementation of EnterSTOPMode(). Basically, on Cortex-M you have two options, WFI or WFE. The WFI has the drawback that it will only wake-up on interrupts received after the instruction. This is why most examples recommend using WFE which just clears the event-flag if it is already set but doesn't put the device into sleep.

Cheers, Jonatan

juliobc commented 2 years ago

Hi @JonatanAntoni

I use WFE to enter in stop mode. I understand that EnterSTOPMode() returns immediately if the ISR has been fired and when osKernelResume() is executed the Thread_test goes into running state.

Then, I understand when Idle Thread run after a while, allways EnterSTOPMode() returns immediately because the event-flag of old ISR´s fired is not clear and then in the next loop EnterSTOPMode() wait for a tc_wakeup time.

Thank you very much for the quickly response. It is very clear now.

juliobc commented 2 years ago

Hi @JonatanAntoni

I use STMF4 micros and I have noticed that two instructions are executed inside EnterSTOPMode() in the ST HAL libray:

void HAL_PWR_EnterSTOPMode()
{
  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));

  /* Request Wait For Event */
  __SEV();
  __WFE();
  __WFE();

  /* Reset SLEEPDEEP bit of Cortex System Control Register */
  CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));  
}

so, I will lost the Thread_test in ready state if that test_ISR is fired between osKernelSuspend() and EnterSTOPMode(). Is it right?

Best regards

JonatanAntoni commented 2 years ago

Hi @juliobc,

Regarding the ST implementation you might want to reach out to the ST community as well.

If I understand the code sequence correctly:

  __SEV();  // Force the event to be set
  __WFE();  // Force the event to be cleared without going to sleep
  __WFE();  // Go to sleep

So, yes, running WFE twice will clear all events that occured before. This looks strange to me as it causes race conditions with ISRs/events coming in.

In order to clear events that have been handled, properly, it might make sense to add the clearing part of the sequence before suspending the kernel. I.e.,

while (true) {
    __SEV();  // Force the event to be set
    __WFE();  // Force the event to be cleared without going to sleep

    tc_wakeup = osKernelSuspend();

    if (tc_wakeup > 0) {

This could reduce the amount of loop-repetitions just for clearing previous events from normal operation.

Cheers, Jonatan

juliobc commented 2 years ago

Hi @JonatanAntoni

I am doing some test and I have a question. I modified the function to enter in stop mode to have only one __WFE() like this:

void HAL_PWR_EnterSTOPMode()
{
  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));

  /* Request Wait For Event */
//  __SEV();
//  __WFE();
  __WFE();

  /* Reset SLEEPDEEP bit of Cortex System Control Register */
  CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));  
}

and then HAL_PWR_EnterSTOPMode() exit immediately. I think this is normal because any ISR fired before has set the event flag, but now I force clearing that flag before suspending the kernel like this:

while (true) {
    __SEV();  // Force the event to be set
    __WFE();  // Force the event to be cleared without going to sleep

    tc_wakeup = osKernelSuspend();

    if (tc_wakeup > 0) {

but now, HAL_PWR_EnterSTOPMode() exit immediately again. Then I clear again that event flag just after kernel suspend like this:

while (true) {
    __SEV();  // Force the event to be set
    __WFE();  // Force the event to be cleared without going to sleep

    tc_wakeup = osKernelSuspend();
    __SEV();  // Force the event to be set
    __WFE();  // Force the event to be cleared without going to sleep

    if (tc_wakeup > 0) {

and now the Stop mode seems to work correctly!! Is like the function:

/// Suspend the RTOS Kernel scheduler.
uint32_t osKernelSuspend (void) {
  uint32_t ticks;

  EvrRtxKernelSuspend();
  if (IsException() || IsIrqMasked()) {
    EvrRtxKernelError((int32_t)osErrorISR);
    ticks = 0U;
  } else {
    ticks = __svcKernelSuspend();
  }
  return ticks;
}

is setting the event flag. It is possible? Could I to see the event flag in debug in anywhere in the uvision IDE to verify that? I do not found it. Thank you very much

JonatanAntoni commented 2 years ago

Hi @juliobc,

Debugging low power mode is a hard challenge. Once you put the device into low power mode the debug connection typically gets lost. Furthermore, the event flag unfortunately is not exposed anywhere, it cannot be read by software nor by the debugger.

The SVC will set the event flag as well, yes. I think you need to disable interrupts before running osKernelSuspend and enable it again after wakeup. The clearing sequence after osKernelSuspend looks prone to a race to me.

I'd give this sequence a try:

SCB->SCR = SCB->SCR | SCB_SCR_SEVONPEND_Msk; // set SCR.SEVONPEND
while (true) {
    __SEV();  // Force the event to be set
    __WFE();  // Force the event to be cleared without going to sleep
        __disable_irq();

    tc_wakeup = osKernelSuspend();

    if (tc_wakeup > 0) {
        // program wakeup timer
        __WFE();
        }

        // retrieve ticks passed during sleep
    osKernelResume(ticks);
        __enable_irq();
}
juliobc commented 2 years ago

Hi @JonatanAntoni

I have a question about that sequence. When you call to osKernelSuspend(), then are you setting the event flag? If you force the event to be set when kernel suspends then _WFE() exit immediately. Is that correct?

JonatanAntoni commented 2 years ago

Hi @juliobc,

We do not "force" the event flag to be set. When calling RTOS service functions we use SuperVisorCalls (SVC) to isolate the context. This implicitly sets the event flag. Hence, I suggest to disable interrupts. Once interrupts are disabled the RTOS services are executed without SVC. Thus the event flag should not get set anymore.

Cheers, Jonatan

juliobc commented 1 year ago

Hi @JonatanAntoni ,

only for curiosity, I still studying this workaround 🤦‍♂️

The system is working but this could be improved. I can not disabe IRQ because I can miss any external interrupt while in sleep mode. I still thinking.

Maybe I could disable IRQ only when I go into kernel suspend? Like this:

SCB->SCR = SCB->SCR | SCB_SCR_SEVONPEND_Msk; // set SCR.SEVONPEND
while (true) {
    __SEV();  // Force the event to be set
    __WFE();  // Force the event to be cleared without going to sleep

        __disable_irq();
    tc_wakeup = osKernelSuspend();
        __enable_irq();

    if (tc_wakeup > 0) {
        // program wakeup timer
        __WFE();
        }

        // retrieve ticks passed during sleep
    osKernelResume(ticks);
}

I have a question. The line

SCB->SCR = SCB->SCR | SCB_SCR_SEVONPEND_Msk; // set SCR.SEVONPEND

is required for __SEV(); to work fine? What is the effect if I comment that line?

JonatanAntoni commented 1 year ago

Please consider raising such questions at https://community.arm.com/. This question is not CMSIS related and the experts around tick-less operation do not read here.

__WFE should wake up the device once any interrupt pending bit gets set, even if serving interrupts is disabled at that point. If consecutive external interrupts arrive faster than the device is able to resume, they still can get lost.

juliobc commented 1 year ago

Hi @JonatanAntoni

ok, I will go to ARM community Thank you very much for your support.