Crashing through __WFI - Page 1
Forum section: electronics / general

author: fearless
author_rank: Contributor
timestamp: September 01, 2023, 04:05:16 pm
content: Context: I'm working with a SAME54, my intent is to put the processor into BACKUP mode until an RTC match wakes it up. Once in a blue moon, I observe execution "crashing through" the __WFI instruction that is supposed to put it into the sleep mode. At the time this function is called, there should not be any pending interrupts.Code:Code:static void enter_backup_mode(void) {/* Configure Backup Sleep, wait for registers to report correct values */PM_REGS->PM_SLEEPCFG = PM_SLEEPCFG_SLEEPMODE_BACKUP_Val;while ((PM_REGS->PM_INTFLAG & PM_INTFLAG_SLEEPRDY_Msk) == 0U) {}while (PM_REGS->PM_SLEEPCFG != PM_SLEEPCFG_SLEEPMODE_BACKUP_Val) {}__WFI();// Normally should not arrive here, but it's possible that stray interrupt// is causing the WFI to behave as a NOP.NVIC_SystemReset();}Hypothesis: If I understand correctly, if there's an interrupt pending at the time the __WFI is called, then the __WFI behaves as a NOP. So that might be what's happening.Question: is there a straightforward way to find out if there are any pending interrupts (and possibly clear them) before executing the __WFI?

author: ejeffrey
author_rank: Super Contributor
timestamp: September 01, 2023, 05:16:22 pm
content: As far as I know __WFI is not guaranteed to be free from spurious wakeup and should always be in a loop.

author: fearless
author_rank: Contributor
timestamp: September 01, 2023, 05:35:31 pm
content: QuoteCan you expand on that? On the SAME54, waking from BACKUP mode is (always) a reset, so it's not clear how you'd use a loop.I suppose you could check to see the wakeup cause upon starting the code and just __WFI again if it's not an RTC match interrupt. But since the bootloader is the first bit of code to be run, and since there are other circumstances that you DO want to continue (e.g. just loaded new code, got a directive from the debugger, etc.), that logic could get highly convoluted.I'll post an alternate suggestion for critique...

author: fearless
author_rank: Contributor
timestamp: September 01, 2023, 05:38:14 pm
content: Here's my current thought. Just before calling __WFI, execute this bit of code:Code:void prep_interrupts(void) {for (IRQn_Type line = 0; line <= PERIPH_MAX_IRQn; line++) {if (line != RTC_IRQn) {if (NVIC_GetEnableIRQ(line)) {NVIC_DisableIRQ(line); // can put debugger bpt here to see which interrupt is enabled}}}}The intent is to make sure that all interrupts (except for RTC) are disabled prior to calling __WFI. Any known gotcha's with that?

author: Jeroen3
author_rank: Super Contributor
timestamp: September 01, 2023, 06:08:58 pm
content: Quote from: fearless on September 01, 2023, 04:05:16 pmThe nvic pending register should tell you. However... that should always read 0 in normal context.Interrupts don't have to be enabled in the nvic to cause a wake-up. So you have to turn them off at the source.Your debugger can also make wfi skip.

author: fearless
author_rank: Contributor
timestamp: September 02, 2023, 04:31:07 am
content: Quote from: Jeroen3 on September 01, 2023, 06:08:58 pmJust to make sure I understand what you're saying: If I executeCode:<configure for BACKUP mode...>;NVIC_DisableIRQ(SERCOM0_0_IRQn);__WFI();asm("nop");... and SERCOM0 subsequently generates an interrupt, it will execute the NOP following the __WFI?!? That doesn't make sense to me -- all of the documentation (and all of my experience) says that the SAME54 will wake from BACKUP mode with a reset; not by continuing from the __WFI.If on the other hand you're saying that a SERCOM0 interrupt will wake the SAME54 from BACKUP mode and arrive at the NOP instruction, well, I'm not sure that's right either. Only a limited number of sources can wake the processor from BACKUP mode, including RTC, WDT, BOD33, System Reset and a couple others.On the third handit's entirely possible I missed your point.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: September 02, 2023, 06:04:26 am
content: I’m not familiar with SAMs, but perhaps there is some latency between hitting the WFI instruction and actually entering the BACKUP mode, during which is possible to “exit” the WFI? (the WFI instruction itself is not a direct “hey, core, enter low power mode now!” thing, it’s main purpose is to wait for interrupt and continue, the low power entry is done by some other hw block triggered by the core upon hitting a WFI). Check the RM for BACKUP entry timing.Or, if you are saying that true exit from BACKUP is system reset (on a hw level), it should be ok to just dowhile(1) { __WFI(); }- a spurious “WFI exit” would just retry the WFI, but a real BACKUP exit would reset and never get looped here.

