16.1. Bare Metal Rust with RTEMS

To develop with Rust and RTEMS together, you must find a Rust bare metal target which matches an RTEMS BSP. The instructions in this section are for a SPARC and a Risc-V hello world program. These examples use the combinations shown in the table below:

RTEMS Architecture

RTEMS BSP

Rust Target

Rust CPU

rtems-sparc

sparc/leon3

sparc-unknown-none-elf

leon3

rtems-riscv

riscv/rv64imafdc

riscv64gc-unknown-none-elf

generic-rv64

The following sources may be helpful to find a matching BSP and target:

  • ./waf bsplist – executed in an RTEMS git clone

  • source-builder/sb-set-builder --list-bsets – executed in an RTEMS source builder git clone

  • ./rtems-bsps – executed in an RTEMS git clone

  • rustc --print target-list

  • rustc --target=riscv64gc-unknown-none-elf --print target-features

  • rustc --target=riscv64gc-unknown-none-elf  --print target-cpus

  • Rust Platform Support

The sample instructions which follow build two executables using the same source code for the RTEMS configuration init.c and the Rust hello-world application lib.rs. Only the configuration as well as the compile and link commands differ for SPARC Leon3 and RISC-V 64 bit. The Rust application uses printk() from RTEMS to print text to the console.

After building the RTEMS BSP and installing Rust, the basic steps are:

  1. Compile the RTEMS configuration in init.c into an object file using the GNU C compiler from the RTEMS tool chain.

  2. Compile the Rust code containing main() into a static library using the Rust compiler.

  3. Link the static library with the Rust code, the RTEMS configuration and the RTEMS OS libraries together into one single executable.

  4. Finally run the executable on a simulator.

You can build the examples in a container. This is optional. If you prefer to follow these instructions directly on your machine simply skip the section Build a Container. Just make sure that you machine meets all prerequisites to build the RTEMS tools and install the Rust tools.

16.1.1. Build a Container

The container must be able to execute the RTEMS source builder and to install and run the Rust tools. In an empty directory of your choice create the following Dockerfile.

cat >Dockerfile <<"EOF"
# Dockerfile to build a container image to use Rust on top of RTEMS
FROM ubuntu:24.04
RUN apt-get update && \
    apt-get -y upgrade && \
    apt-get install -y \
        bison \
        build-essential \
        curl \
        flex \
        g++ \
        gdb \
        git \
        libncurses5-dev \
        ninja-build \
        pax \
        pkg-config \
        python3-dev \
        python-is-python3 \
        qemu-system-misc \
        texinfo \
        unzip \
        zlib1g-dev && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
RUN useradd -c "Rust Developer" -g "users" \
            -d "/home/ferris" --create-home "ferris" && \
    mkdir -p /opt/rtems && \
    chown ferris:users /opt/rtems && \
    runuser -u ferris echo \
            'export PATH=/opt/rtems/6/bin:${PATH}' \
            >>/home/ferris/.bashrc
USER ferris
WORKDIR /home/ferris
CMD ["/bin/bash"]
EOF

Podman is used in the shell commands below. If you prefer Docker simply replace podman through docker.

Build the container image rtems_rust, create and start a container with these commands:

podman build -t rtems_rust .
podman run -it --name=rusty_rtems rtems_rust bash

To follow the step-by-step instructions of the next sub-sections, simply execute them as user ferris in the container. Note that this container will not automatically be deleted on exit. The building of the RTEMS tools takes a while and you probably want to keep the container for further experiments.

16.1.2. Build the RTEMS Tools

In an empty directory of your choice, clone the RTEMS source builder git repository:

git clone https://gitlab.rtems.org/rtems/tools/rtems-source-builder.git rsb

Next build the RTEMS tools. In this example, you need tools for SPARC and RISC-V architectures. The source builder installs them in the prefix directory /opt/rtems/6. The directory /opt/rtems must exist and the user must have read and write access.

cd rsb/rtems
../source-builder/sb-set-builder --prefix /opt/rtems/6 \
    6/rtems-sparc \
    6/rtems-riscv
cd ../..

The tools will end up in /opt/rtems/6/bin and that directory should be part of the $PATH environment variable of the user. For example:

export PATH=/opt/rtems/6/bin:${PATH}

The following commands should work:

sparc-rtems6-gcc --version
riscv-rtems6-gcc --version

16.1.3. Build and Install the RTEMS BSPs

Clone the RTEMS git repository:

git clone https://gitlab.rtems.org/rtems/rtos/rtems.git

Create a config.ini file for the two BSPs for which your are going to build RTEMS:

cd rtems

cat >config.ini <<"EOF"
[sparc/leon3]
RTEMS_SMP = True
[riscv/rv64imafdc]
EOF

Build and install RTEMS:

./waf configure --prefix=/opt/rtems/6
./waf
./waf install

Run some RTEMS tests to make sure the installation and the emulators are working:

sparc-rtems6-sis -leon3 -nouartrx -r m 4 \
    build/sparc/leon3/testsuites/samples/hello.exe
sparc-rtems6-sis -leon3 -nouartrx -r m 4 \
    build/sparc/leon3/testsuites/samples/ticker.exe
qemu-system-riscv64 -M virt -nographic -bios \
    build/riscv/rv64imafdc/testsuites/samples/hello.exe
qemu-system-riscv64 -M virt -nographic -bios \
    build/riscv/rv64imafdc/testsuites/samples/ticker.exe

Finally, leave the git working tree:

cd ..

16.1.4. Install and Setup Rust Tools

Install Rust from the web-page with this command:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

At this point you must setup the environment variables:

source "$HOME/.cargo/env"

Check that rust is correctly setup:

rustup update
cargo --version

16.1.5. Setup a Rust Project and Create Sources

Write a simple RTEMS init.c to configure RTEMS in a new directory:

mkdir example-rust
cd example-rust

cat >init.c <<"EOF"
/*
 * Simple RTEMS configuration
 */

#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER

#define CONFIGURE_UNLIMITED_OBJECTS
#define CONFIGURE_UNIFIED_WORK_AREAS

#define CONFIGURE_RTEMS_INIT_TASKS_TABLE

#define CONFIGURE_INIT

#include <rtems/confdefs.h>
EOF

Create a new Rust project which produces a static linked library:

cargo new --lib --vcs=none hello-rtems
cat >>hello-rtems/Cargo.toml <<"EOF"

[lib]
crate-type = ["staticlib"]
EOF

Store the Rust application code:

cat >hello-rtems/src/lib.rs <<"EOF"
#![no_std]
#![no_main]

use core::fmt::Write;
use core::ffi::c_char;

extern "C" {
    fn printk(fmt: *const core::ffi::c_char, ...) -> core::ffi::c_int;
    fn rtems_panic(fmt: *const core::ffi::c_char, ...) -> !;
    fn rtems_shutdown_executive(fatal_code: u32);
}

/// Write text to the console using RTEMS `printk()` function
struct Console;

impl core::fmt::Write for Console {
    fn write_str(&mut self, message: &str) -> core::fmt::Result {
        const FORMAT_STR: &core::ffi::CStr = {
            let Ok(s) = core::ffi::CStr::from_bytes_with_nul(b"%.*s\0") else {
                panic!()
            };
            s
        };
        if message.len() != 0 {
            unsafe {
                printk(FORMAT_STR.as_ptr(), message.len() as core::ffi::c_int, message.as_ptr());
            }
        }
        Ok(())
    }
}

/// Our `Init()` calls `rust_main()` and handles errors
#[no_mangle]
pub extern "C" fn Init() {
    if let Err(e) = rust_main() {
        panic!("Main returned {:?}", e);
    }
    unsafe {
        rtems_shutdown_executive( 0 );
    }
}

/// This is the main function of this program
fn rust_main() -> Result<(), core::fmt::Error> {
    let mut console = Console;
    writeln!(console, "Hello from Rust")?;
    Ok(())
}

/// Handle panic by forwarding it to the `rtems_panic()` handler
#[panic_handler]
fn panic(panic: &core::panic::PanicInfo) -> ! {
    // The panic message can only be reached from libcore in unstable
    // (i.e. nightly builds). Print at least the location raising the panic.
    // See https://www.ralfj.de/blog/2019/11/25/how-to-panic-in-rust.html
    if let Some(location) = panic.location() {
        const FORMAT_STR: *const c_char = {
            const BYTES: &[u8] = b"Panic occurred at %.*s:%d:%d\n\0";
            BYTES.as_ptr().cast()
        };
        if location.file().len() != 0 {
            unsafe {
                rtems_panic(FORMAT_STR,
                    location.file().len() as core::ffi::c_int,
                    location.file().as_ptr(),
                    location.line() as core::ffi::c_int,
                    location.column() as core::ffi::c_int,
                );
            }
        }
    }

    // If there is no location, fall back to the basic.
    let message = "Panic occured!";
    const FORMAT_PTR: *const c_char = {
        const BYTES: &[u8] = b"%.*s\n\0";
        BYTES.as_ptr().cast()
    };
    unsafe {
       rtems_panic(FORMAT_PTR,
           message.len() as core::ffi::c_int,
           message.as_ptr());
    }
}
EOF

Create a configuration file for Cargo:

mkdir hello-rtems/.cargo

cat >hello-rtems/.cargo/config.toml <<"EOF"
[target.riscv64gc-unknown-none-elf]
# Either kind should work as a linker
linker = "riscv-rtems6-gcc"
# linker = "riscv-rtems6-clang"
rustflags = [
    # See `rustc --target=riscv64gc-unknown-none-elf  --print target-cpus`
    "-Ctarget-cpu=generic-rv64",
    # The linker is a gcc compatible C Compiler
    "-Clinker-flavor=gcc",
    # Pass these options to the linker
    "-Clink-arg=-march=rv64imafdc",
    "-Clink-arg=-mabi=lp64d",
    "-Clink-arg=-mcmodel=medany",
    # Rust needs libatomic.a to satisfy Rust's compiler-builtin library
    "-Clink-arg=-latomic",
]
runner = "qemu-system-riscv64 -M virt -nographic -bios"

# Target available in rust nightly from 2023-07-18
[target.sparc-unknown-none-elf]
# Either kind should work as a linker
linker = "sparc-rtems6-gcc"
# linker = "sparc-rtems6-clang"
rustflags = [
    # The target is LEON3
    "-Ctarget-cpu=leon3",
    # The linker is a gcc compatible C Compiler
    "-Clinker-flavor=gcc",
    # Pass these options to the linker
    "-Clink-arg=-mcpu=leon3",
    # Rust needs libatomic.a to satisfy Rust's compiler-builtin library
    "-Clink-arg=-latomic",
]
runner = "sparc-rtems6-sis -leon3 -nouartrx -r m 4"

[build]
target = ["riscv64gc-unknown-none-elf", "sparc-unknown-none-elf"]

[unstable]
build-std = ["core"]
EOF

16.1.6. Build and Run on RISC-V

First, download some additional files needed for this target:

rustup target add riscv64gc-unknown-none-elf

Compile the Rust source file into a static library:

cd hello-rtems
cargo build --target=riscv64gc-unknown-none-elf
cd ..

This should create hello-rtems/target/riscv64gc-unknown-none-elf/debug/libhello_rtems. a. Note that the project directory (hello-rtems) is written with a minus “-” while the library (libhello_rtems.a) is written with an underscore “_”.

Compile the RTEMS init.c file and link everything together into a single executable:

export PKG_CONFIG_RISCV=/opt/rtems/6/lib/pkgconfig/riscv-rtems6-rv64imafdc.pc

riscv-rtems6-gcc -Wall -Wextra -O2 -g \
    -fdata-sections -ffunction-sections \
    $(pkg-config --cflags ${PKG_CONFIG_RISCV}) init.c -c -o init_riscv.o

riscv-rtems6-gcc init_riscv.o \
  -Lhello-rtems/target/riscv64gc-unknown-none-elf/debug \
  -lhello_rtems \
  -ohello_rtems_riscv.exe \
  $(pkg-config --variable=ABI_FLAGS ${PKG_CONFIG_RISCV}) \
  $(pkg-config --libs ${PKG_CONFIG_RISCV})

This should produce the executable file hello_rtems_riscv.exe. Finally, run the executable on an emulator (qemu):

