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:
A series of sequential states that represent phases of the boot process
A mechanism for callback registration to execute code during state transitions
A framework for blocking and unblocking state transitions
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
:
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:
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:
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
:
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
:
BS_PRE_DEVICE: Initial state before any device operations begin
BS_DEV_INIT_CHIPS: Early chip initialization for critical components
BS_DEV_ENUMERATE: Device enumeration (discovering devices on buses)
BS_DEV_RESOURCES: Resource allocation for devices
BS_DEV_ENABLE: Enabling devices that were discovered
BS_DEV_INIT: Device initialization
BS_POST_DEVICE: All device operations have been completed
BS_OS_RESUME_CHECK: Check if we’re resuming from a sleep state
BS_OS_RESUME: Handle OS resume process (if needed)
BS_WRITE_TABLES: Write system tables (e.g., ACPI, SMBIOS)
BS_PAYLOAD_LOAD: Load the payload into memory
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()
: Callsdev_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()
: Callsdev_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()
: Callsdev_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()
: Callsdev_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()
: Callsdev_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()
: Callsdev_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()
: Callsacpi_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()
: Callswrite_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()
: Callspayload_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()
: Callspayload_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:
It identifies the current state.
Runs all
BS_ON_ENTRY
callbacks for that state.Executes the state’s specific function (e.g.,
bs_dev_enumerate()
).Runs all
BS_ON_EXIT
callbacks for that state.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
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
).
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
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.
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
:
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:
// 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):
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:
#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
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_*
) inhardwaremain.c
.Keep callbacks focused: Each callback should perform a specific, related task and avoid complex operations that might significantly delay the boot process.
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
).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.
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.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.Debug with timestamps and
DEBUG_BOOT_STATE
: Use the timestamp API (timestamp_add_now()
) and enableDEBUG_BOOT_STATE
to measure callback execution time, identify bottlenecks, and understand the flow during development.Document state-specific behavior: When adding callbacks, add comments explaining why they are placed in a particular state and sequence.
Be careful with late states: Avoid registering non-essential callbacks in
BS_PAYLOAD_BOOT
orBS_OS_RESUME
. Callbacks onBS_ON_EXIT
for these states are disallowed by compile-time asserts, as coreboot is about to transfer control.
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.