8000 timer: handle timer overflow by Samir-Rashid · Pull Request #395 · tock/libtock-c · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

timer: handle timer overflow #395

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

Merged
merged 4 commits into from
Jun 21, 2024
Merged

Conversation

Samir-Rashid
Copy link
Contributor
@Samir-Rashid Samir-Rashid commented Apr 5, 2024

PR fixes tock/tock#3879
Tock does not support setting alarms more than 2^32 ticks in the future as the alarm_at syscall takes in ticks. For the default nrf52840 tock configuration at 32KHz, the greatest possible value for the syscall alarm is 37 hours.

timer_in takes in milliseconds, but is flawed in that it is just a wrapper around alarm_at which can only accept a max value of 2^32 ticks. Thus, calling timer_in longer than 2^32 ticks in milliseconds will not have the expected behavior. delay_ms calls timer_in, so it is flawed for the same reason.

We fixed this by modifying timer_in. If the timer length is longer than the time that alarm_at can count up to, then timer_in will schedule multiple alarms to reach the full length. We calculate the number of full overflows and the remainder ticks to reach the target length of time. The overflows use the overflow_callback for each intermediate overflow.

Testing

We verified this code works by creating a test program timer_overflow that simulates setting a timer longer than the threshold of what would overflow and verifies the total timer length was correct. To test overflow you need to edit #define MAX_TICKS in alarm_timer.c to overflow in a reasonable amount of time. This tests the overflow logic, which works correctly.

@Samir-Rashid Samir-Rashid force-pushed the handle-timer-overflow branch 3 times, most recently from 5693cc7 to f0b6210 Compare April 5, 2024 20:34
Copy link
Contributor
@tyler-potyondy tyler-potyondy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to testing the wrap around logic, it would likely prove useful to test the timing precision of this implementation (i.e. by calling timer_in in conjunction with gettimeasticks to see if the timer fired near when it was expected).

@ppannuto
Copy link
Member
ppannuto commented Apr 6, 2024

Following on Tyler's comment, it would be nice to capture the more complete testing program in the tests/ folder, so that we have something to validate with down the road as well.

@Samir-Rashid Samir-Rashid marked this pull request as draft April 7, 2024 18:28
@atar13 atar13 force-pushed the handle-timer-overflow branch 2 times, most recently from 71cb726 to 9ca0b9c Compare April 12, 2024 20:40
@Samir-Rashid Samir-Rashid marked this pull request as ready for review April 12, 2024 20:42
@Samir-Rashid Samir-Rashid marked this pull request as draft April 18, 2024 03:22
Copy link
Member
@ppannuto ppannuto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last documentation ask, then I think this is good to go. Implementation looks sound.

@Samir-Rashid Samir-Rashid marked this pull request as ready for review April 24, 2024 23:37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this test does. Setting a timer for 2 seconds will work just fine.

I think having to modify the libtock library is a high burden for running this test. I think it would be better to calculate how long until a overflow will happen, and set a timer for longer than that.

We then only run this test on a kernel with a very fast clock.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable.