author: bugnate
author_rank: Regular Contributor
timestamp: September 02, 2023, 06:28:31 am
content: ejeffrey told you the the only thing you really need to know here:Quote from: ejeffrey on September 01, 2023, 05:16:22 pmThe "in a loop" bit just means that you have to be able to detect that a spurious wakeup happened (eg RTC time was less than expected) and re-try the sleep. There are lots of reasons why spurious wakeups could happen, some you can mitigate (eg disable peripherals) and some you cannot. But you must code assuming they can happen.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 02, 2023, 06:56:58 am
content: Pseudocode:WFIcheck what to do, + do itWFI againExpressed as loop:while(1){WFIcheck what to do, + do it}Expressed differentlyAGAIN:WFIcheck what to do, + do itgoto AGAIN;Not difficult.

author: nctnico
author_rank: Super Contributor
timestamp: September 02, 2023, 08:11:17 am
content: IIRC it is possible not all interrupts are disabled immediately but need a few clock cycles to propagate through the peripherals. Looking at my own code I wrote a long time ago (for NXP LPC1700 series), I see I have 3 __NOP operations before calling __WFI to make sure all interrupts are actually disabled & handled before going to sleep.Adding some more debugging code to check the wake-up source (as others suggested) is a good plan to see if there aren't any interrupt enabled that shouldn't be. I'm also wondering why the MCU is reset after a wake-up. Why not re-initialise the parts that are disabled and continue?

author: fearless
author_rank: Contributor
timestamp: September 02, 2023, 01:23:38 pm
content: I believe I finally understand. It's important to understand that the SAME54 -- when conditioned to enter BACKUP mode -- will enter BACKUP mode upon executing __WFIprovided there's no pending interrupt. And the ONLY return from BACKUP mode is a system reset. This is why I thought a loop wouldn't make sense.But as I understand it, if there is a pending interrupt, the __WFI acts like a NOP and will "crash through" as I initially described it. So the following is a candidate:Code:<prep SAME54 for backup mode>while(1) {__WFI(); // normally this will enter BACKUP mode and never return (since waking from BACKUP is a system reset)asm("nop"); // execution will arrive here ONLY IF the SAME54 did not enter BACKUP mode.}I'll give that a shot. If anyone has specific experience with the SAME54 and its backup mode, feel free to chime in!

author: fearless
author_rank: Contributor
timestamp: September 02, 2023, 01:25:19 pm
content: Quote from: nctnico on September 02, 2023, 08:11:17 am...because that's how the SAME54 is designed to work in BACKUP mode. It provides other low-power modes (e.g. STANDBY) that do what you describe.

author: fearless
author_rank: Contributor
timestamp: September 02, 2023, 01:29:56 pm
content: Quote from: abyrvalg on September 02, 2023, 06:04:26 amYes - I believe you correctly described the solution as well as why it works. Thanks.

author: ejeffrey
author_rank: Super Contributor
timestamp: September 02, 2023, 02:57:45 pm
content: Quote from: fearless on September 02, 2023, 01:23:38 pmYes that all sounds right with a couple of refinements. 1) there is likely a window between WFI executing and backup mode activating, it's likely that interrupts in this time window can also cause it to wake up. This doesn't really matter as there is no way to tell whether it happened "right before" or "right after" WFI but it might increase the window of vulnerability more than you expect 2) other CPU conditions (including non use visible state) can also potentially cause WFI to wake / not sleep debug probe interactions are the most common and maybe the only example on a processor like this but the point is to not try to prevent every possible interrupt to make WFI "reliable" but to use a loop to reenter it.

author: bson
author_rank: Unknown
timestamp: September 02, 2023, 08:00:51 pm
content: If you have interrupts disabled via PRIMASK but there's a pending interrupt, then WFI won't block. So the most likely cause here is a bug in your program that doesn't reenable interrupts prior to WFI.

