Upstream contributor’s guide#
pw_kernel: A Rust-based kernel for embedded systems
This guide is for Upstream Pigweed contributors working on
pw_kernel. If you’re interested in using pw_kernel in a downstream
project, see Quickstart.
Prerequisites#
This guide assumes you’re already set up for upstream development. If not, see Contributing guide.
Configurations#
pw_kernel provides several Bazel configurations for different targets and
build types:
k_host: For building and running on your host machine (Linux, macOS).k_qemu_mps2_an505: For QEMU emulating an Arm Cortex-M33 based system (MPS2-AN505).k_qemu_virt_riscv32: For QEMU emulating a RISC-V 32-bit based system.k_rp2350: For the Raspberry Pi RP2350 microcontroller.
In subsequent sections the placeholder <config> should be replaced with one
of these values.
VS Code setup#
For the best Rust development experience, especially with VS Code, we recommend rust-analyzer.
rust-analyzer needs a rust-project.json file at the root of your workspace
to understand the project structure, dependencies, and build configurations.
You can generate this file using Bazel.
For a given configuration:
bazelisk run @rules_rust//tools/rust_analyzer:gen_rust_project \
-- --config <config> //pw_kernel/...
This command creates or updates the rust-project.json file in your Pigweed
project root.
To enable rust-analyzer to provide real-time feedback (errors and warnings)
in VS Code based on your Bazel build configuration, add the following to your
Pigweed project’s .vscode/settings.json file.
"rust-analyzer.check.overrideCommand": [
"bazelisk",
"build",
"--config=k_lint",
"--config=$CONFIG",
"--@rules_rust//:error_format=json",
"--experimental_ui_max_stdouterr_bytes=10485760",
"//pw_kernel/..."
]
Build#
To build pw_kernel for a given configuration:
bazelisk build --config <config> //pw_kernel/...
Test#
To run all pw_kernel tests for a given configuration:
bazelisk test --config <config> //pw_kernel/...
To run unit tests tests for the RISC-V QEMU target and see all test output:
bazelisk test --test_output=all --cache_test_results=no \
--config k_qemu_virt_riscv32 \
//pw_kernel/target/qemu_virt_riscv32/unittest_runner
The test runner executes all discovered tests and reports their status. Bare-metal tests are run first, followed by kernel-aware tests if the kernel is initialized.
Raspberry Pi RP2350#
You can run the tests on a physical RP2350-based board.
Build a test:
bazelisk build --config k_rp2350 \ //pw_kernel/target/pw_rp2350/ipc/user:ipc
Confirm that the ELF file was output to
//bazel-bin/pw_kernel/target/pw_rp2350/ipc/user/.Flash a test:
probe-rs download bazel-bin/pw_kernel/target/pw_rp2350/ipc/user/ipc.elf \ && probe-rs reset --chip rp2350
See Installation if you don’t have
probe-rsinstalled.Run a test:
bazelisk run --config k_rp2350 //pw_kernel/target/pw_rp2350/ipc/user:ipc \ -- -d <PORT>
The placeholder
<PORT>should be replaced with the actual serial port, e.g./dev/ttyACM0on Linux or/dev/cu.usbmodemXXXXXXon macOS.This command will first build the firmware if necessary and then connect to the device using pw_tokenizer.serial_detokenizer to display human-readable logs.
Conventions#
This section outlines the conventions used in pw_kernel to ensure
consistency and maintainability.
Logging and error handling#
Consistent logging is crucial for debugging and maintaining pw_kernel.
General logging style#
Capitalization: Log messages should be capitalized as sentences, unless they begin with a specific identifier or symbol that is lowercase by definition (e.g.,
thread_name).Punctuation: Do not end log messages with a trailing period. Exception: If a log message contains multiple full sentences, use periods for the intermediate sentences, but omit it for the final one.
Clarity: Prefer clear, descriptive messages over cryptic abbreviations.
Register and value dumping#
Format: Use
Key=Valueformat for technical dumps, separated by commas if on the same line. Example:Exception: cause={:#x}, epc={:#x}Hexadecimal: Use standard Rust hex formatting
{:#x}for values. For full 32-bit addresses, use{:#010x}to include the0xprefix and leading zeros.Threads: When logging thread information, use the format
'thread_name' ({:#010x}), where the name is in single quotes and the ID is in parentheses and hex format. Example:Context switch to thread 'idle' (0x20001234)
Optional debug logging#
High-frequency or verbose logs that are useful for specific debugging sessions
but too noisy for general use should be gated by a const bool flag using
the log_if crate.
Flags: Define a
const boolflag at the top of the file (e.g.,const LOG_SCHEDULER_EVENTS: bool = false;).Default State: These flags must be committed as
falseby default.Usage: Use
log_if::info_if!orlog_if::debug_if!with the flag. Example:log_if::info_if!(LOG_CONTEXT_SWITCH, "Context switch to thread '{}' ({:#x})", ...)
Panics#
Macro: Always use
pw_assert::panic!instead of the standard librarypanic!.Style: Follow the same capitalization and punctuation rules as general logs (capitalized, no trailing period).
Context: Provide as much context as reasonable in the panic message.
Bad:
pw_assert::panic!("Run queue empty")Good:
pw_assert::panic!("Run queue empty: no runnable threads (idle thread missing?)")
Unimplemented: For unimplemented features, use
pw_assert::panic!("Unimplemented: <feature_name>")instead oftodo!()or generic “Unimplemented” messages.