Documentation/lib: Update Timestamp documentation

- Rephrase some items
- Add additional code examples
- Add additional sections
- Update documentation to the current state of the codebase.

Change-Id: I6e4cc244edf6cc860cc66165173f134a30a81589
Signed-off-by: Martin Roth <gaumless@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/87186
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
This commit is contained in:
Martin Roth 2025-04-05 14:57:28 -06:00 committed by Matt DeVillier
commit 812d0e2f62

View file

@ -4,179 +4,406 @@
The aim of the timestamp library is to make it easier for different
boards to save timestamps in cbmem / stash (until cbmem is brought up)
by providing a simple API to initialize, add and sync timestamps. In
by providing a simple API to initialize, add, and sync timestamps. In
order to make the timestamps persistent and accessible from the kernel,
we need to ensure that all the saved timestamps end up in cbmem under
the CBMEM_ID_TIMESTAMP tag. However, until the cbmem area is available,
the timestamps can be saved to a SoC-defined \_timestamp region or in a
local stage-specific stash. The work of identifying the right location
for storing timestamps is done by the library and is not exposed to the
user.
the timestamps can be saved to a SoC-defined `_timestamp` region if one
is defined in the board's `memlayout.ld`. The work of identifying the
right location for storing timestamps is done by the library and is not
exposed to the user.
Timestamps in coreboot are a critical feature for performance analysis,
debugging, and optimization of the boot process. They provide precise
timing information about various stages and operations during system
initialization, allowing developers to identify bottlenecks and optimize
boot performance. The timestamp system is designed to be lightweight,
accurate, and persistent across different boot stages.
Working of timestamp library from a user perspective can be outlined in
the following steps:
1. Initialize the base time and reset cbmem timestamp area
2. Start adding timestamps
1. Initialize the base time and reset cbmem timestamp area using
`timestamp_init()`.
2. Start adding timestamps using `timestamp_add()` or
`timestamp_add_now()`.
Behind the scenes, the timestamp library takes care of:
1. Identifying the correct location for storing timestamps (cbmem or
timestamp region or local stash).
2. Once cbmem is up, ensure that all timestamps are synced from
timestamp region or local stash into the cbmem area.
3. Add a new cbmem timestamp area based on whether a reset of the cbmem
1. Identifying the correct location for storing timestamps (`_timestamp`
region before cbmem is ready, then cbmem region).
2. Add a new cbmem timestamp area based on whether a reset of the cbmem
timestamp region is required or not.
3. Once cbmem is ready, ensuring that all timestamps are synced from the
`_timestamp` region into the cbmem area.
### Transition from cache to cbmem
To move timestamps from the cache to cbmem (and initialize the cbmem
area in the first place), we use the CBMEM_INIT_HOOK infrastructure of
coreboot.
When cbmem is initialized, the hook is called, which creates the area,
copies all timestamps to cbmem and disables the cache.
After such a transition, timestamp_init() must not be run again.
Note that if `CONFIG_COLLECT_TIMESTAMPS` is disabled, all timestamp
functions are implemented as no-ops, and no timestamps will be
collected.
## Data structures used
## Background
The main structure that maintains information about the timestamp cache
is:
The timestamp implementation in coreboot has evolved over time to meet
the needs of the firmware development and performance analysis.
Initially designed as a simple timing mechanism, it has grown into a
sophisticated system that:
```c
struct __packed timestamp_cache {
uint16_t cache_state;
struct timestamp_table table;
struct timestamp_entry entries[MAX_TIMESTAMP_CACHE];
};
```
### cache_state
The state of the cache is maintained by `cache_state` attribute which
can be any one of the following:
```c
enum {
TIMESTAMP_CACHE_UNINITIALIZED = 0,
TIMESTAMP_CACHE_INITIALIZED,
TIMESTAMP_CACHE_NOT_NEEDED,
};
```
By default, if the cache is stored in local stash (bss area), then it
will be reset to uninitialized state. However, if the cache is stored in
timestamp region, then it might have garbage in any of the attributes.
Thus, if the timestamp region is being used by any board, it is
initialized to default values by the library.
Once the cache is initialized, its state is set to
`CACHE_INITIALIZED`. Henceforth, the calls to cache i.e. `timestamp_add`
know that the state reflected is valid and timestamps can be directly
saved in the cache.
Once the cbmem area is up (i.e. call to
`timestamp_sync_cache_to_cbmem`), we do not need to store the timestamps
in local stash / timestamp area anymore. Thus, the cache state is set to
`CACHE_NOT_NEEDED`, which allows `timestamp_add` to store all timestamps
directly into the cbmem area.
- Tracks boot stages and critical operations
- Supports multiple hardware platforms (x86, ARM, RISC-V)
- Provides persistent storage across boot stages
- Enables post-boot analysis of boot performance
- Integrates with vendor-specific firmware components
### table
## Timestamp Architecture
This field is represented by a structure which provides overall
information about the entries in the timestamp area:
### Transition from cache (`_timestamp` region) to cbmem
```c
struct timestamp_table {
uint64_t base_time;
uint32_t max_entries;
uint32_t num_entries;
struct timestamp_entry entries[0]; /* Variable number of entries */
} __packed;
```
To move timestamps from the early `_timestamp` region to cbmem (and
initialize the cbmem area in the first place), we use the
`CBMEM_READY_HOOK` infrastructure of coreboot.
It indicates the base time for all timestamp entries, maximum number of
entries that can be stored, total number of entries that currently exist
and an entry structure to hold variable number of entries.
When cbmem is initialized (`cbmem_initialize` or `cbmem_recovery`), the
hook calls the `timestamp_reinit` function. This function allocates or
finds the `CBMEM_ID_TIMESTAMP` area in cbmem, copies all timestamps from
the `_timestamp` region (if used) using `timestamp_sync_cache_to_cbmem`,
and updates an internal global pointer (`glob_ts_table`) to point to the
cbmem table. Subsequent calls to `timestamp_add` will then write
directly to cbmem.
After such a transition, `timestamp_init()` must not be run again (it is
asserted to only run in `ENV_ROMSTAGE_OR_BEFORE`).
### entries
### Data structures used
Timestamps are stored using instances of `struct timestamp_table`. A
global pointer, `glob_ts_table`, points to the currently active table,
which is either the `_timestamp` memory region (during early boot) or
the CBMEM area.
The `_timestamp` region acts as an early cache before cbmem is ready.
Its size is determined by `REGION_SIZE(timestamp)` defined in the linker
script (`memlayout.ld`) and dictates the maximum number of entries that
can be stored early on.
For timestamps stored in the cbmem area, a `timestamp_table` is
allocated with space for a fixed number of entries (currently 192)
defined by `MAX_TIMESTAMPS` in `src/lib/timestamp.c`.
This field holds the details of each timestamp entry, up to a maximum of
`MAX_TIMESTAMP_CACHE` which is defined as 16 entries. Each entry is
defined by:
```c
struct timestamp_entry {
uint32_t entry_id;
uint64_t entry_stamp;
uint32_t entry_id;
int64_t entry_stamp;
} __packed;
```
`entry_id` holds the timestamp id corresponding to this entry and
`entry_stamp` holds the actual timestamp.
>>> _Source: `/src/commonlib/include/commonlib/timestamp_serialized.h`_
For timestamps stored in the cbmem area, a `timestamp_table` is
allocated with space for `MAX_TIMESTAMPS` equal to 30. Thus, the cbmem
area holds `base_time`, `max_entries` (which is 30), current number of
entries and the actual entries represented by `timestamp_entry`.
```c
struct timestamp_table {
uint64_t base_time;
uint16_t max_entries;
uint16_t tick_freq_mhz;
uint32_t num_entries;
struct timestamp_entry entries[]; /* Variable number of entries */
} __packed;
```
>>> _Source: `/src/commonlib/include/commonlib/timestamp_serialized.h`_
- `base_time`: Indicates the base time (offset) for all timestamp
entries in this table.
- `max_entries`: Maximum number of entries that can be stored in this
table instance.
- `tick_freq_mhz`: Timestamp tick frequency in MHz. Populated later in
boot.
- `num_entries`: Total number of entries that currently exist in this
table instance.
- `entries`: Array holding the actual timestamp entries.
### Memory Layout
Timestamps are stored in two locations during the boot process:
1. **`_timestamp` Region**: Used during early boot stages (before CBMEM
is available), *if* defined in `memlayout.ld`.
- Located at the `_timestamp` symbol.
- Persists across stage transitions within `ENV_ROMSTAGE_OR_BEFORE`.
- Size determined by `REGION_SIZE(timestamp)` in the linker script,
which limits the number of entries.
2. **CBMEM**: Used after CBMEM is initialized.
- Identified by `CBMEM_ID_TIMESTAMP`.
- Provides persistent storage across warm reboots.
- Supports a fixed maximum number of entries.
- Automatically synchronized from the `_timestamp` region when CBMEM
becomes available via `timestamp_reinit`.
## Function APIs
### timestamp_init
### Core Functions
This function initializes the timestamp cache and should be run as early
as possible. On platforms with SRAM, this might mean in bootblock, on
x86 with its CAR backed memory in romstage, this means romstage before
memory init.
#### timestamp_init
### timestamp_add
```c
void timestamp_init(uint64_t base);
```
>>> _Source: `/src/include/timestamp.h`_
This function accepts from user a timestamp id and time to record in the
timestamp table. It stores the entry in the appropriate table in cbmem
or `_timestamp` region or local stash.
Initializes the timestamp system with a base time. This function sets up
the timestamp cache (`_timestamp` region) and should be run in
bootblock. It must be called once in *one* of the
`ENV_ROMSTAGE_OR_BEFORE` stages. It will fail if no `timestamp` region
is defined in `memlayout.ld`.
Note that platform setup code on x86 or the decompressor on Arm can
measure some timestamps earlier and pass them in to
bootblock_main_with_timestamp().
### timestamp_add_now
#### timestamp_add
This function calls `timestamp_add` with user-provided id and current
time.
```c
void timestamp_add(enum timestamp_id id, int64_t ts_time);
```
>>> _Source: `/src/include/timestamp.h`_
Adds a new timestamp with the specified ID and time value. It stores the
timestamp in the currently active table (either `_timestamp` region or
cbmem). The time value must be an absolute time value (typically
obtained from `timestamp_get()`), as it will be adjusted by subtracting
the table's `base_time` before being stored as an entry.
#### timestamp_add_now
```c
void timestamp_add_now(enum timestamp_id id);
```
>>> _Source: `/src/include/timestamp.h`_
Adds a new timestamp with the current time. This function calls
`timestamp_add` with user-provided id and current time obtained from
`timestamp_get()`.
#### timestamp_rescale_table
```c
void timestamp_rescale_table(uint16_t N, uint16_t M);
```
>>> _Source: `/src/include/timestamp.h`_
Applies a scaling factor N/M to all recorded timestamps (including the
`base_time`).
#### get_us_since_boot
```c
uint32_t get_us_since_boot(void);
```
>>> _Source: `/src/include/timestamp.h`_
Returns the time since boot (relative to `base_time`) in microseconds.
Requires `tick_freq_mhz` to be populated.
#### timestamp_get
```c
uint64_t timestamp_get(void);
```
>>> _Source: `/src/include/timestamp.h`_
Returns the current raw timestamp value from the underlying hardware
timer (platform-specific weak implementation).
#### timestamp_tick_freq_mhz
```c
int timestamp_tick_freq_mhz(void);
```
>>> _Source: `/src/include/timestamp.h`_
Returns the timestamp tick frequency in MHz (platform-specific weak
implementation).
### Timestamp IDs
The system uses predefined timestamp IDs to mark various boot stages and
operations. These are organized in ranges:
- 1-500: Miscellaneous coreboot operations (e.g., `TS_POSTCAR_START`,
`TS_DELAY_START`, `TS_READ_UCODE_START`)
- 500-600: Google/ChromeOS specific (e.g., `TS_VBOOT_START`,
`TS_EC_SYNC_START`).
Note many of the existing timestamps here are no longer
Google-specific since many features originally added for Google
vendorcode have since been migrated into general coreboot code.
- 900-940: AMD specific (e.g., `TS_AGESA_INIT_EARLY_START`)
- 940-950: Intel ME specific (e.g., `TS_ME_INFORM_DRAM_START`)
- 950-989: Intel FSP specific (e.g., `TS_FSP_MEMORY_INIT_START`)
- 990-999: Intel ME specific (continued) (e.g., `TS_ME_ROM_START`)
- 1000+: Payload specific
- Depthcharge: 1000-1199
- ChromeOS Hypervisor: 1200-1299
Refer to `src/commonlib/include/commonlib/timestamp_serialized.h` for
the complete list and descriptions.
## Use / Test Cases
The following cases have been considered while designing the timestamp
library. It is important to ensure that any changes made to this library
satisfy each of the following use cases:
The following cases describe the behavior based on the presence of the
`timestamp` region and when cbmem is initialized.
### Case 1: Timestamp Region Exists (Fresh Boot / Resume)
In this case, the library needs to call `timestamp_init` as early as
possible to enable the timestamp cache. Once cbmem is available, the
values will be transferred automatically.
### Case 1: Timestamp Region Exists
All regions are automatically reset on initialization.
This is the standard configuration for collecting early timestamps.
`timestamp_init` must be called in an `ENV_ROMSTAGE_OR_BEFORE` stage to
initialize the `_timestamp` region. When the `CBMEM_READY_HOOK` runs
`timestamp_reinit`, the contents of the `_timestamp` region are copied
to the cbmem table, and subsequent timestamps go directly to cbmem. The
cbmem table is reset on fresh boot or resume.
### Case 2: No timestamp region, fresh boot, cbmem_initialize called after timestamp_init
`timestamp_init` will set up a local cache. cbmem must be initialized
before that cache vanishes - as happens when jumping to the next stage.
### Case 2: No Timestamp Region Defined
### Case 3: No timestamp region, fresh boot, cbmem_initialize called before timestamp_init
If no `timestamp` region is defined in `memlayout.ld`, attempts to call
`timestamp_init` will fail (specifically, `timestamp_cache_get()` will
return NULL). No timestamps can be collected before cbmem is ready.
Timestamps added after `timestamp_reinit` has run (via the
`CBMEM_READY_HOOK`) will be added directly to the cbmem table, but there
will be no `base_time` established from early boot.
This case is not supported right now, just don't call `timestamp_init`
after `cbmem_initialize`. (Patches to make this more robust are
welcome.)
### Case 4: No timestamp region, resume, cbmem_initialize called after timestamp_init
### Case 3: Resume
We always reset the cbmem region before using it, so pre-suspend
timestamps will be gone.
On resume (e.g., x86 S3), `timestamp_reinit` is typically called again.
If `ENV_CREATES_CBMEM` is true for the resume path (as it is for x86
S3), a new cbmem table is allocated by `timestamp_alloc_cbmem_table`,
effectively clearing any pre-suspend timestamps. The `_timestamp` region
content (if any) is copied over, but this usually contains stale data
from the previous boot's early stages.
### Case 5: No timestamp region, resume, cbmem_initialize called before timestamp_init
We always reset the cbmem region before using it, so pre-suspend
timestamps will be gone.
## Configuration
### Kconfig Options
- `CONFIG_COLLECT_TIMESTAMPS`: Enable/disable timestamp collection
globally. If disabled, timestamp functions become no-ops.
- `CONFIG_TIMESTAMPS_ON_CONSOLE`: Print timestamps to console during
boot as they are added.
### Memory Layout
Collecting timestamps before cbmem is ready requires an `_timestamp`
region in the memory layout, defined in the `memlayout.ld` linker
script. Depending on the platform, the memory layout can be for the
board, the SOC, or the Architecture. Any of them will typically follow
the following pattern:
```text
#include <memlayout.h>
...
TIMESTAMP(., 0x200)
...
```
The size allocated to this region determines the maximum number of
timestamps that can be stored before cbmem is available.
The cbmem timestamp table (`CBMEM_ID_TIMESTAMP`) has a fixed size,
currently allowing up to 192 entries. This limit is defined by
`MAX_TIMESTAMPS` in `src/lib/timestamp.c`.
### Hardware Considerations
- x86: `timestamp_init` must be called before CAR (Cache-as-RAM) is torn
down if called from bootblock or separate romstage. The library
includes checks (`timestamp_should_run`) to ensure timestamps are only
added by the primary processor during early boot on AP systems.
- ARM: No special considerations noted in the code.
- RISC-V: No special considerations noted in the code.
## Examples
### Initializing Timestamps (in bootblock)
```c
/* In src/mainboard/$(MAINBOARDDIR)/bootblock.c */
#include <timestamp.h>
#include <timer.h> /* For timestamp_get() default implementation */
void bootblock_mainboard_init(void)
{
/* Initialize timestamp region with current time as base. */
timestamp_init(timestamp_get());
/* Add first timestamp */
timestamp_add_now(TS_BOOTBLOCK_START);
/* ... other bootblock code ... */
}
```
Note: `timestamp_get()` here provides the initial base time. Requires
`CONFIG_COLLECT_TIMESTAMPS=y` and a `timestamp` region.
### Adding Custom Timestamps
```c
#include <timestamp.h>
void my_custom_function(void)
{
timestamp_add_now(TS_DEVICE_INITIALIZE); /* Use a relevant ID */
// ... perform initialization ...
timestamp_add_now(TS_DEVICE_DONE); /* Use a relevant ID */
}
```
## Best Practices
1. **Initialization**:
- Enable `CONFIG_COLLECT_TIMESTAMPS` if needed.
- Define a `timestamp` region in `memlayout.ld` if early
timestamps (before cbmem) are required. Ensure it's large enough
for the expected number of early entries.
- Call `timestamp_init()` exactly once in the earliest possible
`ENV_ROMSTAGE_OR_BEFORE` stage (e.g., `bootblock`).
- Use a consistent base time, typically `timestamp_get()`.
2. **Adding Timestamps**:
- Use appropriate predefined timestamp IDs from
`timestamp_serialized.h` whenever possible. Add custom IDs if
necessary, avoiding conflicts.
- Add timestamps for significant operations or stage transitions
using `timestamp_add_now()`.
- Be mindful of the entry limits: the size of the `_timestamp`
region for early timestamps, and the fixed limit for the cbmem
table. Check for "Timestamp table full" errors in the log.
3. **Analysis**:
- Use the `cbmem -t` utility in the OS (if using LinuxBoot/NERF)
to read and display timestamps stored in CBMEM.
- Consider the `tick_freq_mhz` (also available in the `cbmem -t`
output) when converting raw timestamp differences (`entry_stamp`)
to time units. The raw values are offsets from `base_time`.