-
Notifications
You must be signed in to change notification settings - Fork 779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[pwrmgr] Deep/Normal sleep enter and WFI interacttion seems incorrect #23351
Comments
We actually are seeing this for normal sleep enter as well. The |
I am might be wrong since I am not a RISC-V expert but I think the spec makes no guarantee about the cirmcumstances in which a core wakes up from Maybe @nbdd0121 or @andreaskurth can say more on this? |
Yeah, wfi can spuriously wake up or be implemented as nop. Moreover, even with wfi implemented to not spuriously wake up, it also needs wake up (by spec) when there are interrupts pending but masked by global interrupt enables (MIE), so I wouldn't suggest rely on WFI stalling the processsor until deep sleep kicks in. |
@jettr Are you sure you're disabling all interrupts that aren't supposed to wake up the device? Note that this does not mean merely disabling Ibex's global interrupt enable bit. That is not at all sufficient, as it doesn't "disable" interrupts from a sleep/wake perspective. The global interrupt enable bit only controls whether the branch to an ISR is taken--Despite its name, it should not be associated with the concept of "disabling interrupts" for this purpose. From the system's sleep/wake perspective (including WFI), interrupts are still "enabled." What you're calling "second-level" interrupts are the actual final gates of the interrupt signals. Those are the roots for disabling interrupts to Ibex. If you expect normal sleep to wake up from some external interrupts, that means you'll need to disable interrupts at the PLIC (or individual IP instances, if you prefer). We probably ought to clarify this in the docs here: opentitan/hw/ip_templates/pwrmgr/doc/programmers_guide.md Lines 8 to 10 in 71f4edb
Maybe we should use different lingo, though. Perhaps "disable interrupts" can continue to mean "disable taking the branch to the ISR," and we can use "mask interrupts" to mean "prevent the interrupt from signaling inside Ibex's controller." |
The deep sleep case seems somewhat reasonable, so let's not spend too much time discussing that case. However, the normal sleep case is the more concerning case as the FW expects to resume execution after the
Are you suggesting that even for normal sleep (where the core should continuing executing from the same PC after normal sleep wake up), that we should be setting What should the OT programmers guide for putting the chip into a normal sleep be?
It does appear that the interrupt that is allowing I have included our sleep function to make the discussion a little more concrete: fn enter_sleep(&self) {
let rbox = unsafe { &RBOX0 };
let pinmux = unsafe { &PINMUX };
let deep_sleep = self.deep_sleep_enabled.get();
if deep_sleep {
self.log_breadcrumb(BreadcrumbValue::PmuDeepSleep);
direct_debug!("\nEntering deep sleep zzz\n");
let _ = self.set_long_life_value(LongLifeValue::EnteredDeepSleep, true as u32);
let _ = self.set_long_life_value(
LongLifeValue::DeepSleepCounter,
self.get_long_life_value(LongLifeValue::DeepSleepCounter) + 1,
);
} else {
self.log_breadcrumb(BreadcrumbValue::PmuNormalSleep);
direct_debug!("\nEntering normal sleep\n");
}
// Wait until main UART HW FIFO is flushed
flush_uart();
let sleep_start_ms = Chip::ticks_ms() as u32;
// Disable interrupts to prepare for sleep, following the steps from
// https://opentitan.org/book/hw/top_earlgrey/ip_autogen/pwrmgr/doc/programmers_guide.html
CSR.mstatus.modify(mstatus::mie::CLEAR);
// For deep sleep we want the WFI below to never return (because resume will follow boot
// path). Ensure this by disabling interrupts in CSR.mie in addition to CSR.mstatus
// (because WFI ignores CSR.mstatus).
if deep_sleep {
CSR.mie
.modify(mie::msoft::CLEAR + mie::mtimer::CLEAR + mie::mext::CLEAR);
}
rbox.enter_sleep(deep_sleep);
pinmux.enter_sleep(deep_sleep);
// Enable wake on RBOX, ADC, and pinmux.
self.registers.wakeup_en[0].set(RBOX_WAKE_MASK + ADC_WAKE_MASK + PIN_WAKE_MASK);
self.registers.wake_info.set(self.registers.wake_info.get());
self.registers
.wake_info_capture_dis
.write(WAKE_INFO_CAPTURE_DIS::VAL::CLEAR);
if deep_sleep {
self.registers.control.set(0);
} else {
self.registers.control.write(CONTROL::MAIN_PD_N::SET);
}
self.registers.control.modify(CONTROL::LOW_POWER_HINT::SET);
// Wait for wakeup_en and control writes.
self.cfg_cdc_sync();
// This WFI will attempt to sleep, because we set LOW_POWER_HINT.
unsafe { rv32i::support::wfi() };
// Deep sleep should never get here because on exit from deep sleep we start in ROM and
// follow the boot path. If we do get here, then force a reset to recover.
if deep_sleep {
self.log_breadcrumb(long_life_value::BreadcrumbValue::DeepSleepWfiShortCircuit);
self.trigger_hard_reset()
}
// Now we have resumed from normal sleep (or fell through without sleeping).
// Restore control register to default. This clears LOW_POWER_HINT.
self.registers
.control
.write(CONTROL::MAIN_PD_N::SET + CONTROL::USB_CLK_EN_ACTIVE::SET);
// Wait for control write.
self.cfg_cdc_sync();
self.registers.intr_state.write(INTR::WAKEUP::SET);
self.registers
.wake_info_capture_dis
.write(WAKE_INFO_CAPTURE_DIS::VAL::SET);
CSR.mstatus.modify(mstatus::mie::SET);
let sleep_end_ms = Chip::ticks_ms() as u32;
self.log_breadcrumb(BreadcrumbValue::PmuResumeFromSleep);
// Delay sleep to give time to stabilize after processing wakeup event.
self.delay_sleep_for(DelaySleepReason::ResumeNormalSleep);
console_info!("Sleep duration: {}ms", sleep_end_ms - sleep_start_ms);
console_info!("Wake source: {:#010x}", self.wake_source());
if self.wake_caused_by(WakeCause::Pin) {
console_info!("Which pin: {:#018x}", self.exit_pins_status());
}
} Note the comment We have noticed we get to somewhere around |
For any low power mode, breaking out of WFI can cause a fall through exit that ends the request. To enter low-power mode, the CPU must remain in WFI continuously through pwrmgr's fast FSM transition. Thus, for any interrupts that could trigger, you must mask interrupt sources that you don't want to cause that fall through exit.
No, definitely not specifically that, haha. In normal sleep, you still need an interrupt to satisfy WFI! You can't mask all the interrupts. By contrast, deep sleep causes a reset of the CPU, so it "breaks out" of WFI as a sort of side effect. But again, you still need to handle interrupt sources here, since the possibility of a fall through exit remains. To clarify, there are four important elements in this system. The main participants are...
For entering low-power mode, the CPU interacts with the pwrmgr Fast FSM by telling it when it is in WFI. Then, the pwrmgr Fast FSM tells the pwrmgr Slow FSM when it has cleared checks, disabled clocks, and entered low-power mode. Finally, the Slow FSM monitors for wakeup sources. For exiting low-power mode, the Slow FSM gets a kick from a wakeup source, and it tells the pwrmgr Fast FSM to perform its power-on sequence (e.g. turn on clocks). Critically, if the CPU is still in WFI, it will stay here if there is no pending interrupt, and the CPU is effectively frozen (though technically, an NMI or entering debug mode could still break it out, haha). From that ti50 code, there's no indication that the interrupt sources are being addressed, but it's required. The interrupts and wakeups work together to manage low power entry/exit. The entry sequence is what I wrote in #23390: 1. Disable interrupt handling.
2. Mask all interrupt sources that should not prevent low power entry.
- Note that merely *disabling* interrupt handling with the `mie` global interrupt-enable bit on the processing host is insufficient.
- Interrupt sources that are not masked can cause the [fall through exit](theory_of_operation.md#fall-through-handling).
3. Enable desired wakeup and reset sources in [`WAKEUP_EN`](registers.md#wakeup_en) and [`RESET_EN`](registers.md#reset_en).
4. Perform any system-specific low power entry steps, e.g.
- Interrupt checks (if something became pending prior to disable)
5. Configure low power mode in [`CONTROL`](registers.md#control).
6. Set low power hint in [`LOW_POWER_HINT`](registers.md#control--low_power_hint).
7. Set and poll [`CFG_CDC_SYNC`](registers.md#cfg_cdc_sync) to ensure above settings propagate across clock domains.
8. Execute wait-for-interrupt instruction on the processing host. If you follow the sequence, then low power exit should only occur from interrupts you wanted as wakeups in the first place. |
Specifically you have |
Ah, that was the original thing you mentioned, wasn't it? Sorry, I got into the discussion later and latched onto the chat about the processes. So I'd have to ask how you know that is the case, since there would be a lot of difficult-to-observe state here. However, if the CPU did indeed continue executing after the Fast FSM transitioned from Or we simply went through a full cycle and came back to the same place but didn't fall through the second time, hehe. |
It is the best explanation for what we see on our UART lines when this occurrs. Here is an example UART output when we hit the delayed sleep after
Here is a typical normal sleep:
You can see on a scope capture of the UART that the The |
Thank you for this hint. I should have thought of it sooner, now it seems obvious. We fall through the first WFI and then sleep on a second WFI. The problem is that I want to avoid sleeping on that second WFI, but that doesn't seem to be working. My write to set pwrmgr.CONTROL.LOW_POWER_HINT=0 after the WFI fallthrough seems to be ignored.
doesn't work, because CONTROL=0x101 afterwards shows LOW_POWER_HINT_BIT is still set. |
The docs here are bad, I think (particularly, the programming guide). LOW_POWER_HINT can only automatically clear, and it does so as soon as the clocks are disabled. As soon as you set LOW_POWER_HINT to 1, the CONTROL register locks! However, that doesn't seem to be what's happening here, since LOW_POWER_HINT is 1, not 0. Instead, I suspect Ibex never manages to enter WFI (or at least, to successfully signal that it is sleeping to pwrmgr). For one thing, you have a pending interrupt before even executing the Meanwhile, pwrmgr's clock runs at potentially less than 1/4 the rate of Ibex, and it merely has a 2-flop synchronizer to bring over the I'm not sure the FSM generally has the right model to handle all the events from this faster domain. It might need a critical eye in light of these possibilities. Here are some extra bits that don't look to be immediately relevant to this particular case, though: There is also a little bit of a race in the (Hopefully someone can show me that I've missed something and at least some of these statements are errors on my part, haha) |
Ok, thanks. I see "this bit is automatically cleared by HW" in the docs, but wasn't sure this meant I can't clear it. |
Aha, I missed that the FSM has a The WFI signaling still needs to be addressed. |
It seems the unclear behavior in this issue is progressively shrinking. I am still having a problem sorting out what is the actual problem: Is it
To go to sleep the fast FSM needs low_power_entry_i == 1 in the FastPwrStateActive state, and to stay at 1 until past the FastPwrStateFallThrough state, and low_power_entry_i == core_sleeping & low_power_hint. Can someone explain what is the actual problem with this logic, meaning what signaling is suspicious? |
I've reviewed the RTL behind the Internally the I'd rather not alter the behaviour of the current |
An alternative would be a |
Now that @a-will 's PR to fix up pwrmgr got merged, I think what is left here is documenting the correct SW routine around sleep entry and making sure software follows that guidance, correct? |
I have no further concerns. Thank you! |
Lowering the priority to P2 now that an RTL fix has been merged. Moving to M5 since remaining work is related to documentation. @a-will, please let us know if you have any objections to this. Thanks! |
Just discussed in triage: @a-will, if you have a chance to complete this documentation for M5 (after your ongoing SDC contributions), this would be helpful. Otherwise we'll take this to the next milestone and cherry pick to the release branch (or master). |
@andreaskurth #23390 is already queued to close this issue and awaiting review. |
Description
When entering enter deep sleep via
wfi
instruction (while performing all of the correct procedures), the core can wake up from thewfi
instruction and proceed to execute the next instructions while the deep sleep mechanism is in the process of turning the core off. The deep sleep hardware will eventually turn off the core at some later point; this doesn't seem like the correct behavior.I think either of the following are more consistent:
wfi
instruction, the deep sleep mechanism is cancelledwfi
instruction.This is not blocking us, since we can disable all of the "second-level" interrupt enable bits (e.g.
mie::msoft::CLEAR + mie::mtimer::CLEAR + mie::mext::CLEAR
) to ensure thatwfi
can never return back to the core.The text was updated successfully, but these errors were encountered: