
From: Simon Glass <sjg@chromium.org> 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> --- Makefile | 18 ++++- examples/rust/.gitignore | 5 ++ examples/rust/Cargo.lock | 14 ++++ examples/rust/Cargo.toml | 17 +++++ examples/rust/Makefile | 95 ++++++++++++++++++++++++ examples/rust/README.md | 120 +++++++++++++++++++++++++++++++ examples/rust/build.rs | 105 +++++++++++++++++++++++++++ examples/rust/src/demo.rs | 112 +++++++++++++++++++++++++++++ examples/rust/src/rust_helper.rs | 59 +++++++++++++++ 9 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 examples/rust/.gitignore create mode 100644 examples/rust/Cargo.lock create mode 100644 examples/rust/Cargo.toml create mode 100644 examples/rust/Makefile create mode 100644 examples/rust/README.md create mode 100644 examples/rust/build.rs create mode 100644 examples/rust/src/demo.rs create mode 100644 examples/rust/src/rust_helper.rs diff --git a/Makefile b/Makefile index c4a5dbea199..8a55b944374 100644 --- a/Makefile +++ b/Makefile @@ -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' \ diff --git a/examples/rust/.gitignore b/examples/rust/.gitignore new file mode 100644 index 00000000000..ff2e4c064a4 --- /dev/null +++ b/examples/rust/.gitignore @@ -0,0 +1,5 @@ +build-dynamic/ +build-static/ +demo +demo_static +target/ diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock new file mode 100644 index 00000000000..bd483c2704f --- /dev/null +++ b/examples/rust/Cargo.lock @@ -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", +] diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml new file mode 100644 index 00000000000..e86f71af699 --- /dev/null +++ b/examples/rust/Cargo.toml @@ -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] diff --git a/examples/rust/Makefile b/examples/rust/Makefile new file mode 100644 index 00000000000..d9fcedea60d --- /dev/null +++ b/examples/rust/Makefile @@ -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 diff --git a/examples/rust/README.md b/examples/rust/README.md new file mode 100644 index 00000000000..cc2a1d0b82f --- /dev/null +++ b/examples/rust/README.md @@ -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/` diff --git a/examples/rust/build.rs b/examples/rust/build.rs new file mode 100644 index 00000000000..9c713ba574c --- /dev/null +++ b/examples/rust/build.rs @@ -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"); +} diff --git a/examples/rust/src/demo.rs b/examples/rust/src/demo.rs new file mode 100644 index 00000000000..4ff58e700c7 --- /dev/null +++ b/examples/rust/src/demo.rs @@ -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(()) +} diff --git a/examples/rust/src/rust_helper.rs b/examples/rust/src/rust_helper.rs new file mode 100644 index 00000000000..e2270065124 --- /dev/null +++ b/examples/rust/src/rust_helper.rs @@ -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 +} \ No newline at end of file -- 2.43.0