-
Notifications
You must be signed in to change notification settings - Fork 8.3k
drivers: systick: Improve the logic for detecting counter wrap-around events when exiting low power mode. #99877
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
base: main
Are you sure you want to change the base?
drivers: systick: Improve the logic for detecting counter wrap-around events when exiting low power mode. #99877
Conversation
mathieuchopstm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me
|
During development, I found that the code logic in the current patch is not sufficient to cover all scenarios. I will update the PR later. |
ea166e8 to
8bc54cd
Compare
drivers/timer/cortex_m_systick.c
Outdated
| bool wrap_case = (idle_timer_pre_idle > idle_timer_post) || | ||
| ((idle_timer_pre_idle == idle_timer_post) && alarm_pending) || | ||
| ((idle_timer_pre_idle < idle_timer_post) && alarm_pending && | ||
| (idle_timer_alarm_ticks != 0) && | ||
| (uint64_t)idle_timer_pre_idle + idle_timer_alarm_ticks >= idle_timer_top); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm struggling to understand the new logic.
Quoting you:
3: idle_timer_pre_idle < idle_timer_post In this case, there are two possibilities:
- No wraparound occurred:
diff = post - pre(note thatalarm_pendingis also true in this case, but the condition(uint64_t)idle_timer_pre_idle + idle_timer_alarm_ticks >= idle_timer_topis not satisfied)- Wraparound occurred:
diff = top + post + 1 - pre(note thatalarm_pendingis true and the condition(uint64_t)idle_timer_pre_idle + idle_timer_alarm_ticks >= idle_timer_topis satisfied)
How can alarm_pending be true if the timer has not overflowed? Why would idle_timer_pre_idle + idle_timer_alarm_ticks >= idle_timer_top - "is the tick on which alarm was expected higher than the counter's max tick value" - affect whether or not the timer has overflowed?
After review and from my understanding of the counter API, I think we should just be checking counter_get_pending_int(): since interrupts are suspended during a PM sequence, the timer cannot service its IRQ and thus the interrupt pending status is a reliable[1] indicator of whether or not an overflow occurred.
[1] It's only reliable up to one overflow: two or more will go undetected, but this should never occur...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the current shortcoming of the counter API. We obtain alarm_pending through counter_get_pending_int, but both alarm and top can cause pending_int. Let me give an example to illustrate what happens if we only use alarm_pending as the judgment condition:
Top=100, scheduled_ticks=90, Pre=60, actual measured Post=70.
- Possible without wraparound (
alarm_pending=false):diff = post - pre; - Possible with wraparound (
alarm_pending=true): diff = top + post + 1 - pre;
Top=100, scheduled_ticks=60, Pre=10, actual measured Post=70. We can't use the above formula anymore, because an alarm was generated. At this time alarm_pending is also true, so using diff = top + post + 1 - pre would be wrong. Therefore, we need to determine the relationship between pre + scheduled_ticks and top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the current shortcoming of the counter API. We obtain
alarm_pendingthroughcounter_get_pending_int, but both alarm and top can causepending_int.
OK, that's the part I didn't understand. (In this case, maybe the SysTick driver should change the counter's top value to guarantee that pending == 1 = timer rollover?)
I'll go through the logic again with this new insight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top=100, scheduled_ticks=60, Pre=10, actual measured Post=70. We can't use the above formula anymore, because an alarm was generated. At this time
alarm_pendingis also true, so usingdiff = top + post + 1 - prewould be wrong. Therefore, we need to determine the relationship betweenpre + scheduled_ticksandtop.
I don't understand why this should be special cased: if Top=100 and Pre=10 and Post=70 (> Pre) and the counter is pending, it must be because of an alarm: if a rollover did occur, it would mean that Top + Post - Pre = 160 ticks have elapsed which is almost 3 times the Scheduled=60 - this should never be occurring?! (SoC is allowed to wake-up earlier than scheduled, but not (too much) later, as far as I understand)
Even though it is not enforced yet, I think Scheduled < Top should always be true, and if it is then Post > Pre implies no overflow occurred (because if it did, the + Top would mean that at least 2x more time than scheduled has elapsed, which to me seems impossible)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more brutal but simple solution is to say that the low-power timer is for exclusive usage by the SysTick driver, because it would allow us to reset the counter to 0 before sleeping, ensuring that pending corresponds to "rollover occurred" and nothing else(-ish?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top=100, scheduled_ticks=60, Pre=10, actual measured Post=70. We can't use the above formula anymore, because an alarm was generated. At this time
alarm_pendingis also true, so usingdiff = top + post + 1 - prewould be wrong. Therefore, we need to determine the relationship betweenpre + scheduled_ticksandtop.I don't understand why this should be special cased: if
Top=100andPre=10andPost=70 (> Pre)and the counter is pending, it must be because of an alarm: if a rollover did occur, it would mean thatTop + Post - Pre = 160ticks have elapsed which is almost 3 times theScheduled=60- this should never be occurring?! (SoC is allowed to wake-up earlier than scheduled, but not (too much) later, as far as I understand)
Post > Pre overflow can indeed occur. This is actually the situation I discovered during debugging. Assume Top=100, Pre=10, Scheduled=85. Theoretically, an alarm signal (interrupt) should be generated at 95 to wake up the MCU. However, on the MCU I was working with, there's a 20 idle timer ticks delay from when the idle timer alarm signal is issued to when the CPU actually resumes and starts executing code. During this time, the idle timer continues counting. By the time the CPU code executes the z_cms_lptim_hook_on_lpm_exit function, the idle timer has already advanced an additional 20 idle timer ticks, meaning the actual Post value obtained is 15. In this case, the calculation formula needed is: diff = top + post + 1 - pre. This is one type of "post > pre" case.
Another Post > Pre case is the scenario where wrap is unlikely to occur. For example, as I mentioned earlier: Top=100, Scheduled=60, Pre=10. In this situation, wrap is unlikely to happen. Even if the CPU has a 20 idle timer ticks delay, Post should be around 90, and in this case alarm_pending is caused by the alarm. The calculation formula needed is: diff = post - pre.
So we need to cover both of these cases. Using alarm_pending alone is insufficient. However, I now find that the condition: (uint64_t)idle_timer_pre_idle + idle_timer_scheduled_sleep_ticks >= idle_timer_top cannot be used to distinguish between these two cases either. Do you have any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Post > Preoverflow can indeed occur. This is actually the situation I discovered during debugging. AssumeTop=100,Pre=10,Scheduled=85. Theoretically, an alarm signal (interrupt) should be generated at95to wake up the MCU. However, on the MCU I was working with, there's a20 idle timer ticksdelay from when the idle timer alarm signal is issued to when the CPU actually resumes and starts executing code. During this time, the idle timer continues counting. By the time the CPU code executes thez_cms_lptim_hook_on_lpm_exitfunction, the idle timer has already advanced an additional20 idle timer ticks, meaning the actual Post value obtained is15. In this case, the calculation formula needed is:diff = top + post + 1 - pre. This is one type of "post > pre" case.
Thanks for the example. That's one edge case I had in the back of my head, but I had trouble finding an example that did break...
Do you have any suggestions?
I'll take the example quoted above. Post=15 could be obtained if:
- SoC woke up early, and only 5 ticks elapsed
- SoC woke up as expected but delay was observed, and 105 ticks have elapsed
Assuming no other alarm on the counter, counter_pending should be False in the former case but True in the latter... but I don't know if that applies in the general case.
Looking at tree right now, the only users of zephyr,cortex-m-idle-timer are ST and Analog Devices, and both are using a single-channel counter. Since the SysTick driver assumes ownership of channel 0, this means that de facto the SysTick drivers "owns" the idle timer.
What do you think of updating the documented semantics to say that the zephyr,cortex-m-idle-timer is owned by the SysTick driver, which allows us to reset the counter to 0 before entering idle? This should ensure that pending() can be reliably used to distinguish early wake-up from overflows (assuming the sleep period is capped to top_value ticks, it allows up to an "overhead delay" of 100%/top_value, which seems reasonable as you report your SoC is around 20%).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take the example quoted above.
Post=15could be obtained if:
- SoC woke up early, and only 5 ticks elapsed
- SoC woke up as expected but delay was observed, and 105 ticks have elapsed
Assuming no other alarm on the counter,
counter_pendingshould be False in the former case but True in the latter... but I don't know if that applies in the general case.
- Early wakeup should be a general case, and it's easy to detect (
counter_pendingis false, andpost > pre). - Not sure whether delayed wakeup is a general case, but it does occur on some NXP MCUs when waking from deep power-down mode. My idea is to not handle delayed wakeup case in the Systick driver for now, because when
post > preandcounter_pendingis true, the three cases I enumerated in the PR description currently cannot be distinguished (It's also possible that I missed some of the available conditions).
Looking at tree right now, the only users of
zephyr,cortex-m-idle-timerare ST and Analog Devices, and both are using a single-channel counter. Since the SysTick driver assumes ownership of channel 0, this means that de facto the SysTick drivers "owns" the idle timer.What do you think of updating the documented semantics to say that the
zephyr,cortex-m-idle-timeris owned by the SysTick driver, which allows us to reset the counter to0before entering idle? This should ensure thatpending()can be reliably used to distinguish early wake-up from overflows (assuming the sleep period is capped totop_valueticks, it allows up to an "overhead delay" of 100%/top_value, which seems reasonable as you report your SoC is around 20%).
The idle-timer currently used by NXP is also one channel. However, I think enhancing the counter API might be better, because I'm not sure whether there will be cases where the alarm is one of multiple channels in the idle-timer as more companies use this feature.
22d68d7 to
7101ec1
Compare
drivers/timer/cortex_m_systick.c
Outdated
| bool wrap_case = (idle_timer_pre_idle > idle_timer_post) || | ||
| ((idle_timer_pre_idle == idle_timer_post) && alarm_pending) || | ||
| ((idle_timer_pre_idle < idle_timer_post) && alarm_pending && | ||
| (idle_timer_alarm_ticks != 0) && | ||
| (uint64_t)idle_timer_pre_idle + idle_timer_alarm_ticks >= idle_timer_top); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Top=100, scheduled_ticks=60, Pre=10, actual measured Post=70. We can't use the above formula anymore, because an alarm was generated. At this time
alarm_pendingis also true, so usingdiff = top + post + 1 - prewould be wrong. Therefore, we need to determine the relationship betweenpre + scheduled_ticksandtop.
I don't understand why this should be special cased: if Top=100 and Pre=10 and Post=70 (> Pre) and the counter is pending, it must be because of an alarm: if a rollover did occur, it would mean that Top + Post - Pre = 160 ticks have elapsed which is almost 3 times the Scheduled=60 - this should never be occurring?! (SoC is allowed to wake-up earlier than scheduled, but not (too much) later, as far as I understand)
Even though it is not enforced yet, I think Scheduled < Top should always be true, and if it is then Post > Pre implies no overflow occurred (because if it did, the + Top would mean that at least 2x more time than scheduled has elapsed, which to me seems impossible)
47ec271 to
ec2f0ca
Compare
|
Hello @mathieuchopstm, I have updated the code and the PR description, please help to review again, thanks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just add comments in the code, otherwise seems reasonable...
(these are just suggestions: feel free to reword)
Improve the logic for detecting counter wrap-around events when exiting low power mode. Add idle_timer_alarm_ticks variable to track alarm configuration at LPM entry. Implement comprehensive wrap detection that considers: - Counter overflow (pre > post) - Alarm pending with same pre/post values - Alarm pending when pre + alarm_ticks would exceed counter top value This ensures accurate time calculation across all counter wrap scenarios during low power mode transitions. Signed-off-by: Zhaoxiang Jin <[email protected]>
ec2f0ca to
ddf09a5
Compare
Thank you for your patient review. I have updated the comments. Please help review again. |
|



When exiting from low power mode, there are three scenarios for
idle_timer_pre_idleandidle_timer_post:idle_timer_pre_idle > idle_timer_postIn this case, a wraparound has definitely occurred, so:diff = top - pre + post + 1idle_timer_pre_idle = idle_timer_postIn this case, there are two possibilities:diff = post - prealarm_pendingbeing true):diff = top - pre + post + 1idle_timer_pre_idle < idle_timer_postIn this case, there are two possibilities:diff = post - pre(note thatalarm_pendingmaybe true caused by alarm, ifalarm_pendingis not true, then it means it was woken up early by another event. )diff = top - pre + post + 1(note thatalarm_pendingis true caused by wraparound), in this situation, there are two possibilities: one ispre + scheduled >= top, and the other ispre + scheduled < top. Currently there is no suitable method to distinguish the latter case.