U-Boot as a Library: Introducing ulib – A Bridge to the Future of Firmware

The world of firmware development is evolving rapidly. Modern SoCs are increasingly complex, boot protocols are multiplying, and new programming languages like Rust and Zig are gaining traction in systems programming. Meanwhile, U-Boot has accumulated over two decades of battle-tested functionality supporting 1300+ boards, a comprehensive driver model, extensive filesystem support, and a wealth of boot protocols.

What if we could make all this U-Boot functionality available to new projects without requiring them to rebuild everything from scratch? This is the vision behind ulib – the U-Boot library system that transforms U-Boot from a standalone firmware into a reusable library.

What is ulib?

The U-Boot library (ulib) is a groundbreaking feature that allows U-Boot to be built as either a shared library (libu-boot.so) or static library (libu-boot.a). This enables external programs to link against U-Boot’s functionality without needing to integrate directly with the U-Boot build system or create full U-Boot images.

Think of it as taking U-Boot’s extensive hardware abstraction layer, driver model, filesystem support, and utility functions and making them available as a traditional library that any C or Rust program can use.

Key Features and Capabilities

Dual Library Support

  • Shared Library (libu-boot.so): Dynamic linking with smaller executables but runtime dependencies, useful for development
  • Static Library (libu-boot.a): Self-contained executables with no runtime dependencies, ideal for embedded systems

Symbol Renaming System

One of ulib’s most elegant solutions addresses a common problem: symbol conflicts. When you link against U-Boot’s library, you don’t want U-Boot’s printf() to override the standard library’s version. ulib automatically handles this through a sophisticated symbol renaming system:

#include <stdio.h>        // Standard library functions
#include <u-boot-api.h>   // U-Boot functions with ub_ prefix

int main() {
    printf("Using standard printf\n");      // System printf
    ub_printf("Using U-Boot printf\n");     // U-Boot's printf
    return 0;
}

Comprehensive Example Programs

The implementation includes both C and Rust examples that demonstrate real-world usage:

#include <u-boot-lib.h>
#include <u-boot-api.h>

int main(int argc, char *argv[])
{
    // Initialize U-Boot library
    if (ulib_init(argv[0]) < 0)
        return 1;

    // Use U-Boot OS abstraction functions
    int fd = os_open("/proc/version", 0);
    if (fd >= 0) {
        char line[256];
        while (os_fgets(line, sizeof(line), fd)) {
            ub_printf("Read: %s", line);
        }
        os_close(fd);
    }

    // Clean up
    ulib_uninit();
    return 0;
}

Multi-Language Support

ulib isn’t limited to C. The implementation includes a Rust crate (u-boot-sys) that provides FFI bindings, enabling Rust programs to leverage U-Boot functionality:

use u_boot_sys::*;

fn main() {
    unsafe {
        if ulib_init(/* ... */) == 0 {
            ub_printf(b"Hello from Rust!\n\0".as_ptr() as *const i8);
            ulib_uninit();
        }
    }
}

Technical Architecture

Build System Integration

The ulib system is deeply integrated into U-Boot’s build process. When you configure U-Boot with CONFIG_ULIB=y, the build system:

  1. Automatically disables LTO (Link Time Optimization) since it’s incompatible with symbol renaming
  2. Excludes the main() function to allow external programs to provide their own entry points
  3. Applies symbol renaming using objcopy --redefine-sym
  4. Generates API headers with renamed function declarations
  5. Preserves the critical U-Boot linker lists for proper driver initialization

Symbol Renaming Pipeline

The symbol renaming system uses a Python script (scripts/build_api.py) that:

  • Parses symbol definition files (lib/ulib/rename.syms)
  • Extracts function declarations from header files
  • Applies renaming transformations to object files
  • Generates unified API headers with renamed declarations

Linker Considerations

Proper linking requires careful attention to U-Boot’s linker lists. For static libraries, this means using whole-archive linking and custom linker scripts to ensure proper section alignment:

-Wl,--whole-archive $(obj)/libu-boot.a -Wl,--no-whole-archive

Current Status and Platform Support

As of this post, ulib supports the sandbox architecture, making it perfect for:

  • Development and testing on host systems
  • Creating test frameworks that exercise U-Boot code
  • Building applications that need U-Boot’s OS abstraction layer
  • Prototyping new boot protocols or filesystem handlers

The sandbox support is the ideal starting point – it provides a safe, familiar environment for developers to experiment with U-Boot functionality without needing embedded hardware.

Testing and Quality Assurance

The ulib implementation includes comprehensive testing through:

  • Unit Tests: Both shared and static library test programs
  • Integration Tests: Complete example programs demonstrating real usage
  • Python Test Suite: Automated testing via U-Boot’s pytest framework
  • Multi-language Validation: Tests for both C and Rust implementations

The test suite validates everything from basic library initialization to complex symbol renaming scenarios.

License Implications and Considerations

It’s important to understand that ulib inherits U-Boot’s GPL-2.0+ license. This means:

  • Static linking creates derivative works requiring GPL compliance
  • Dynamic linking is less clear
  • Source code distribution is typically required
  • Commercial users should consult legal counsel

The documentation explicitly covers these considerations, ensuring developers make informed decisions.

Real-World Applications

ulib opens up several compelling use cases:

Test Framework Development

Create comprehensive test suites that can exercise U-Boot drivers and functionality without needing physical hardware:

// Test U-Boot filesystem code
if (!ulib_init("test-program")) {
    // Test FAT filesystem functions
    // Test ext4 filesystem functions
    // Test device tree parsing
    ulib_uninit();
}

Rapid Prototyping

Develop new boot protocols or features using familiar development environments before porting to embedded systems.

Cross-Language Integration

Build systems where Rust handles high-level logic while leveraging U-Boot’s proven hardware abstraction:

// Rust application using U-Boot's hardware drivers
let device_info = unsafe { ub_get_device_info() };
process_hardware_data(device_info);

Educational Tools

Create interactive learning tools that demonstrate embedded systems concepts using U-Boot’s extensive codebase.

The Vision: A Bridge to the Future

The author of ulib, Simon Glass, describes it as “a bridge from the best of what we have today to whatever it is that will replace it.” This isn’t hyperbole – it’s a recognition that firmware development is in transition.

Consider the possibilities:

  • Language Evolution: As Rust gains traction in systems programming, ulib provides a path to gradually migrate while preserving decades of hardware support
  • Architectural Changes: Future boot systems can leverage U-Boot’s proven functionality while experimenting with new approaches
  • Testing Revolution: Complex embedded systems can be tested on development machines using U-Boot’s hardware abstraction
  • Innovation Platform: New boot protocols, filesystems, and hardware support can be developed and tested rapidly

Implementation Highlights

The commit series that introduces ulib spans 50+ commits and represents a major engineering effort:

Core Infrastructure

  • Initial library build support
  • Shared library infrastructure
  • Test program foundation
  • Linker script adaptations

Symbol Management

  • Python-based symbol renaming system
  • API header generation
  • Build system integration
  • Symbol conflict resolution

Documentation and Examples

  • Comprehensive documentation covering usage, licensing, and architecture
  • Working C and Rust examples
  • Integration with U-Boot’s test framework
  • Complete build instructions

Tests

  • Python test suite integration
  • Symbol renaming validation
  • Multi-language example testing
  • Comprehensive error handling

Getting Started

Using ulib is straightforward:

# Build U-Boot with library support
make sandbox_defconfig
make -j$(nproc)

# Run the test programs
LD_LIBRARY_PATH=. ./test/ulib/ulib_test          # Shared library
./test/ulib/ulib_test_static                     # Static library

# Build and run examples
cd examples/ulib
make UBOOT_BUILD=/tmp/b/sandbox srctree=../..
./demo_static

Future Roadmap

The ulib foundation enables several exciting developments:

  • Multi-Architecture Support: Extending beyond sandbox to ARM, RISC-V, and x86
  • Selective Subsystem Inclusion: Choose which U-Boot components to include
  • Package Management: Integration with pkg-config and other dependency systems
  • API Versioning: Stability guarantees for external developers
  • Enhanced Language Bindings: Improved Rust integration and potential Python support

Conclusion

ulib represents more than just a technical feature – it’s a paradigm shift that opens U-Boot’s extensive capabilities to the broader systems programming community. By making U-Boot functionality available as a library, we create opportunities for innovation that were previously impossible.

Whether you’re building test frameworks, prototyping new boot systems, exploring cross-language development, or simply want to leverage U-Boot’s proven hardware support in new applications, ulib provides the foundation.

The future of firmware development is uncertain, but with ulib, we can build that future on the solid foundation of U-Boot’s two decades of evolution. It’s not just about preserving the past – it’s about enabling the next generation of innovations in systems software.

The bridge to the future starts here. Welcome to ulib 🙂