author: Jeroen3
author_rank: Super Contributor
timestamp: September 02, 2023, 09:23:50 pm
content: You did read this?QuoteWhat you can try is turning off all peripherals first.Two things you must consider.1. The NVIC receives signals from the chip. The NVIC is a core component, not modified by the vendor.2. The entry to sleep is hooked into the _WFI instruction. So you must first ensure the core design requirements are fulfilled for _WFI to work, before even the backup state is initiated.Quote

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 02, 2023, 09:27:56 pm
content: Quote from: Siwastaja on September 02, 2023, 06:56:58 amYes that's how it should usually be done.Then it doesn't matter whether WFI returns immediately or not.Using it *merely* to wait on some interrupt while being guaranteed to do nothing until said interrupt happens is IMO a misuse of WFI.The whole benefit is that it usually puts the CPU in a low-power mode. If you don't need low-power, a simple busy loop would do the trick just fine.Calling WFI in a "busy loop", as shown above, is (again, usually) the right compromise.Of course, if you have one interrupt source that potentially wakes up the CPU too often (which could counterbalance the benefit of the low power achieved with WFI), and this interrupt doesn't interest you as a wake-up source, you may want to disable it before. It all depends on the use case really, but it's often not a good idea and the simple wait loop is much simpler and much safer.If the goal is to put the CPU into some sleep mode and have it woken up only from a given wake-up event, many MCU do have other means of achieving this, some waking up only from a given wake-up event, configurable and independent from interrupts, so that both can be handled separately, and you don't need to constantly mess with interrupts enabling and disabling.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: September 03, 2023, 12:03:25 pm
content: Just for curiosity: try replacing WFI with WFE. If my guess is correct, WFE should also trigger the low power mode entry, but it should ignore occasional pending interrupts (since it is Wait ForEvent). Such replacement would be wrong for LP modes allowing wake up and continue (WFE has a different exit condition), but could be and easier to use option for "wake up via reset" modes.

author: fearless
author_rank: Contributor
timestamp: September 05, 2023, 07:20:39 pm
content: Quote from: Jeroen3 on September 02, 2023, 09:23:50 pmAll true. The "prep SAME54 for backup mode" code that I didn't show does just that. The full implementation is:Code:static void enter_backup_mode(void) {/* Configure Backup Sleep */PM_REGS->PM_SLEEPCFG = PM_SLEEPCFG_SLEEPMODE_BACKUP_Val;/* Wait till the voltage regulator low power mode is ready */while ((PM_REGS->PM_INTFLAG & PM_INTFLAG_SLEEPRDY_Msk) == 0U) {}/* Wait till SLEEPCFG reads the desired value */// NOTE: this while() loop is omitted in the library PM_BackupModeEnter()// implementation (v3.16.0), though the documentation requires it.// If a future version on PM_BackupModeEnter() corrects this, this code// should revert to using the library version.while (PM_REGS->PM_SLEEPCFG != PM_SLEEPCFG_SLEEPMODE_BACKUP_Val) {}while(true) {// If no interrupt is pending, this will enter BACKUP mode and will only// exit BACKUP mode with a reset. If an interrupt is pending, the __WFI// will behave as a no-op and execution will continue...__WFI();// Arrive here if there was a pending interrupt. Toggle TP_J103P6 to// show that we hit this point and loop until the interrupt is serviced.TP_J103P6_OutputEnable();TP_J103P6_Toggle();}}

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 05, 2023, 08:39:36 pm
content: Quote from: abyrvalg on September 03, 2023, 12:03:25 pmYes, WFE is the ARM Cortex way of doing what I suggested (waiting on events rather than interrupts). The wake-up source(s) have to be set up properly beforehand.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: September 05, 2023, 10:02:10 pm
content: What I’m trying to say: for LP modes where the core state is not maintained (like OP’s mode resuming via reset) a WFE could be a better alternative to WFI since it does not depend on pending interrupts and should go to sleep from a first try.And it should work with the same wakeup configuration since the wakeup is thru reset and the core would not remember was it WFI or WFE, we don’t need to satisfy these instruction’s wait conditions to wake up.

author: fearless
author_rank: Contributor
timestamp: September 06, 2023, 12:59:54 am
content: Though I 've never seen Microchip suggest WFE instead of WFI for entering backup mode, I'm intrigued and will give it a try as soon as I stabilize the current set of changes.Many thanks for the suggestions!

author: Jeroen3
author_rank: Super Contributor
timestamp: September 06, 2023, 06:09:54 am
content: Is it possible that entering WFI or sleep causes some analog glitch in your specific situation that triggers wakeup?