Documentation: Add Timers, Stopwatch, and Delays
Change-Id: I3b58817c1ed06e6d7d5d5668b0e38ec8cfedf122 Signed-off-by: Martin Roth <gaumless@gmail.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/87187 Reviewed-by: Matt DeVillier <matt.devillier@gmail.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
parent
4f1f502fd5
commit
5993dd6ef5
2 changed files with 911 additions and 0 deletions
|
|
@ -11,4 +11,5 @@ ABI data consumption <abi-data-consumption.md>
|
|||
Timestamps <timestamp.md>
|
||||
Firmware Configuration Interface <fw_config.md>
|
||||
Relocatable Modules <rmodules.md>
|
||||
Timers, Stopwatch, and Delays <stopwatch.md>
|
||||
```
|
||||
|
|
|
|||
910
Documentation/lib/stopwatch.md
Normal file
910
Documentation/lib/stopwatch.md
Normal file
|
|
@ -0,0 +1,910 @@
|
|||
# coreboot Timers, Stopwatch, Delays, and Associated Callbacks
|
||||
|
||||
## Introduction
|
||||
|
||||
coreboot provides several mechanisms for handling time, including
|
||||
high-precision stopwatches for profiling, simple delay functions for
|
||||
hardware timing, and a monotonic timer system that forms the foundation
|
||||
for these features. It also supports scheduling timer callbacks for
|
||||
deferred execution. These tools are crucial for performance
|
||||
optimization, debugging, ensuring proper hardware timing, and managing
|
||||
asynchronous events during the boot process.
|
||||
|
||||
This document describes the core monotonic timer, the delay functions
|
||||
(`udelay`, `mdelay`, `delay`), the stopwatch API (`struct stopwatch`),
|
||||
and the timer callback mechanism.
|
||||
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The timing facilities are layered:
|
||||
|
||||
- **Platform Timer:** Hardware-specific implementations provide a raw
|
||||
time source (e.g., LAPIC timer, Time Base register). These are often
|
||||
abstracted by `timer_monotonic_get()`.
|
||||
- **Monotonic Timer:** (`src/include/timer.h`, `src/lib/timer.c`)
|
||||
Provides a consistent time source (`struct mono_time`) counting
|
||||
microseconds since timer initialization. Requires
|
||||
`CONFIG(HAVE_MONOTONIC_TIMER)`.
|
||||
- **Delay Functions:** (`src/include/delay.h`, `src/lib/delay.c`,
|
||||
`src/lib/timer.c`) Simple blocking delays (`udelay`, `mdelay`,
|
||||
`delay`). The generic `udelay` uses the stopwatch API if yielding via
|
||||
`thread_yield_microseconds()` is not performed.
|
||||
- **Stopwatch API:** (`src/include/timer.h`) A lightweight wrapper
|
||||
around the monotonic timer for measuring durations and handling
|
||||
timeouts (`struct stopwatch`,`wait_us`,`wait_ms`).
|
||||
- **Timer Callbacks:** (`src/include/timer.h`) Allows scheduling
|
||||
functions to be called after a specified delay
|
||||
(`struct timeout_callback`, `timer_sched_callback()`, `timers_run()`).
|
||||
|
||||
These APIs are generally designed to be thread-safe and usable across
|
||||
different boot stages.
|
||||
|
||||
|
||||
## Core Monotonic Timer
|
||||
|
||||
The foundation is the monotonic timer, enabled by
|
||||
`CONFIG(HAVE_MONOTONIC_TIMER)`. It provides a time source that
|
||||
continuously increases from an arbitrary starting point (usually near
|
||||
timer initialization).
|
||||
|
||||
### Data Structures
|
||||
|
||||
#### struct mono_time
|
||||
|
||||
```c
|
||||
struct mono_time {
|
||||
uint64_t microseconds; // Time in microseconds since timer init
|
||||
};
|
||||
```
|
||||
Represents a point in monotonic time. Direct field access outside core
|
||||
timer code is discouraged; use helper functions.
|
||||
|
||||
### API Functions
|
||||
|
||||
#### timer_monotonic_get
|
||||
|
||||
```c
|
||||
void timer_monotonic_get(struct mono_time *mt);
|
||||
```
|
||||
Retrieves the current monotonic time.
|
||||
|
||||
**Parameters:**
|
||||
- `mt`: Pointer to a `struct mono_time` to store the current time.
|
||||
|
||||
**Preconditions:**
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` must be enabled.
|
||||
- The underlying platform timer must be functional. Platform
|
||||
implementations may require an `init_timer()` call.
|
||||
|
||||
**Postconditions:**
|
||||
- `mt` contains the current monotonic time in microseconds.
|
||||
|
||||
**Note:** If `CONFIG(HAVE_MONOTONIC_TIMER)` is *not* enabled, functions
|
||||
relying on it (like stopwatch functions) will generally fall back to
|
||||
basic behavior (e.g., returning 0 durations, immediate expiration
|
||||
checks).
|
||||
|
||||
#### init_timer
|
||||
|
||||
```c
|
||||
void init_timer(void);
|
||||
```
|
||||
|
||||
Platform-specific timer initialization. The generic version in
|
||||
`src/lib/timer.c` is a weak empty function
|
||||
`__weak void init_timer(void) { /* do nothing */ }`. Platforms needing
|
||||
explicit timer setup (e.g., configuring frequency, enabling the
|
||||
counter) must provide their own strong implementation. Check platform
|
||||
code (`src/cpu/`, `src/soc/`) for details.
|
||||
|
||||
#### `mono_time` Helper Functions
|
||||
|
||||
`src/include/timer.h` also provides several inline helper functions for
|
||||
manipulating `struct mono_time` values:
|
||||
- `mono_time_set_usecs()`, `mono_time_set_msecs()`: Set time.
|
||||
- `mono_time_add_usecs()`, `mono_time_add_msecs()`: Add duration.
|
||||
- `mono_time_cmp()`: Compare two times.
|
||||
- `mono_time_after()`, `mono_time_before()`: Check time ordering.
|
||||
- `mono_time_diff_microseconds()`: Calculate difference between two
|
||||
times.
|
||||
|
||||
Refer to `src/include/timer.h` for their exact definitions.
|
||||
|
||||
|
||||
## Delay Functions
|
||||
|
||||
coreboot provides simple functions for introducing delays.
|
||||
|
||||
### udelay
|
||||
|
||||
```c
|
||||
void udelay(unsigned int usecs);
|
||||
```
|
||||
Delays execution for *at least* the specified number of microseconds.
|
||||
|
||||
**Parameters:**
|
||||
- `usecs`: Number of microseconds to delay.
|
||||
|
||||
**Implementation Notes:**
|
||||
- The generic implementation in `src/lib/timer.c` first attempts to
|
||||
yield using `thread_yield_microseconds()` if `CONFIG(HAS_THREADS)` is
|
||||
enabled. If yielding handles the required delay (function returns 0),
|
||||
`udelay` returns immediately, allowing other threads to run.
|
||||
- If `thread_yield_microseconds()` does not handle the delay (returns
|
||||
non-zero), or if threading is disabled, the generic `udelay` falls
|
||||
back to a busy-wait using the stopwatch API
|
||||
(`stopwatch_init_usecs_expire()` and
|
||||
`stopwatch_wait_until_expired()`).
|
||||
- Architecture-specific implementations (e.g., in `src/arch/`) may
|
||||
override the generic one for better precision or efficiency.
|
||||
- Adds 1 microsecond internally to ensure the delay is *at least* the
|
||||
requested duration.
|
||||
|
||||
**Preconditions:**
|
||||
- If relying on the stopwatch fallback,
|
||||
`CONFIG(HAVE_MONOTONIC_TIMER)` is needed.
|
||||
- Underlying timer (if used) must be initialized (potentially via a
|
||||
platform `init_timer()`).
|
||||
- The delay value should be reasonable (overly large values might cause
|
||||
issues or unexpected behavior depending on the implementation).
|
||||
|
||||
**Example:**
|
||||
```c
|
||||
#include <delay.h>
|
||||
|
||||
// Wait for 100 microseconds
|
||||
udelay(100);
|
||||
```
|
||||
|
||||
### mdelay
|
||||
|
||||
```c
|
||||
void mdelay(unsigned int msecs);
|
||||
```
|
||||
Delays execution for *at least* the specified number of milliseconds.
|
||||
|
||||
**Parameters:**
|
||||
- `msecs`: Number of milliseconds to delay.
|
||||
|
||||
**Implementation Notes:**
|
||||
- The generic implementation in `src/lib/delay.c` simply calls
|
||||
`udelay(1000)` in a loop `msecs` times.
|
||||
- Therefore, it inherits the behavior of `udelay`, including the attempt
|
||||
to yield first if `udelay` supports it.
|
||||
|
||||
**Preconditions:**
|
||||
- Same as `udelay`.
|
||||
|
||||
**Example:**
|
||||
```c
|
||||
#include <delay.h>
|
||||
|
||||
// Wait for 50 milliseconds
|
||||
mdelay(50);
|
||||
```
|
||||
|
||||
### delay
|
||||
|
||||
```c
|
||||
void delay(unsigned int secs);
|
||||
```
|
||||
Delays execution for *at least* the specified number of seconds.
|
||||
|
||||
**Parameters:**
|
||||
- `secs`: Number of seconds to delay.
|
||||
|
||||
**Implementation Notes:**
|
||||
- The generic implementation in `src/lib/delay.c` simply calls
|
||||
`mdelay(1000)` in a loop `secs` times.
|
||||
- Inherits the behavior of `mdelay` and `udelay`.
|
||||
|
||||
**Preconditions:**
|
||||
- Same as `udelay`.
|
||||
|
||||
**Example:**
|
||||
```c
|
||||
#include <delay.h>
|
||||
|
||||
// Wait for 2 seconds
|
||||
delay(2);
|
||||
```
|
||||
|
||||
|
||||
## Stopwatch API
|
||||
|
||||
The stopwatch API provides a convenient way to measure time durations
|
||||
and implement timeouts based on the monotonic timer.
|
||||
|
||||
### Data Structures
|
||||
|
||||
#### struct stopwatch
|
||||
|
||||
```c
|
||||
#include <timer.h> // For struct stopwatch and struct mono_time
|
||||
|
||||
struct stopwatch {
|
||||
struct mono_time start; // Time when stopwatch was started or initialized
|
||||
struct mono_time current; // Time when stopwatch was last ticked
|
||||
struct mono_time expires; // Expiration time for timeout operations
|
||||
};
|
||||
```
|
||||
Holds the state for a stopwatch instance.
|
||||
|
||||
### API Functions
|
||||
|
||||
#### Initialization Functions
|
||||
|
||||
##### stopwatch_init
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline void stopwatch_init(struct stopwatch *sw);
|
||||
```
|
||||
Initializes a stopwatch structure. `start`, `current`, and `expires` are
|
||||
all set to the current monotonic time. Use this when you only need to
|
||||
measure elapsed duration from initialization.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure to initialize.
|
||||
|
||||
**Preconditions:**
|
||||
- `sw` must point to valid memory.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled for meaningful
|
||||
timing.
|
||||
|
||||
**Postconditions:**
|
||||
- The stopwatch is initialized.
|
||||
|
||||
|
||||
##### stopwatch_init_usecs_expire
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline void stopwatch_init_usecs_expire(struct stopwatch *sw, uint64_t us);
|
||||
```
|
||||
Initializes a stopwatch and sets an expiration time `us` microseconds
|
||||
from now.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
- `us`: Timeout duration in microseconds.
|
||||
|
||||
**Preconditions:**
|
||||
- `sw` must point to valid memory.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
**Postconditions:**
|
||||
- The stopwatch is initialized, and `expires` is set `us` microseconds
|
||||
after `start`.
|
||||
|
||||
|
||||
##### stopwatch_init_msecs_expire
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline void stopwatch_init_msecs_expire(struct stopwatch *sw, uint64_t ms);
|
||||
```
|
||||
Initializes a stopwatch and sets an expiration time `ms` milliseconds
|
||||
from now.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
- `ms`: Timeout duration in milliseconds.
|
||||
|
||||
**Preconditions:**
|
||||
- `sw` must point to valid memory.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
**Postconditions:**
|
||||
- The stopwatch is initialized, and `expires` is set `ms` milliseconds
|
||||
after `start`.
|
||||
|
||||
|
||||
#### Time Measurement Functions
|
||||
|
||||
##### stopwatch_tick
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline void stopwatch_tick(struct stopwatch *sw);
|
||||
```
|
||||
Updates the `current` time field in the stopwatch structure to the
|
||||
current monotonic time. This is often called implicitly by other
|
||||
stopwatch functions like `stopwatch_expired()` and
|
||||
`stopwatch_duration_usecs()`.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
|
||||
**Preconditions:**
|
||||
- The stopwatch must be initialized.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
|
||||
##### stopwatch_expired
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline int stopwatch_expired(struct stopwatch *sw);
|
||||
```
|
||||
Checks if the stopwatch's expiration time (`expires`) has passed. It
|
||||
implicitly calls `stopwatch_tick()` to get the current time for
|
||||
comparison.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
|
||||
**Returns:**
|
||||
- Non-zero (true) if `current` time >= `expires` time.
|
||||
- Zero (false) otherwise.
|
||||
|
||||
**Preconditions:**
|
||||
- The stopwatch must be initialized (preferably with an expiration
|
||||
time).
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
|
||||
##### stopwatch_wait_until_expired
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline void stopwatch_wait_until_expired(struct stopwatch *sw);
|
||||
```
|
||||
Blocks (busy-waits) until the stopwatch expires by repeatedly calling
|
||||
`stopwatch_expired()`.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
|
||||
**Preconditions:**
|
||||
- The stopwatch must be initialized with an expiration time.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
**Postconditions:**
|
||||
- The function returns only after the stopwatch has expired.
|
||||
|
||||
|
||||
#### Duration Measurement Functions
|
||||
|
||||
##### stopwatch_duration_usecs
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline int64_t stopwatch_duration_usecs(struct stopwatch *sw);
|
||||
```
|
||||
Returns the elapsed time in microseconds between `start` and `current`.
|
||||
If `current` hasn't been updated since `stopwatch_init`, it implicitly
|
||||
calls `stopwatch_tick()` first.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
|
||||
**Returns:**
|
||||
- Elapsed time in microseconds (`current` - `start`).
|
||||
|
||||
**Preconditions:**
|
||||
- The stopwatch must be initialized.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled for a meaningful
|
||||
duration.
|
||||
|
||||
|
||||
##### stopwatch_duration_msecs
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
static inline int64_t stopwatch_duration_msecs(struct stopwatch *sw);
|
||||
```
|
||||
Returns the elapsed time in milliseconds since the stopwatch was
|
||||
started. It calls `stopwatch_duration_usecs()` and divides by 1000.
|
||||
|
||||
**Parameters:**
|
||||
- `sw`: Pointer to the stopwatch structure.
|
||||
|
||||
**Returns:**
|
||||
- Elapsed time in milliseconds.
|
||||
|
||||
**Preconditions:**
|
||||
- The stopwatch must be initialized.
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
|
||||
### Utility Macros
|
||||
|
||||
These macros combine stopwatch initialization and expiration checking with
|
||||
a user-provided condition.
|
||||
|
||||
#### wait_us
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
#define wait_us(timeout_us, condition) ...
|
||||
```
|
||||
Waits until a condition becomes true or a timeout elapses, whichever
|
||||
comes first. Internally uses a `struct stopwatch`.
|
||||
|
||||
**Parameters:**
|
||||
- `timeout_us`: Timeout duration in microseconds.
|
||||
- `condition`: A C expression that evaluates to true or false. The loop
|
||||
continues as long as the condition is false and the timeout has not
|
||||
expired.
|
||||
|
||||
**Returns:**
|
||||
- 0: If the condition was still false when the timeout expired.
|
||||
- >0: If the condition became true before the timeout. The return value
|
||||
is the approximate number of microseconds waited (at least 1).
|
||||
|
||||
**Preconditions:**
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
|
||||
#### wait_ms
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
#define wait_ms(timeout_ms, condition) ...
|
||||
```
|
||||
Similar to `wait_us`, but the timeout is specified in milliseconds.
|
||||
|
||||
**Parameters:**
|
||||
- `timeout_ms`: Timeout duration in milliseconds.
|
||||
- `condition`: C expression to wait for.
|
||||
|
||||
**Returns:**
|
||||
- 0: If the condition was still false after the timeout.
|
||||
- >0: If the condition became true before the timeout. The return value
|
||||
is the approximate number of milliseconds waited (rounded up, at
|
||||
least 1).
|
||||
|
||||
**Preconditions:**
|
||||
- `CONFIG(HAVE_MONOTONIC_TIMER)` should be enabled.
|
||||
|
||||
|
||||
## Key differences between `mdelay` and `wait_ms`
|
||||
|
||||
While both can be used to wait, they serve different purposes:
|
||||
|
||||
### Purpose and Use Case
|
||||
|
||||
- `mdelay`: Provides an *unconditional* delay for a fixed duration. The
|
||||
generic implementation attempts to yield to other threads first (if
|
||||
`CONFIG(HAS_THREADS)` is enabled via `udelay`), but it always waits
|
||||
for (at least) the specified time, potentially using a busy-wait if
|
||||
yielding doesn't occur. Primarily used for hardware timing
|
||||
requirements or pacing operations.
|
||||
|
||||
- `wait_ms`: Provides a *conditional* wait. It waits for a specific C
|
||||
expression (`condition`) to become true, but *only up to* a maximum
|
||||
timeout duration. Used when polling for a status change or event, with
|
||||
a safeguard against waiting indefinitely.
|
||||
|
||||
### When to Use Which
|
||||
|
||||
#### Use `mdelay` when:
|
||||
|
||||
- You need a guaranteed minimum delay (e.g., waiting for hardware to
|
||||
settle after a register write).
|
||||
- You are implementing a hardware initialization sequence with specific
|
||||
timing requirements between steps.
|
||||
- You want a simple pause, potentially allowing other threads to run if
|
||||
the underlying `udelay` yields.
|
||||
|
||||
**Example:**
|
||||
```c
|
||||
#include <delay.h>
|
||||
#include <arch/io.h> // For write_reg hypothetical function
|
||||
|
||||
// Initialize hardware with specific timing requirements
|
||||
write_reg(REG_A, value);
|
||||
mdelay(10); // Must wait (at least) 10ms after writing REG_A
|
||||
write_reg(REG_B, another_value);
|
||||
```
|
||||
|
||||
#### Use `wait_ms` when:
|
||||
|
||||
- You are waiting for a hardware status bit to change or a condition to
|
||||
become true.
|
||||
- You need to implement a timeout for an operation that might fail or
|
||||
take too long.
|
||||
- You want to know approximately how long you waited for the condition
|
||||
to become true (via the return value).
|
||||
|
||||
**Example:**
|
||||
```c
|
||||
#include <timer.h>
|
||||
#include <console/console.h>
|
||||
#include <arch/io.h> // For read_reg hypothetical function
|
||||
|
||||
// Wait for hardware to become ready, but not forever
|
||||
#define STATUS_REG 0x100
|
||||
#define READY_BIT (1 << 0)
|
||||
int64_t waited_ms = wait_ms(100, (read_reg(STATUS_REG) & READY_BIT));
|
||||
|
||||
if (waited_ms > 0) {
|
||||
printk(BIOS_INFO, "Hardware ready after ~%lld ms\n", waited_ms);
|
||||
} else {
|
||||
printk(BIOS_ERR, "Timeout: Hardware failed to become ready within 100 ms\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- `mdelay`: Generally simpler if the underlying `udelay` performs a
|
||||
busy-wait. If it yields (`thread_yield_microseconds`), overhead depends
|
||||
on the scheduler. Always waits for the full duration (approximately).
|
||||
- `wait_ms`: Involves repeated condition checking and stopwatch
|
||||
management (`stopwatch_expired`, which calls `timer_monotonic_get`),
|
||||
adding overhead within the loop. However, it can return early if the
|
||||
condition becomes true, potentially saving time compared to a fixed
|
||||
`mdelay`.
|
||||
|
||||
### Error Handling / Feedback
|
||||
|
||||
- `mdelay`: Provides no feedback. It simply waits.
|
||||
- `wait_ms`: Returns whether the condition was met within the timeout
|
||||
and provides the approximate wait time if successful. This allows for
|
||||
explicit timeout handling.
|
||||
|
||||
### Summary
|
||||
|
||||
Use `mdelay` for fixed, unconditional delays, potentially allowing
|
||||
thread yielding. Use `wait_ms` for conditional waits with a timeout and
|
||||
feedback on success or failure. Choose based on whether you need to poll
|
||||
for a condition or simply need to pause execution.
|
||||
|
||||
|
||||
## Timer Callbacks
|
||||
|
||||
coreboot provides a mechanism to schedule functions (callbacks) to be
|
||||
executed after a certain time has elapsed. This requires
|
||||
`CONFIG(TIMER_QUEUE)`.
|
||||
|
||||
### Data Structures
|
||||
|
||||
#### struct timeout_callback
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
struct timeout_callback {
|
||||
void *priv; // Private data for the callback
|
||||
void (*callback)(struct timeout_callback *tocb); // Function to call
|
||||
/* Internal use by timer library: */
|
||||
struct mono_time expiration; // Calculated expiration time
|
||||
};
|
||||
```
|
||||
|
||||
Represents a scheduled callback. The user initializes `priv` and
|
||||
`callback`.
|
||||
|
||||
### API Functions
|
||||
|
||||
#### timer_sched_callback
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
int timer_sched_callback(struct timeout_callback *tocb, uint64_t us);
|
||||
```
|
||||
Schedules a callback function to run after a specified delay.
|
||||
|
||||
**Parameters:**
|
||||
- `tocb`: Pointer to a `struct timeout_callback`. The `priv` and
|
||||
`callback` fields must be set by the caller. The structure must
|
||||
persist until the callback runs or is canceled (cancellation not
|
||||
directly supported by API).
|
||||
- `us`: Delay in microseconds from the time of this call until the
|
||||
callback should run.
|
||||
|
||||
**Returns:**
|
||||
- 0: Success.
|
||||
- <0: Error (e.g., timer queue full, invalid arguments).
|
||||
|
||||
**Preconditions:**
|
||||
- `CONFIG(TIMER_QUEUE)` and `CONFIG(HAVE_MONOTONIC_TIMER)` must be
|
||||
enabled.
|
||||
- `tocb` must point to a valid structure with `callback` assigned.
|
||||
|
||||
**Postconditions:**
|
||||
- The callback is added to a queue, to be executed when `timers_run()`
|
||||
is called after the expiration time.
|
||||
|
||||
|
||||
#### timers_run
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
int timers_run(void);
|
||||
```
|
||||
|
||||
Checks the timer callback queue and executes any callbacks whose
|
||||
expiration time has passed. This function needs to be called
|
||||
periodically from the main execution flow (e.g., in a boot state loop
|
||||
or idle task) for callbacks to be processed.
|
||||
|
||||
**Returns:**
|
||||
- 1: If callbacks were run or are still pending in the queue.
|
||||
- 0: If the queue is empty and no callbacks were run.
|
||||
|
||||
**Preconditions:**
|
||||
- `CONFIG(TIMER_QUEUE)` and `CONFIG(HAVE_MONOTONIC_TIMER)` must be
|
||||
enabled.
|
||||
|
||||
**Usage:**
|
||||
Typically called in loops where deferred work might need processing:
|
||||
```c
|
||||
#include <timer.h>
|
||||
|
||||
// Example main loop structure
|
||||
while (some_condition) {
|
||||
// ... do other work ...
|
||||
|
||||
// Process any expired timer callbacks
|
||||
timers_run();
|
||||
|
||||
// ... potentially yield or sleep ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Timing Measurement Example
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
#include <console/console.h>
|
||||
|
||||
struct stopwatch sw;
|
||||
stopwatch_init(&sw);
|
||||
|
||||
// ---> Code section to measure start
|
||||
// ... perform some operations ...
|
||||
// ---> Code section to measure end
|
||||
|
||||
// stopwatch_duration_usecs implicitly ticks the stopwatch if needed
|
||||
int64_t duration_us = stopwatch_duration_usecs(&sw);
|
||||
printk(BIOS_INFO, "Operation took %lld microseconds\n", duration_us);
|
||||
|
||||
// Or in milliseconds
|
||||
int64_t duration_ms = stopwatch_duration_msecs(&sw);
|
||||
printk(BIOS_INFO, "Operation took %lld milliseconds\n", duration_ms);
|
||||
```
|
||||
|
||||
|
||||
### Timeout Operation (Polling) Example
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
#include <console/console.h>
|
||||
#include <stdint.h> // For uint8_t example
|
||||
|
||||
// Hypothetical hardware status check
|
||||
extern uint8_t check_hardware_status(void);
|
||||
#define HW_READY (1 << 0)
|
||||
|
||||
struct stopwatch sw;
|
||||
// Initialize stopwatch with a 100 millisecond timeout
|
||||
stopwatch_init_msecs_expire(&sw, 100);
|
||||
|
||||
uint8_t status = 0;
|
||||
while (!(status & HW_READY)) {
|
||||
status = check_hardware_status();
|
||||
|
||||
if (stopwatch_expired(&sw)) {
|
||||
printk(BIOS_ERR, "Operation timed out waiting for HW_READY\n");
|
||||
// Handle timeout error...
|
||||
status = 0; // Indicate failure perhaps
|
||||
break;
|
||||
}
|
||||
// Optional: Add a small delay here to avoid busy-spinning the CPU excessively
|
||||
// udelay(10); // e.g., wait 10us between checks
|
||||
}
|
||||
|
||||
if (status & HW_READY) {
|
||||
printk(BIOS_DEBUG, "Hardware became ready.\n");
|
||||
// Continue with operation...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Waiting for a Condition (Using `wait_ms`) Example
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
#include <console/console.h>
|
||||
#include <stdint.h> // For uint8_t example
|
||||
|
||||
// Hypothetical hardware status check
|
||||
extern uint8_t check_hardware_status(void);
|
||||
#define HW_READY (1 << 0)
|
||||
|
||||
// Wait up to 100ms for the HW_READY bit
|
||||
int64_t waited_ms = wait_ms(100, (check_hardware_status() & HW_READY));
|
||||
|
||||
if (waited_ms > 0) {
|
||||
printk(BIOS_INFO, "Condition met: HW_READY asserted after ~%lld ms\n", waited_ms);
|
||||
// Continue...
|
||||
} else {
|
||||
printk(BIOS_ERR, "Timeout: HW_READY not asserted within 100 ms\n");
|
||||
// Handle timeout error...
|
||||
}
|
||||
```
|
||||
|
||||
### Scheduling a Timer Callback Example
|
||||
|
||||
```c
|
||||
#include <timer.h>
|
||||
#include <console/console.h>
|
||||
|
||||
// Data structure for our callback context
|
||||
struct my_callback_data {
|
||||
int counter;
|
||||
const char *message;
|
||||
};
|
||||
|
||||
// The callback function
|
||||
static void my_callback_handler(struct timeout_callback *tocb)
|
||||
{
|
||||
struct my_callback_data *data = tocb->priv;
|
||||
printk(BIOS_INFO, "Callback executed! Message: %s, Counter: %d\n",
|
||||
data->message, data->counter);
|
||||
// Note: 'tocb' can be reused here to reschedule if needed,
|
||||
// or the memory it points to can be freed if dynamically allocated.
|
||||
}
|
||||
|
||||
// Somewhere in initialization code:
|
||||
static struct timeout_callback my_timer;
|
||||
static struct my_callback_data my_data;
|
||||
|
||||
void schedule_my_task(void)
|
||||
{
|
||||
my_data.counter = 42;
|
||||
my_data.message = "Hello from timer";
|
||||
|
||||
my_timer.priv = &my_data;
|
||||
my_timer.callback = my_callback_handler;
|
||||
|
||||
// Schedule the callback to run after 500 milliseconds (500,000 us)
|
||||
int rc = timer_sched_callback(&my_timer, 500 * 1000);
|
||||
if (rc == 0) {
|
||||
printk(BIOS_DEBUG, "Scheduled my_callback_handler successfully.\n");
|
||||
} else {
|
||||
printk(BIOS_ERR, "Failed to schedule callback!\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Remember that timers_run() must be called periodically elsewhere
|
||||
// for the callback to actually execute after the delay.
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Enable Monotonic Timer:** For accurate timing and stopwatch
|
||||
functionality, ensure `CONFIG(HAVE_MONOTONIC_TIMER)` is enabled and a
|
||||
suitable platform timer is configured.
|
||||
2. **Minimize Stopwatch Overhead:** While lightweight, frequent calls,
|
||||
especially `timer_monotonic_get()` implicitly called by stopwatch
|
||||
functions, can add up. Measure larger, significant code blocks rather
|
||||
than tiny ones in tight loops unless absolutely necessary.
|
||||
3. **Use Appropriate Time Units:** Choose `usecs` or `msecs` variants
|
||||
based on the scale of time you are dealing with.
|
||||
4. **Handle Timeouts Gracefully:** When using expiration or
|
||||
`wait_ms`/`wait_us`, always check the result and handle the timeout
|
||||
case appropriately. Don't assume success.
|
||||
5. **Process Timer Callbacks:** If using `timer_sched_callback`, ensure
|
||||
`timers_run()` is called regularly in your main processing loops or
|
||||
idle states.
|
||||
6. **Avoid `mdelay` for Polling:** Prefer `wait_ms` or manual polling
|
||||
with `stopwatch_expired` when waiting for conditions, as `mdelay`
|
||||
offers no timeout handling or feedback.
|
||||
7. **Consider Platform Accuracy:** Timer resolution and accuracy vary
|
||||
between platforms. Don't assume microsecond precision unless verified
|
||||
for the target hardware.
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Monotonic Timer Dependence:** Stopwatch and timer callbacks rely
|
||||
heavily on `CONFIG(HAVE_MONOTONIC_TIMER)`. Without it, their
|
||||
behavior is limited.
|
||||
2. **Timer Resolution:** Accuracy is limited by the underlying platform
|
||||
timer's resolution and the frequency of `timer_monotonic_get()`
|
||||
updates internally.
|
||||
3. **64-bit Microsecond Counter:** While large, the
|
||||
`uint64_t microseconds` counter will eventually wrap around
|
||||
(after ~584,000 years), though this is not a practical concern for
|
||||
boot times. More relevant is the potential rollover of the
|
||||
*underlying hardware counter* between `timer_monotonic_get` calls,
|
||||
which the implementation must handle correctly (typically ok for
|
||||
intervals up to several seconds).
|
||||
4. **Busy Waiting:** `stopwatch_wait_until_expired` and the internal
|
||||
loops of `wait_us`/`wait_ms` (and potentially the fallback in
|
||||
`udelay`/`mdelay`) perform busy-waits, consuming CPU cycles. Consider
|
||||
alternatives like interrupt-driven timers or yielding
|
||||
(`thread_yield_microseconds`) if power consumption or concurrency is
|
||||
critical.
|
||||
5. **Callback Queue Size:** The timer callback queue has a fixed size
|
||||
defined by `CONFIG(TIMER_QUEUE_SIZE)`, limiting the number of pending
|
||||
callbacks.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Common issues and solutions:
|
||||
|
||||
1. **Inaccurate Timing / Durations are Zero:**
|
||||
- Verify `CONFIG(HAVE_MONOTONIC_TIMER)` is enabled.
|
||||
- Check if a platform-specific `init_timer()` is required and being
|
||||
called.
|
||||
- Ensure the platform timer frequency is correctly configured.
|
||||
- Check for potential timer hardware issues or conflicts.
|
||||
|
||||
2. **Timeouts Not Working / `wait_ms` Never Returns > 0:**
|
||||
- Verify the timeout value is appropriate (not too short).
|
||||
- Double-check the `condition` logic in `wait_ms`/`wait_us`. Is it
|
||||
actually capable of becoming true?
|
||||
- Ensure the stopwatch (`sw` or the internal one in `wait_ms`) is
|
||||
properly initialized before the check loop.
|
||||
- Confirm `CONFIG(HAVE_MONOTONIC_TIMER)` is enabled.
|
||||
|
||||
3. **Timer Callbacks Not Running:**
|
||||
- Verify `CONFIG(TIMER_QUEUE)` is enabled.
|
||||
- Ensure `timer_sched_callback()` returned success (0).
|
||||
- **Crucially:** Check that `timers_run()` is being called
|
||||
periodically in a suitable loop after the callback was scheduled.
|
||||
- Make sure the `struct timeout_callback` structure persists in memory
|
||||
until the callback runs.
|
||||
|
||||
4. **Performance Impact:**
|
||||
- Reduce frequency of stopwatch checks or `wait_ms`/`wait_us` calls if
|
||||
possible.
|
||||
- Measure larger code blocks.
|
||||
- Investigate if the underlying `udelay` implementation is
|
||||
busy-waiting excessively; yielding (`thread_yield_microseconds`)
|
||||
might be preferable if available and appropriate.
|
||||
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
The core APIs are generic, but the underlying implementation relies on
|
||||
platform code:
|
||||
- **Timer Source:** Platforms implement `timer_monotonic_get` (often
|
||||
weakly linked) or provide the necessary hooks for it, using hardware
|
||||
like the APIC timer (x86), Time Base (PPC), architectural timers
|
||||
(ARM), etc.
|
||||
- **`init_timer()`:** Platforms may need a custom `init_timer` to set up
|
||||
clocks, dividers, or enable the timer hardware.
|
||||
- **`udelay`:** Platforms might provide optimized `udelay`
|
||||
implementations.
|
||||
|
||||
Refer to the documentation and code within specific `src/soc/`,
|
||||
`src/cpu/`, or `src/arch/` directories for platform details.
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- `src/include/timer.h`: Monotonic timer, stopwatch, and callback
|
||||
declarations.
|
||||
- `src/include/delay.h`: `udelay`, `mdelay`, `delay` declarations.
|
||||
- `src/lib/timer.c`: Generic `udelay` (using stopwatch/yield), weak
|
||||
`init_timer`.
|
||||
- `src/lib/delay.c`: Generic `mdelay` and `delay` implementations
|
||||
(calling `udelay`).
|
||||
- `src/lib/hardwaremain.c`: Example usage of `timers_run()` in boot
|
||||
state machine.
|
||||
- Platform timer implementations: Search for `timer_monotonic_get` or
|
||||
`init_timer` in `src/cpu/`, `src/soc/`, `src/arch/` directories
|
||||
(e.g., `src/cpu/x86/lapic/apic_timer.c`,
|
||||
`src/arch/arm64/timer.c`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue