eclipse-threadx / threadx

Eclipse ThreadX is an advanced real-time operating system (RTOS) designed specifically for deeply embedded applications.
https://github.com/eclipse-threadx/rtos-docs/blob/main/rtos-docs/threadx/index.md
MIT License
2.89k stars 784 forks source link

Interrupts are not enabled before entering low power mode #279

Open hadongzhu opened 1 year ago

hadongzhu commented 1 year ago

Hi,

I'm using ThreadX (6.2.1) on STM32H750 (Cortex-M7) with AC6 toolchain. When TX_PORT_USE_BASEPRI is enabled, it can not exit low power mode when an interrupt masked by BASEPRI occurs.

I find that interrupts are not enabled before entering low power mode, in .\ports\cortex_m7\ac6\src\tx_thread_schedule.S:

__tx_ts_wait:
#ifdef TX_PORT_USE_BASEPRI
    LDR     r1, =TX_PORT_BASEPRI                    // Mask interrupt priorities =< TX_PORT_BASEPRI
    MSR     BASEPRI, r1
#else
    CPSID   i                                       // Disable interrupts
#endif
    LDR     r1, [r2]                                // Pickup the next thread to execute pointer
    STR     r1, [r0]                                // Store it in the current pointer
    CBNZ    r1, __tx_ts_ready                       // If non-NULL, a new thread is ready!

#ifdef TX_LOW_POWER
    PUSH    {r0-r3}
    BL      tx_low_power_enter                      // Possibly enter low power mode
    POP     {r0-r3}
#endif

#ifdef TX_ENABLE_WFI
    DSB                                             // Ensure no outstanding memory transactions
    WFI                                             // Wait for interrupt
    ISB                                             // Ensure pipeline is flushed
#endif

#ifdef TX_LOW_POWER
    PUSH    {r0-r3}
    BL      tx_low_power_exit                       // Exit low power mode
    POP     {r0-r3}
#endif

#ifdef TX_PORT_USE_BASEPRI
    MOV     r4, #0                                  // Disable BASEPRI masking (enable interrupts)
    MSR     BASEPRI, r4
#else
    CPSIE   i                                       // Enable interrupts
#endif
    B       __tx_ts_wait                            // Loop to continue waiting

Interrupts are enabled after WFI. and in .\utility\low_power\tx_low_power.c:

VOID  tx_low_power_enter(VOID)
{
...
    /* Disable interrupts while we prepare for low power mode.  */
    TX_DISABLE
...
    /* Re-enable interrupts before low power mode is entered.  */
    TX_RESTORE

    /* User code to enter low power mode. This allows the application to power down
       peripherals and put the processor in sleep mode.
    */
#ifdef TX_LOW_POWER_USER_ENTER
    TX_LOW_POWER_USER_ENTER;
#endif

    /* If the low power code returns, this routine returns to the tx_thread_schedule loop.  */
}

Since the interrupt is disabled before calling tx_low_power_enter, TX_RESTORE in tx_low_power_enter cannot re-enable interrupts. So interrupts are not enabled before entering low power mode. It results in interrupts masked by BASEPRI not being able to make it exit from low power mode. And when using PRIMASK to disable interrupts, there is no such problem, it can still exit low power mode when any interrupt occurs.

wangwen-4220 commented 1 year ago

@hadongzhu, Thanks, you are right that it cannot exit low power mode when an interrupt masked by BASEPRI occurs When TX_PORT_USE_BASEPRI is enabled. Here is a workaround.

#ifdef TX_ENABLE_WFI
#ifdef TX_PORT_USE_BASEPRI
    CPSID   i
    MOV     r1, #0                    
    MSR     BASEPRI, r1
#endif 
    DSB                                             // Ensure no outstanding memory transactions
    WFI                                             // Wait for interrupt
    ISB                                             // Ensure pipeline is flushed
#ifdef TX_PORT_USE_BASEPRI
    LDR     r1, =TX_PORT_BASEPRI                    
    MSR     BASEPRI, r1
    CPSIE   i
#endif   
#endif
hadongzhu commented 12 months ago

@wangwen-4220, thank you for your reply, your workaround works fine, but I think this is not the best solution. WFI instroction means "Wait For Interrupt", and in function tx_low_power_enter, the comment for TX_RESTORE is /* Re-enable interrupts before low power mode is entered. */, but it do not enable interrupts actually. So, it's may be It may be better to enable interrupts before entering low power mode, instead of after entering low power mode, like this:

__tx_ts_wait:
#ifdef TX_PORT_USE_BASEPRI
    LDR     r1, =TX_PORT_BASEPRI                    // Mask interrupt priorities =< TX_PORT_BASEPRI
    MSR     BASEPRI, r1
#else
    CPSID   i                                       // Disable interrupts
#endif
    LDR     r1, [r2]                                // Pickup the next thread to execute pointer
    STR     r1, [r0]                                // Store it in the current pointer
    CBNZ    r1, __tx_ts_ready                       // If non-NULL, a new thread is ready!

#ifdef TX_PORT_USE_BASEPRI
    MOV     r4, #0                                  // Disable BASEPRI masking (enable interrupts)
    MSR     BASEPRI, r4
#else
    CPSIE   i                                       // Enable interrupts
#endif

#ifdef TX_LOW_POWER
    PUSH    {r0-r3}
    BL      tx_low_power_enter                      // Possibly enter low power mode
    POP     {r0-r3}
#endif

#ifdef TX_ENABLE_WFI
    DSB                                             // Ensure no outstanding memory transactions
    WFI                                             // Wait for interrupt
    ISB                                             // Ensure pipeline is flushed
#endif

#ifdef TX_LOW_POWER
    PUSH    {r0-r3}
    BL      tx_low_power_exit                       // Exit low power mode
    POP     {r0-r3}
#endif

    B       __tx_ts_wait                            // Loop to continue waiting