rtems-run --rtems-bsp=rv64imafdc hello_rtems_riscv.exe

The emulator run should produce the following output:

RTEMS Testing - Run, 6.0.not_released
 Command Line: /opt/rtems/6/bin/rtems-run --rtems-bsp=rv64imafdc hello_rtems_riscv.exe
 Host: Linux 0fa931a464ca 5.14.21-150500.55.62-default #1 SMP PREEMPT_DYNAMIC Tue May 7 11:55:30 UTC 2024 (66dfe0d) x86_64
 Python: 3.12.3 (main, Apr 10 2024, 05:33:47) [GCC 13.2.0]
Host: Linux-5.14.21-150500.55.62-default-x86_64-with-glibc2.39 (Linux 0fa931a464ca 5.14.21-150500.55.62-default #1 SMP PREEMPT_DYNAMIC Tue May 7 11:55:30 UTC 2024 (66dfe0d) x86_64 x86_64)
Hello from Rust

[ RTEMS shutdown ]
RTEMS version: 6.0.0.e74cea4172ee8564bef5f8500c5dd07512257d99
RTEMS tools: 13.2.1 20240502 (RTEMS 6, RSB 484c7a4095fe8cebba0320b58bf9477f2f40b1b6, Newlib 730703b)
executing thread ID: 0x0a010001
executing thread name: UI1
Run time     : 0:00:00.254684

Version numbers may be different in your output.

16.1.7. Build and Run on SPARC

You need to use the Rust nightly build because the support for Gaisler LEON3/4/5 was added in July 2023 and is not yet available in stable Rust:

rustup toolchain add nightly
rustup component add rust-src --toolchain=nightly

Compile the Rust source file into a static library:

cd hello-rtems
cargo +nightly build --target=sparc-unknown-none-elf
cd ..

It should create hello-rtems/target/sparc-unknown-none-elf/debug/libhello_rtems.a.

Compile the RTEMS init.c file and link everything together into an executable:

export PKG_CONFIG_SPARC=/opt/rtems/6/lib/pkgconfig/sparc-rtems6-leon3.pc

sparc-rtems6-gcc -Wall -Wextra -O2 -g \
    -fdata-sections -ffunction-sections \
    $(pkg-config --cflags ${PKG_CONFIG_SPARC}) init.c -c -o init_sparc.o

sparc-rtems6-gcc init_sparc.o \
    -qnolinkcmds -T linkcmds.leon3 \
    -Lhello-rtems/target/sparc-unknown-none-elf/debug \
    -lhello_rtems \
    -ohello_rtems_sparc.exe \
    $(pkg-config --libs ${PKG_CONFIG_SPARC})

This should produce the executable file hello_rtems_sparc.exe. Finally, run the executable on an emulator (sis):

rtems-run --rtems-bsp=leon3-sis hello_rtems_sparc.exe

The emulator run should produce the following output:

RTEMS Testing - Run, 6.0.not_released
 Command Line: /opt/rtems/6/bin/rtems-run --rtems-bsp=leon3-sis hello_rtems_sparc.exe
 Host: Linux 0fa931a464ca 5.14.21-150500.55.62-default #1 SMP PREEMPT_DYNAMIC Tue May 7 11:55:30 UTC 2024 (66dfe0d) x86_64
 Python: 3.12.3 (main, Apr 10 2024, 05:33:47) [GCC 13.2.0]
Host: Linux-5.14.21-150500.55.62-default-x86_64-with-glibc2.39 (Linux 0fa931a464ca 5.14.21-150500.55.62-default #1 SMP PREEMPT_DYNAMIC Tue May 7 11:55:30 UTC 2024 (66dfe0d) x86_64 x86_64)

 SIS - SPARC/RISCV instruction simulator 2.30,  copyright Jiri Gaisler 2020
 Bug-reports to jiri@gaisler.se

 LEON3 emulation enabled, 4 cpus online, delta 50 clocks

 Loaded hello_rtems_sparc.exe, entry 0x40000000
Hello from Rust
cpu 0 in error mode (tt = 0x80)
   217550  40018f60:  91d02000   ta  0x0
Run time     : 0:00:00.253700

Version numbers may be different in your output.