ulib: Add a Rust example

The U-Boot library can be used from Rust fairly easily. Add an example
for this, following along the lines of the existing ulib example.

Note that the new way of representing C strings is not used for now,
since it is not available in v1.75 of cargo, as shipped by Ubuntu 24.04

Co-developed-by: Claude <noreply@anthropic.com>
Co-developed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass
2025-09-10 13:28:45 -06:00
parent 791e492d39
commit 44127a8185
9 changed files with 544 additions and 1 deletions

View File

@@ -1049,7 +1049,7 @@ ifeq ($(NO_LIBS),)
INPUTS-$(CONFIG_ULIB) += libu-boot.so test/ulib/ulib_test
INPUTS-$(CONFIG_ULIB) += libu-boot.a test/ulib/ulib_test_static
ifdef CONFIG_EXAMPLES
INPUTS-$(CONFIG_ULIB) += examples_ulib
INPUTS-$(CONFIG_ULIB) += examples_ulib examples_rust
endif
endif
endif
@@ -1950,6 +1950,18 @@ examples_ulib: libu-boot.a libu-boot.so FORCE
PLATFORM_LIBS="$(PLATFORM_LIBS)" \
LIB_STATIC_LDS="$(abspath $(LIB_STATIC_LDS))"
PHONY += examples_rust
examples_rust: libu-boot.a libu-boot.so FORCE
@if command -v cargo >/dev/null 2>&1; then \
$(MAKE) -C $(srctree)/examples/rust \
UBOOT_BUILD=$(abspath $(obj)) \
OUTDIR=$(abspath $(obj)/examples/rust) \
srctree="$(abspath $(srctree))"; \
else \
echo "Skipping Rust examples (cargo not found)"; \
echo "Install from https://rustup.rs/"; \
fi
quiet_cmd_sym ?= SYM $@
cmd_sym ?= $(OBJDUMP) -t $< > $@
u-boot.sym: u-boot FORCE
@@ -2383,6 +2395,10 @@ clean: $(clean-dirs)
$(call cmd,rmfiles)
@$(MAKE) -C $(srctree)/examples/ulib clean \
OUTDIR=$(abspath $(obj)/examples/ulib)
@if command -v cargo >/dev/null 2>&1 && [ -d $(srctree)/examples/rust ]; then \
$(MAKE) -C $(srctree)/examples/rust clean \
OUTDIR=$(abspath $(obj)/examples/rust); \
fi
@find $(if $(KBUILD_EXTMOD), $(KBUILD_EXTMOD), .) $(RCS_FIND_IGNORE) \
\( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \
-o -name '*.ko.*' -o -name '*.su' -o -name '*.pyc' \

5
examples/rust/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
build-dynamic/
build-static/
demo
demo_static
target/

14
examples/rust/Cargo.lock generated Normal file
View File

@@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "u-boot-sys"
version = "0.1.0"
[[package]]
name = "uboot-rust-demo"
version = "0.1.0"
dependencies = [
"u-boot-sys",
]

17
examples/rust/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0+
[package]
name = "uboot-rust-demo"
version = "0.1.0"
edition = "2021"
license = "GPL-2.0+"
description = "Rust demo program for U-Boot library"
authors = ["Simon Glass <simon.glass@canonical.com>"]
[dependencies]
u-boot-sys = { path = "../../lib/rust" }
[[bin]]
name = "demo"
path = "src/demo.rs"
[build-dependencies]

95
examples/rust/Makefile Normal file
View File

@@ -0,0 +1,95 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Makefile for U-Boot Rust example
#
# Copyright 2025 Canonical Ltd.
# Written by Simon Glass <simon.glass@canonical.com>
# This Makefile integrates with the U-Boot build system, similar to
# examples/ulib
#
# Usage: cd examples/rust; make UBOOT_BUILD=/tmp/b/sandbox srctree=../..
#
# Default paths - can be overridden
UBOOT_BUILD ?= /tmp/b/sandbox
srctree ?= ../..
OUTDIR ?= .
# Export for build.rs
export UBOOT_BUILD
# Common source dependencies
RUST_SOURCES = Cargo.toml src/demo.rs build.rs
# Absolute output directory for Cargo builds
OUTDIR_ABS = $(abspath $(OUTDIR))
# Ensure 'cargo' is available
CARGO := $(shell command -v cargo 2> /dev/null)
ifeq ($(CARGO),)
$(error "Cargo not found. Please install Rust toolchain from \
https://rustup.rs/")
endif
# Default target - build both static and dynamic versions like examples_ulib
all: $(OUTDIR)/demo $(OUTDIR)/demo_static
# Create output directory
$(OUTDIR):
@mkdir -p $(OUTDIR)
# Build dynamic version (links with libu-boot.so)
$(OUTDIR)/demo: $(RUST_SOURCES) $(UBOOT_BUILD)/libu-boot.so | $(OUTDIR)
@echo "Building Rust demo (dynamic) with library from $(UBOOT_BUILD)"
@echo "OUTDIR=$(OUTDIR), abspath=$(OUTDIR_ABS)"
@if [ ! -f "$(UBOOT_BUILD)/libu-boot.so" ]; then \
echo "No shared library at $(UBOOT_BUILD)/libu-boot.so" >&2; \
echo "Please build U-Boot: make sandbox_defconfig && make" >&2; \
exit 1; \
fi
@UBOOT_DYNAMIC=1 $(CARGO) build --target-dir \
$(OUTDIR_ABS)/build-dynamic --release --bin demo -q
@cp $(OUTDIR_ABS)/build-dynamic/release/demo $(OUTDIR)/
# Build static version (links with libu-boot.a)
$(OUTDIR)/demo_static: $(RUST_SOURCES) $(UBOOT_BUILD)/libu-boot.a | $(OUTDIR)
@echo "Building Rust demo (static) with library from $(UBOOT_BUILD)"
@if [ ! -f "$(UBOOT_BUILD)/libu-boot.a" ]; then \
echo "No static library at $(UBOOT_BUILD)/libu-boot.a" >&2; \
echo "Please build U-Boot: make sandbox_defconfig && make" >&2; \
exit 1; \
fi
@$(CARGO) build --target-dir $(OUTDIR_ABS)/build-static --release \
--bin demo -q
@cp $(OUTDIR_ABS)/build-static/release/demo $(OUTDIR)/demo_static
demo: $(OUTDIR)/demo
# Test the programs
test: $(OUTDIR)/demo $(OUTDIR)/demo_static
@echo "Testing Rust demos..."
@echo "Testing dynamic version:"
@LD_LIBRARY_PATH="$(UBOOT_BUILD)" $(OUTDIR)/demo
@echo "Testing static version:"
@$(OUTDIR)/demo_static
# Clean build artifacts
clean:
$(CARGO) clean
@if [ "$(OUTDIR)" != "." ]; then \
rm -rf $(OUTDIR_ABS)/build-dynamic \
$(OUTDIR_ABS)/build-static $(OUTDIR)/demo \
$(OUTDIR)/demo_static; \
else \
rm -f demo demo_static; \
fi
# Show cargo version and info
info:
@echo "Rust toolchain information:"
$(CARGO) --version
@echo "U-Boot build directory: $(UBOOT_BUILD)"
@echo "U-Boot source tree: $(srctree)"
.PHONY: all test clean info

120
examples/rust/README.md Normal file
View File

@@ -0,0 +1,120 @@
# Technical Notes - Rust U-Boot Integration
This directory contains a simple Rust implementation for using U-Boot's
library. Both static and dynamic linking (of libu-boot) are available.
This directory is intended to be used separately from U-Boot's build system,
e.g. by copying it somewhere else.
For comprehensive documentation, see :doc:`/doc/develop/ulib`.
## Build System Success
The following components are included:
### 1. u-boot-sys Crate
The `lib/rust/u-boot-sys` crate provides FFI bindings organized into modules:
- `uboot_lib`: Library management functions (`ulib_*`)
- `uboot_api`: U-Boot API functions (`ub_*`)
- `os`: OS abstraction functions (`os_*`)
Functions are re-exported at the crate root for convenience.
### 2. Example Program Structure
- `demo.rs`: Main program using the u-boot-sys crate
- `rust_helper.rs`: Helper functions similar to C demo_helper.c
- Modular design with proper separation of concerns
### 3. Build Configuration
- `build.rs` configures linking with both static and dynamic libraries
- Uses `--whole-archive` for static linking to preserve U-Boot linker lists
- Links required system libraries (pthread, dl, rt, SDL2) with proper ordering
- Uses U-Boot's existing stack-protection implementation from stackprot.c
### 4. Integration
- Makefile can be use standalone or from U-Boot build system
- Cargo project depends on `u-boot-sys` crate from `../../lib/rust`
- Examples are built automatically as part of U-Boot's build system
### 5. Runtime Execution ✅
- `demo` (dynamic) and `demo-static` executables work the same
- Calls library init with `ulib_init()`
- File operations using U-Boot's OS abstraction layer
- Uses renamed U-Boot library functions (`ub_printf`)
- Clean shutdown with `ulib_uninit()`
## Architecture Overview
### u-boot-sys Crate Design
- Located in `lib/rust/` for reuse across U-Boot Rust projects
- Follows Rust `*-sys` naming conventions for FFI binding crates
- Intended to provide a safe, well-documented interfaces to U-Boot functions
- Organized into logical modules with re-exports for convenience
### Dependency Structure
```
examples/rust/demo → u-boot-sys → U-Boot C library
libu-boot.so/.a
```
### Build System Integration
- Uses standard Cargo dependency resolution
- Makefile provides U-Boot-specific environment setup
- Compatible with both U-Boot's build system and standalone Cargo builds
## Features Demonstrated
- **FFI Integration**: Shows how to call U-Boot C functions from Rust
- **Library Initialization**: Proper `ulib_init()` and `ulib_uninit()` usage
- **File Operations**: Using U-Boot's `os_open()`, `os_fgets()`, `os_close()`
- **Print Functions**: Using U-Boot's `ub_printf()` vs Rust's `println!()`
- **Mixed Languages**: Combining Rust and C functionality seamlessly
## Prerequisites
1. **Rust toolchain**: Install from https://rustup.rs/
2. **U-Boot library**: Build U-Boot sandbox with library support:
```bash
make sandbox_defconfig
make
```
## Usage Examples
```bash
# Ensure U-Boot is built first
# Note this builds rust examples at /tmp/b/sandbox/example/rust
make O=/tmp/b/sandbox sandbox_defconfig all
# Build both versions using Makefile
cd examples/rust
make UBOOT_BUILD=/tmp/b/sandbox srctree=../..
# Or build directly with Cargo
env UBOOT_BUILD=/tmp/b/sandbox cargo build --release
# Test dynamic version
LD_LIBRARY_PATH=/tmp/b/sandbox ./demo
# Test static version
./demo-static
# Run tests
make test
```
## License
GPL-2.0+ (same as U-Boot)
Note: Linking with U-Boot's GPL-2.0+ library makes your program subject to
GPL licensing terms.
## Documentation and Further Reading
- See `doc/develop/ulib.rst`
- **C examples**: Located in `examples/ulib/` for comparison
- **u-boot-sys crate**: API documentation via `cargo doc` in `lib/rust/`

105
examples/rust/build.rs Normal file
View File

@@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Build script for U-Boot Rust demo
*
* This configures linking with the U-Boot library, similar to how
* the C examples are built in examples/ulib
*
* Copyright 2025 Canonical Ltd.
* Written by Simon Glass <simon.glass@canonical.com>
*/
use std::env;
use std::path::PathBuf;
use std::process::Command;
fn main() {
// Get the U-Boot build directory from environment or use default
let uboot_build = env::var("UBOOT_BUILD")
.unwrap_or_else(|_| "/tmp/b/sandbox".to_string());
let uboot_build_path = PathBuf::from(&uboot_build);
println!("cargo:rerun-if-env-changed=UBOOT_BUILD");
println!("cargo:rerun-if-changed=build.rs");
// Add library search path
println!("cargo:rustc-link-search=native={}", uboot_build_path.display());
// Check if dynamic linking is requested
if env::var("UBOOT_DYNAMIC").is_ok() {
// Dynamic linking with libu-boot.so
println!("cargo:rustc-link-lib=dylib=u-boot");
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", uboot_build_path.display());
} else {
// Static linking with libu-boot.a using whole-archive to ensure
// all symbols are included (required for U-Boot linker lists)
// Use the same linker script as the C examples for proper linker list ordering
let static_lib_path = uboot_build_path.join("libu-boot.a");
// Use absolute path to the linker script in the source tree
let current_dir = env::current_dir().expect("Failed to get current directory");
let linker_script_path = current_dir.join("../ulib/static.lds");
println!("cargo:rustc-link-arg=-Wl,-T,{}", linker_script_path.display());
println!("cargo:rustc-link-arg=-Wl,--whole-archive");
println!("cargo:rustc-link-arg={}", static_lib_path.display());
println!("cargo:rustc-link-arg=-Wl,--no-whole-archive");
println!("cargo:rustc-link-arg=-Wl,-z,noexecstack");
// Add required system libraries AFTER --no-whole-archive like the C version
println!("cargo:rustc-link-arg=-lpthread");
println!("cargo:rustc-link-arg=-ldl");
// Get SDL libraries using sdl2-config like the C version does
if let Ok(output) = Command::new("sdl2-config").arg("--libs").output() {
if output.status.success() {
let libs_str = String::from_utf8_lossy(&output.stdout);
for lib_flag in libs_str.split_whitespace() {
if lib_flag.starts_with("-l") {
println!("cargo:rustc-link-arg={}", lib_flag);
}
}
}
} else {
// Fallback to just SDL2 if sdl2-config is not available
println!("cargo:rustc-link-arg=-lSDL2");
}
}
// For dynamic linking, link required system libraries normally
if env::var("UBOOT_DYNAMIC").is_ok() {
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=dl");
println!("cargo:rustc-link-lib=rt");
}
// Optional SDL2 support (if available) - only for dynamic linking
if env::var("UBOOT_DYNAMIC").is_ok() {
if let Ok(sdl_libs) = env::var("SDL_LIBS") {
for lib in sdl_libs.split_whitespace() {
if let Some(lib_name) = lib.strip_prefix("-l") {
println!("cargo:rustc-link-lib={}", lib_name);
}
}
}
}
// Define symbols that may be missing (from U-Boot troubleshooting docs)
// Note: __stack_chk_guard is provided by U-Boot's common/stackprot.c
println!("cargo:rustc-link-arg=-Wl,--defsym,sandbox_sdl_sync=0");
// For static linking, we need to ensure libc is available for atexit and other functions
if env::var("UBOOT_DYNAMIC").is_err() {
println!("cargo:rustc-link-arg=-lc");
}
// Add include path for headers if needed for bindgen (future enhancement)
let uboot_include = uboot_build_path.join("include");
if uboot_include.exists() {
println!("cargo:include={}", uboot_include.display());
}
// Recompile if main source changes
println!("cargo:rerun-if-changed=src/demo.rs");
}

112
examples/rust/src/demo.rs Normal file
View File

@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Rust demo program showing U-Boot library functionality
*
* This demonstrates using U-Boot library functions from Rust,
* modeled after examples/ulib/demo.c
*
* Copyright 2025 Canonical Ltd.
* Written by Simon Glass <simon.glass@canonical.com>
*/
#![allow(clippy::manual_c_str_literals)]
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
mod rust_helper;
use rust_helper::{demo_add_numbers, demo_show_banner, demo_show_footer};
use u_boot_sys::{os_close, os_fgets, os_open, ub_printf, ulib_get_version,
ulib_init, ulib_uninit};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get program name for ulib_init
let args: Vec<String> = std::env::args().collect();
let program_name = CString::new(args[0].clone())?;
// Init U-Boot library
let ret = unsafe {
ulib_init(program_name.as_ptr() as *mut c_char)
};
if ret != 0 {
eprintln!("Failed to initialize U-Boot library");
return Err("ulib_init failed".into());
}
demo_show_banner();
// Display U-Boot version using ulib_get_version()
let version_ptr = unsafe { ulib_get_version() };
unsafe {
ub_printf(
b"U-Boot version: %s\n\0".as_ptr() as *const c_char,
version_ptr,
);
ub_printf(b"\n\0".as_ptr() as *const c_char);
}
// Use U-Boot's os_open() to open a file
let filename = CString::new("/proc/version")?;
let fd = unsafe { os_open(filename.as_ptr(), 0) };
if fd < 0 {
eprintln!("Failed to open /proc/version");
unsafe { ulib_uninit(); }
return Err("os_open failed".into());
}
unsafe {
ub_printf(
b"System version:\n\0".as_ptr() as *const c_char,
);
}
// Read lines using U-Boot's os_fgets()
let mut lines = 0;
let mut buffer = [0i8; 256]; // Use array instead of Vec to avoid heap
// allocation
loop {
let result = unsafe {
os_fgets(buffer.as_mut_ptr(), buffer.len() as c_int, fd)
};
if result.is_null() {
break;
}
unsafe {
ub_printf(
b" %s\0".as_ptr() as *const c_char,
buffer.as_ptr(),
);
}
lines += 1;
}
unsafe { os_close(fd) };
unsafe {
ub_printf(
b"\nRead %d line(s) using U-Boot library functions.\n\0"
.as_ptr() as *const c_char,
lines,
);
}
// Test the helper function
let result = demo_add_numbers(42, 13);
unsafe {
ub_printf(
b"Helper function result: %d\n\0".as_ptr() as *const c_char,
result,
);
}
demo_show_footer();
// Clean up
unsafe {
ulib_uninit();
}
Ok(())
}

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Helper functions for Rust U-Boot library demo
*
* Copyright 2025 Canonical Ltd.
* Written by Simon Glass <simon.glass@canonical.com>
*/
#![allow(clippy::manual_c_str_literals)]
use std::os::raw::c_char;
use u_boot_sys::ub_printf;
/// Show the demo banner
pub fn demo_show_banner() {
unsafe {
ub_printf(
b"U-Boot Library Demo Helper\n\0".as_ptr() as *const c_char,
);
ub_printf(
b"==========================\n\0".as_ptr() as *const c_char,
);
}
}
/// Show the demo footer
pub fn demo_show_footer() {
unsafe {
ub_printf(
b"=================================\n\0".as_ptr() as *const c_char,
);
ub_printf(
b"Demo complete (hi from rust)\n\0".as_ptr() as *const c_char,
);
}
}
/// Add two numbers and print the result
///
/// # Arguments
///
/// * `a` - First number
/// * `b` - Second number
///
/// # Returns
///
/// Sum of the two numbers
pub fn demo_add_numbers(a: i32, b: i32) -> i32 {
unsafe {
ub_printf(
b"helper: Adding %d + %d = %d\n\0".as_ptr() as *const c_char,
a,
b,
a + b,
);
}
a + b
}