Documentation: Add Ramstage Bootstates

Change-Id: I18801967be50e2f318b4404d08c171ffa7e92bbc
Signed-off-by: Martin Roth <gaumless@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/87189
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Singer <service+coreboot-gerrit@felixsinger.de>
This commit is contained in:
Martin Roth 2025-04-05 15:52:08 -06:00 committed by Felix Singer
commit d24c4086e1
2 changed files with 515 additions and 0 deletions

View file

@ -13,4 +13,5 @@ Firmware Configuration Interface <fw_config.md>
Relocatable Modules <rmodules.md>
Timers, Stopwatch, and Delays <stopwatch.md>
Threads <threads.md>
Ramstage Bootstates & Bootstate Callbacks <ramstage_bootstates.md>
```

View file

@ -0,0 +1,514 @@
# coreboot Ramstage Bootstates & Bootstate Callbacks
## Introduction
The coreboot boot process is divided into several discrete phases, one
of which is **ramstage**. Ramstage is the phase where the main hardware
initialization and device setup occurs after memory initialization.
Within ramstage, a state machine called the **bootstate machine**
manages the sequence of operations needed to initialize the system,
configure devices, and prepare to load and execute the payload (such as
a bootloader, operating system, or firmware utility).
The bootstate machine provides a structured and extensible way to
organize code execution during the boot process. It allows for clear
separation of concerns between different initialization phases and
provides hooks for component-specific code to run at well-defined
points.
**Important Note:** The exact execution order of multiple callbacks
registered for the same state and sequence (entry/exit) is not
guaranteed. This means that you cannot depend on one call for the
state/sequence in any other calls to the same state/sequence. If this
ordering is required, join the calls to the two functions into a single
function which specifies the order and create a callback to call the
top-level function instead of the two individual callbacks.
## Bootstate Machine Architecture
The bootstate machine's public API is defined in
`src/include/bootstate.h`, and its core implementation resides in
`src/lib/hardwaremain.c`. At its core, it consists of:
1. A series of sequential states that represent phases of the boot process
2. A mechanism for callback registration to execute code during state transitions
3. A framework for blocking and unblocking state transitions
4. Timing and debugging facilities to measure and report performance during boot
### Key Data Structures
The primary public data structure for interacting with the bootstate
machine is `struct boot_state_callback`. The internal implementation
also uses `struct boot_state` and `struct boot_phase`.
#### Boot State Callback (Public API)
Callbacks that run during state transitions are defined by this
structure in `src/include/bootstate.h`:
```c
struct boot_state_callback {
void *arg; // Argument to pass to the callback
void (*callback)(void *arg); // Function pointer to the callback
struct boot_state_callback *next; // Next callback in linked list (internal use)
#if CONFIG(DEBUG_BOOT_STATE)
const char *location; // Source location for debugging
#endif
};
```
#### Boot State Sequence (Public API)
The boot state sequence type, defined in `src/include/bootstate.h`,
specifies when a callback should run relative to the state's main
action:
```c
typedef enum {
BS_ON_ENTRY, // Execute before state function
BS_ON_EXIT // Execute after state function
} boot_state_sequence_t;
```
#### Boot State (Internal Implementation)
The main internal data structure in `src/lib/hardwaremain.c` is
`struct boot_state`, which defines a single state in the bootstate
machine:
```c
struct boot_state {
const char *name; // Human-readable name of the state
boot_state_t id; // Enumerated identifier for the state
u8 post_code; // POST code to output during state execution
struct boot_phase phases[2]; // Entry and exit phases (internal use)
boot_state_t (*run_state)(void *arg); // Function to execute during the state
void *arg; // Argument to pass to the run_state function
int num_samples; // Counter for timing samples (internal use)
bool complete; // Flag indicating if state has completed (internal use)
};
```
#### Boot Phase (Internal Implementation)
Each boot state has two internal phases ("entry" and "exit") represented
by `struct boot_phase` in `src/lib/hardwaremain.c`:
```c
struct boot_phase {
struct boot_state_callback *callbacks; // Linked list of callbacks
int blockers; // Counter for blocking state transition
};
```
## Bootstate Sequence
The bootstate machine defines the following sequence of states, executed
in order by the `bs_walk_state_machine` function in
`src/lib/hardwaremain.c`. The sequence is defined by the `boot_state_t`
enum in `src/include/bootstate.h`:
1. **BS_PRE_DEVICE**: Initial state before any device operations begin
2. **BS_DEV_INIT_CHIPS**: Early chip initialization for critical components
3. **BS_DEV_ENUMERATE**: Device enumeration (discovering devices on buses)
4. **BS_DEV_RESOURCES**: Resource allocation for devices
5. **BS_DEV_ENABLE**: Enabling devices that were discovered
6. **BS_DEV_INIT**: Device initialization
7. **BS_POST_DEVICE**: All device operations have been completed
8. **BS_OS_RESUME_CHECK**: Check if we're resuming from a sleep state
9. **BS_OS_RESUME**: Handle OS resume process (if needed)
10. **BS_WRITE_TABLES**: Write system tables (e.g., ACPI, SMBIOS)
11. **BS_PAYLOAD_LOAD**: Load the payload into memory
12. **BS_PAYLOAD_BOOT**: Boot the payload
This sequence forms the backbone of the ramstage execution flow. Each
state performs a specific task, runs associated callbacks, and
transitions to the next state upon completion, unless blocked.
## Bootstate Details
### BS_PRE_DEVICE
**Purpose**: Serves as the initial state before any device tree
operations begin.
**Key Functions**:
- `bs_pre_device()`: Sets up initial environment and transitions to next
state.
**Usage**: This state is used for initializing core components that need
to be set up before any device operations. Examples include:
- Setting up global NVRAM variables
- Initializing debugging facilities
- Preparing ACPI tables or other critical system structures
### BS_DEV_INIT_CHIPS
**Purpose**: Initializes critical chips early in the boot process.
**Key Functions**:
- `bs_dev_init_chips()`: Calls `dev_initialize_chips()` to initialize
all chips in the device tree.
**Notes**: Chip initialization can disable unused devices, which is why
it happens before device enumeration.
### BS_DEV_ENUMERATE
**Purpose**: Discovers devices in the system.
**Key Functions**:
- `bs_dev_enumerate()`: Calls `dev_enumerate()` to probe and identify
devices.
**Notes**: During this phase, the system scans buses and detects
connected devices.
### BS_DEV_RESOURCES
**Purpose**: Allocates and assigns resources (I/O, memory, IRQs) to
devices.
**Key Functions**:
- `bs_dev_resources()`: Calls `dev_configure()` to compute and assign
bus resources.
**Notes**: Resource allocation resolves conflicts and ensures each
device has the resources it needs.
### BS_DEV_ENABLE
**Purpose**: Enables devices in the system.
**Key Functions**:
- `bs_dev_enable()`: Calls `dev_enable()` to enable devices on the bus.
**Notes**: Some devices may be selectively disabled based on hardware
configuration or policy.
### BS_DEV_INIT
**Purpose**: Initializes enabled devices.
**Key Functions**:
- `bs_dev_init()`: Calls `dev_initialize()` to initialize devices on the
bus.
**Notes**: This state performs device-specific initialization routines
for all enabled devices.
### BS_POST_DEVICE
**Purpose**: Final state after all device operations have completed.
**Key Functions**:
- `bs_post_device()`: Calls `dev_finalize()` to complete any final
device operations.
**Notes**: This state serves as a checkpoint that all device
initialization is complete.
### BS_OS_RESUME_CHECK
**Purpose**: Checks if the system should resume from a sleep state.
**Key Functions**:
- `bs_os_resume_check()`: Looks for a wake vector to determine if resume
is needed.
**Notes**: This state branches the boot flow based on whether the system
is resuming from a sleep state.
### BS_OS_RESUME
**Purpose**: Handles the OS resume process.
**Key Functions**:
- `bs_os_resume()`: Calls `acpi_resume()` with the wake vector to resume
the OS.
**Notes**: After successful resume, control is transferred to the OS and
does not return to coreboot.
### BS_WRITE_TABLES
**Purpose**: Writes configuration tables for the payload or OS.
**Key Functions**:
- `bs_write_tables()`: Calls `write_tables()` to generate system tables.
**Notes**: Tables include ACPI, SMBIOS, and other system configuration
data.
### BS_PAYLOAD_LOAD
**Purpose**: Loads the payload into memory.
**Key Functions**:
- `bs_payload_load()`: Calls `payload_load()` to load the payload.
**Notes**: The payload could be a bootloader, an operating system kernel,
or a firmware utility.
### BS_PAYLOAD_BOOT
**Purpose**: Final state that boots the loaded payload.
**Key Functions**:
- `bs_payload_boot()`: Calls `payload_run()` to execute the payload.
**Notes**: After successful execution, control is transferred to the
payload and does not return to coreboot. If execution returns (which
indicates an error), a boot failure message is printed.
## Driving the State Machine
The state machine is driven by the `main()` function in
`src/lib/hardwaremain.c`. After initial setup (like initializing the
console and CBMEM), it calls `bs_walk_state_machine()`.
`bs_walk_state_machine()` loops through the defined boot states:
1. It identifies the current state.
2. Runs all `BS_ON_ENTRY` callbacks for that state.
3. Executes the state's specific function (e.g., `bs_dev_enumerate()`).
4. Runs all `BS_ON_EXIT` callbacks for that state.
5. Transitions to the next state returned by the state function.
This loop continues until the final state (`BS_PAYLOAD_BOOT` or
`BS_OS_RESUME`) transfers control away from coreboot.
## External Functions (Public API)
The bootstate machine provides several functions in
`src/include/bootstate.h` for interacting with states:
### Callback Registration
```c
int boot_state_sched_on_entry(struct boot_state_callback *bscb, boot_state_t state_id);
```
Schedules a callback to run when entering a state (`BS_ON_ENTRY`).
```c
int boot_state_sched_on_exit(struct boot_state_callback *bscb, boot_state_t state_id);
```
Schedules a callback to run when exiting a state (`BS_ON_EXIT`).
### State Transition Control
```c
int boot_state_block(boot_state_t state, boot_state_sequence_t seq);
```
Blocks a state transition from occurring after the specified sequence
(entry or exit callbacks). The transition will pause until the block is
removed.
```c
int boot_state_unblock(boot_state_t state, boot_state_sequence_t seq);
```
Removes a previously set block on a state transition.
### Static Callback Registration
For registering callbacks at compile time, use the `BOOT_STATE_INIT_ENTRY`
macro defined in `src/include/bootstate.h`:
```c
BOOT_STATE_INIT_ENTRY(state, when, func, arg)
```
This macro creates a static entry in a special section (`.bs_init`) of
the binary. These entries are processed early in `main()` by
`boot_state_schedule_static_entries()` to register the callbacks before
the state machine starts running.
## Configuration Options
The bootstate machine behavior can be modified through Kconfig options:
### DEBUG_BOOT_STATE
```
config DEBUG_BOOT_STATE
bool "Debug boot state machine"
default n
help
Control debugging of the boot state machine. When selected displays
the state boundaries in ramstage.
```
When enabled, this option causes the bootstate machine to output
debugging information via `printk`, including:
- State transition notifications (`Entering/Exiting <state> state.`)
- Callback execution details (address, source location, execution time)
- Timing information for state execution phases (entry, run, exit)
## Examples
### Adding a New Bootstate Callback
To register a function to be called when entering a specific state using
the static registration method:
```c
// Function to be called
static void my_init_function(void *arg)
{
// Initialization code
printk(BIOS_DEBUG, "My initialization running...\n");
}
// Register the callback at compile time
BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_ENTRY, my_init_function, NULL);
```
### Runtime Callback Registration
For dynamic callback registration during runtime (e.g., within another
callback or state function):
```c
static void runtime_init(void *arg)
{
// Do something
}
void register_my_callbacks(void)
{
// Allocate or define a static callback structure
static struct boot_state_callback bscb = {
.callback = runtime_init,
.arg = NULL,
// .location is automatically handled if DEBUG_BOOT_STATE=y
};
// Schedule it
boot_state_sched_on_entry(&bscb, BS_DEV_ENABLE);
}
```
### Blocking State Transition
To temporarily block a state from progressing until a condition is met,
often used with timers:
```c
#include <timer.h> // Required for timer functions
static void wait_for_device(void *arg)
{
if (!device_is_ready()) {
// Block the transition *after* BS_DEV_INIT exits
boot_state_block(BS_DEV_INIT, BS_ON_EXIT);
// Schedule a function to check again later (e.g., after 100us)
// Assume schedule_timer exists and works appropriately
schedule_timer(check_device_ready, NULL, 100);
}
}
static void check_device_ready(void *arg)
{
if (device_is_ready()) {
// Device is ready, unblock the transition
boot_state_unblock(BS_DEV_INIT, BS_ON_EXIT);
} else {
// Still not ready, check again later
schedule_timer(check_device_ready, NULL, 100);
}
}
// Register the initial check to run when entering BS_DEV_INIT
BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_ENTRY, wait_for_device, NULL);
```
## Best Practices
### When Working with Bootstates
1. **Choose the appropriate state**: Register callbacks at the earliest
state where all dependencies are guaranteed to be initialized, but no
earlier. Check the state descriptions and the functions called by
each state function (`bs_*`) in `hardwaremain.c`.
2. **Keep callbacks focused**: Each callback should perform a specific,
related task and avoid complex operations that might significantly
delay the boot process.
3. **Consider dependencies carefully**: Ensure any hardware, data
structures, or other resources your callback needs are available and
initialized at the chosen state and sequence (`BS_ON_ENTRY` vs.
`BS_ON_EXIT`).
4. **Do not rely on callback order**: Remember that the execution order
of callbacks within the same state and sequence is not guaranteed.
Callbacks should be self-contained and not depend on side effects from
other callbacks that might run before or after them in the same phase.
5. **Use blocking sparingly**: The blocking mechanism is powerful for
synchronization but can complicate the boot flow and make debugging
harder if overused. Always ensure a corresponding `boot_state_unblock`
call will eventually run.
6. **Leverage compile-time registration**: Prefer using
`BOOT_STATE_INIT_ENTRY` for callbacks whenever possible. It makes the
registration explicit and easier to find. Runtime registration is
necessary only when the need for the callback is determined dynamically.
7. **Debug with timestamps and `DEBUG_BOOT_STATE`**: Use the timestamp API
(`timestamp_add_now()`) and enable `DEBUG_BOOT_STATE` to measure
callback execution time, identify bottlenecks, and understand the
flow during development.
8. **Document state-specific behavior**: When adding callbacks, add
comments explaining why they are placed in a particular state and
sequence.
9. **Be careful with late states**: Avoid registering non-essential
callbacks in `BS_PAYLOAD_BOOT` or `BS_OS_RESUME`. Callbacks on
`BS_ON_EXIT` for these states are disallowed by compile-time asserts,
as coreboot is about to transfer control.
## Related Documentation
- [Boot Stages in coreboot](https://doc.coreboot.org/getting_started/architecture.html):
Overview of all coreboot boot stages.
## References
- `src/include/bootstate.h`: Public API definitions (callbacks, enums,
scheduling/blocking functions, static registration macro).
- `src/lib/hardwaremain.c`: Internal implementation (state machine driver,
state definitions, state functions).
- `src/ec/google/wilco/chip.c`: Example of bootstate callback usage.
- `src/mainboard/prodrive/hermes/mainboard.c`: Examples of mainboard-specific
bootstate callbacks.