I have 2 questions:

  1. How do we know the number of milliseconds that it will take to overflow? We know that overflows happen after $2^{32}$ ticks, however the number of milliseconds that takes depends on the frequency of the clock. We can query this value for the purpose of the test using alarm_internal_frequency function, but we were told not to use the alarm_internal_* functions in our testing code. We could hard code this value for the NRF52840dk board or just set a timer for an absurdly large number (like $2^{32}$ ms) likely overflow.
  2. How can a user customize their kernel to run with a fast clock? Does the kernel already allow users to customize the clock speed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Once the left aligned frequency changes are merged (capsules/alarm: left-justify 32 bit ticks, re-architect alarm, add unit tests tock#3975), we will be able to determine the wrap point. I don't think we should block on waiting for that PR. For now, I think it's reasonable to just set this to some "large value" i.e. 10 minutes and place a comment saying this time value is board specific.

  2. Yes, this can be configured within the main.rs initialization.

@atar13 atar13 force-pushed the handle-timer-overflow branch 2 times, most recently from 6277a89 to 568c4e2 Compare May 2, 2024 06:15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Once the left aligned frequency changes are merged (capsules/alarm: left-justify 32 bit ticks, re-architect alarm, add unit tests tock#3975), we will be able to determine the wrap point. I don't think we should block on waiting for that PR. For now, I think it's reasonable to just set this to some "large value" i.e. 10 minutes and place a comment saying this time value is board specific.

  2. Yes, this can be configured within the main.rs initialization.

// (`frequency` (ticks per second) / `1000` milliseconds per second)
// The division is done this way because of the same argument in
// `ms_to_ticks`.
milliseconds += (leftover_ticks * milliseconds_per_second) / frequency;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover_ticks is at most equal to frequency. For frequency values larger than 4MHz, I believe this calculation will overflow. I do not think there is a way around having a uint64_t to handle the intermediate calculations.

Using a uint64_t would allow us to support up to and beyond GHz frequencies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just browsing through all this, and I think I agree with the logic here. Using uint64_t would also allow us to simply the logic here a bunch, and remove the split between seconds and fractional seconds, effectively making this equivalent to: ticks_to_ms = (ticks * 1000) / freq.

I'm wondering how much the split into seconds and fractional seconds buys us here in terms of performance in code size, vs. 64-bit arithmetic on a 32-bit platform.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I just saw that the new, merged PR has been changed to 64-bit, my bad. Part of my comment still applies, I suppose: https://github.com/tock/libtock-c/pull/434/files#r1642913413

Comment on lines 45 to 46
// WARNING: This function can silently overflow if the output
// number of ticks is larger than `UINT32_MAX`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem ideal considering that libtock-c assumes:

 * The client should not assume anything about the underlying clock used by an
 * implementation other than that it is running at sufficient frequency to
 * deliver at least millisecond granularity and that it is a 32-bit clock (i.e.
 * it will wrap at 2^32 clock ticks).

Perhaps it would be better to pass a uint32_t reference that this function updates and instead returns a returncode?

@tyler-potyondy
Copy link
Contributor
9E88 tyler-potyondy commented May 23, 2024

Considering this PR has stalled for a bit, I propose that we split this into two PRs:

  1. The improvements to calculating ticks to ms and ms to ticks.
  2. The timer_in wrapping logic to set timers greater than $2^{32}$ and accompanying test.

We can keep this PR as (2) blocking on the new PR (1).

Samir-Rashid added a commit to Samir-Rashid/libtock-c that referenced this pull request May 28, 2024
This helper function has been thoroughly debugged in tock#395. Floating
point imprecision was causing timer overflows to work incorrectly.
`ms_to_ticks` now better corresponds with the behavior of the
libtock-c 32-bit tick rollover and is precise to 1ms of error.
Samir-Rashid added a commit to Samir-Rashid/libtock-c that referenced this pull request May 28, 2024
This helper function has been thoroughly debugged in tock#395. Floating
point imprecision was causing timer overflows to work incorrectly.
`ms_to_ticks` now better corresponds with the behavior of the
libtock-c 32-bit tick rollover and is precise to 1ms of error.
Samir-Rashid added a commit to Samir-Rashid/libtock-c that referenced this pull request Jun 3, 2024
This helper function has been thoroughly debugged in tock#395. Floating
point imprecision was causing timer overflows to work incorrectly.
`ms_to_ticks` now better corresponds with the behavior of the
libtock-c 32-bit tick rollover and is precise to 1ms of error.
Comment on lines 48 to 65
F438
/** \brief Opaque handle to a repeating alarm.
*
* An opaque handle to a repeating alarm created by `libtock_alarm_repeating_every`.
* An opaque handle to an alarm created by `libtock_alarm_repeating_every_ms`
* and `libtock_alarm_in_ms`.
*/
typedef struct alarm_repeating {
uint32_t interval;
libtock_alarm_callback cb;
void* ud;
typedef struct alarm_data {
// Length of timer in milliseconds.
uint32_t interval_ms;
// Number of times the underlying counter will overflow
// before hitting the target interval_ms.
uint32_t overflows_left;
// Number of ticks remaining after the last time the
// counter overflows.
uint32_t remaining_ticks;
libtock_alarm_callback callback;
void* user_data;
libtock_alarm_t alarm;
} libtock_alarm_repeating_t;
} libtock_alarm_data_t;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this modify libtock_alarm_repeating_t and not libtock_alarm_t? Couldn't a single alarm also have an overflow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libtock_alarm_at sets a single alarm and takes in a 32 bit argument (dt) for the length of the alarm in ticks. This cannot overflow as the kernel guarantees that the alarm buffer will not overflow sooner than 32 bits of ticks. Only alarms longer than $2^{32} - 1$ ticks can overflow. These alarms can only be set by libtock_alarm_in_ms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that makes sense.

What about renaming libtock_alarm_t to something like libtock_alarm_dt_t, creating a new libtock_alarm_t that supports overflows, and leaving libtock_alarm_repeating_t as it is? It would be nice to not have to change all of the examples and to keep the more descriptive "repeating" name. Changing the type for libtock_alarm_at() seems the least invasive since it isn't really used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have restored the libtock_alarm_t name to make the renaming less invasive. We cannot keep the libtock_alarm_repeating_t unchanged for a few reasons:

  • libtock_alarm_repeating_t sets repeating alarms in milliseconds, so it must support overflows. Thus we must add extra fields to support the new overflow logic.
  • libtock_alarm_repeating_every_ms calls libtock_alarm_in_ms with the alarm struct it was given. To support the less intrusive naming, both must be the same type name libtock_alarm_t. As the struct libtock_alarm_repeating_t is not relevant to the user calling these functions, I do not think it is more helpful to include the "repeating" moniker. This is why I believe there should only be two structs and why the name of the repeating struct must be changed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok makes sense I agree.

@Samir-Rashid Samir-Rashid force-pushed the handle-timer-overflow branch 2 times, most recently from cf70666 to e294786 Compare June 19, 2024 23:04
@Samir-Rashid Samir-Rashid requested a review from atar13 June 20, 2024 05:52
bradjc
bradjc previously approved these changes Jun 20, 2024
@bradjc
Copy link
Contributor
bradjc commented Jun 20, 2024

@hudson-ayers Do you think there is any reason to keep the libtock_alarm_repeating_t type around? It would essentially be an alias for libtock_alarm_t right now, but if we need to keep more state for repeating alarms in the future then it would be helpful. But I can't think of what that state might be.

@hudson-ayers
Copy link
Contributor

I think it is fine to get rid of libtock_alarm_repeating_t with this change since they would become identical, I cannot think of any other data we would really want to add to repeating alarms.

Samir-Rashid and others added 3 commits June 20, 2024 21:59
Tock does not support setting alarms more than 2^32 ticks in the future as the
`alarm_at` syscall takes in ticks. For the default nrf52840 tock configuration
at 32KHz, the greatest possible value for the syscall alarm is 37 hours.

`alarm_in` takes in milliseconds, but is flawed in that it is just a wrapper
around `alarm_at` which can only accept a max value of 2^32 ticks. Thus,
calling `alarm_in` longer than 2^32 ticks in milliseconds will not have the
expected behavior. `delay_ms` calls `alarm_in`, so it is flawed for the same
reason.

We fixed this by modifying `alarm_in`. If the timer length is longer than the
time that `alarm_at` can count up to, then `alarm_in` will schedule multiple
alarms to reach the full length. We calculate the number of full overflows
and the remainder ticks to reach the target length of time. The overflows use
the `overflow_callback` for each intermediate overflow.

Co-Authored-By: Anthony Tarbinian <atar13@users.noreply.github.com>
This tests the libtock alarm implementation's ability to handle situations
where setting a alarm would overflow the underlying hardware counter.

Co-Authored-By: Anthony Tarbinian <atar13@users.noreply.github.com>
Renamed libtock_alarm_repeating_t to libtock_alarm_t since the
previous name suggested it was only used by libtock_alarm_repeating_ms
when it was also used by libtock_alarm_in_ms. This new name is more
general and can apply to either function. This must be changed to
support the new fields for overflow.

Co-authored-by: Samir Rashid <Samir-Rashid@godsped.com>
@alevy alevy requested a review from alistair23 June 21, 2024 20:54
@ppannuto ppannuto dismissed their stale review June 21, 2024 20:59

stale

@alevy alevy enabled auto-merge June 21, 2024 21:01
@alevy alevy added this pull request to the merge queue Jun 21, 2024
Merged via the queue into tock:master with commit bd49a00 Jun 21, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RFC: Expand Alarm Range
9 participants
0