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:
5
examples/rust/.gitignore
vendored
Normal file
5
examples/rust/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
build-dynamic/
|
||||
build-static/
|
||||
demo
|
||||
demo_static
|
||||
target/
|
||||
14
examples/rust/Cargo.lock
generated
Normal file
14
examples/rust/Cargo.lock
generated
Normal 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
17
examples/rust/Cargo.toml
Normal 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
95
examples/rust/Makefile
Normal 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
120
examples/rust/README.md
Normal 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
105
examples/rust/build.rs
Normal 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
112
examples/rust/src/demo.rs
Normal 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(())
|
||||
}
|
||||
59
examples/rust/src/rust_helper.rs
Normal file
59
examples/rust/src/rust_helper.rs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user