Rust on RTEMS

Frank Kühndel frank.kuehndel at embedded-brains.de
Fri Dec 22 13:40:23 UTC 2023


Hello Joel,

On 12/20/23 23:37, Joel Sherrill wrote:
> Karel Gardas posted in February that he has Rust on RTEMS on an arm.

This is one email from that thread:
https://lists.rtems.org/pipermail/devel/2023-March/074532.html
> 
> Frank. Are there instructions on building the tools chain somewhere?

I attach a file with instructions which I just compiled from my notices 
(my Christmas gift to you ;-) ). I have not tested these instructions 
(again) but I hope they will give you a hint how it did it. I attach 
them as file to avoid my mail program inserts line breaks.

In case you or anyone else gives these instructions a try, I will be 
happy for all feedback. The documentation I want to write for the User 
Manual (if no one else does it before me or has a better approach) would 
closely follow the steps in the attached file.
> 
> And is Jan Sommer part of the DLR Rust on RTEMS project?

I met Jan at ESA ADCSS 2023 at Nordwijk. I prefer Jan answers your 
question as I do not want to make anything public without prior 
agreement from DLR.

Greetings,
fk

> 
> --joel
> 
> On Wed, Dec 20, 2023, 3:59 PM Frank Kühndel <
> frank.kuehndel at embedded-brains.de> wrote:
> 
>> Hello Dwaine,
>>
>> On 12/20/23 20:41, Molock, Dwaine S. (GSFC-5820) wrote:
>>   > Hello,
>>   >
>>   > Has anyone been able to execute Rust on RTEMS?
>>
>> Yes – to use RTEMS from within a Rust application, with
>>
>> #![no_std]
>> #![no_main]
>>
>>   >
>>   > If so, is there a how to guide and what architecture and development
>> hardware was used?
>>
>> I have not yet found time to write a documentation despite I want to do
>> so. Sorry for this. Ferrous Systems first extended Rust to run on
>> Gaisler SPARC bare metal and then they figured it works with RTEMS 5
>> from Gaisler, too. Their documentation is here:
>> https://github.com/ferrous-systems/sparc-experiments/
>>
>> I did run my examples only on simulators. I tried two architectures with
>> RTEMS 6: Leon3 and RISC-V.
>>
>> Greetings,
>> Frank
>>
>> --
>> embedded brains GmbH & Co. KG
>> Herr Frank KÜHNDEL
>> Dornierstr. 4
>> 82178 Puchheim
>> Germany
>> email:frank.kuehndel at embedded-brains.de
>> phone:  +49-89-18 94 741 - 23
>> mobile: +49-176-15 22 06 - 11
>>
>> Registergericht: Amtsgericht München
>> Registernummer: HRA 117265
>> Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
>> Unsere Datenschutzerklärung finden Sie hier:
>> https://embedded-brains.de/datenschutzerklaerung/
>> _______________________________________________
>> users mailing list
>> users at rtems.org
>> http://lists.rtems.org/mailman/listinfo/users
> 

-- 
embedded brains GmbH & Co. KG
Herr Frank KÜHNDEL
Dornierstr. 4
82178 Puchheim
Germany
email: frank.kuehndel at embedded-brains.de
phone:  +49-89-18 94 741 - 23
mobile: +49-176-15 22 06 - 11

Registergericht: Amtsgericht München
Registernummer: HRA 117265
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/
-------------- next part --------------
Use Rust with RTEMS
===================

2023-Dec-22 Frank Kühndel, embedded brains GmbH & Co. KG

This text describes how I used Rust with RTEMS.
This is from my notices, I have not checked these instructions
again and they come without any grantees.
I intent to write a (better) how-to for the RTEMS User Manual
as soon as I find some time for it.

These instructions are for two Hello-World programs, one for
SPARC Leon3 and the other for RISC-V. In both cases, the
Rust program use `printk()` from RTEMS to print text to the
console.

The basic steps are these:
  * Compile the Rust code containing `main()` into a
    static library using the Rust compiler.
  * Compile the RTEMS configuration in `Init.c` into an object
    file using the gcc from the RTEMS tool chain.
  * Finally link the static library with the Rust code,
    the RTEMS init configuration and the RTEMS OS
    together into one single executable.
  * Run the executable on a simulator.

Note, the Rust SPARC support for Leon3/4/5 is rather new and
requires the nightly build of the Rust compiler.

My whole work was triggered and inspired by a presentation
made by Jonathan Pallant from Ferrous Systems GmbH on
ESA Software Product Assurance Workshop 2023:
"Using Rust for mission critical systems"
https://www.cosmos.esa.int/documents/10939403/13948935/1_Jonathan_Pallant_Using+Rust+for+Mission+Critical+Systems+v5.pdf/12be67e0-de49-3baf-4c00-d797784c6b6a?t=1695808956535
Moreover, he created https://github.com/ferrous-systems/sparc-experiments/

Essentially, Ferrous Systems has certified the open source Rust
compiler for ISO 26262 (ASIL D) as a product named ferrocene.

There is one known Bug in the examples below: `panic()`
always prints "panic occured!" instead of the panic message.
If you have a fix, I will be happy to use it for the documentation.

Setup RTEMS and Rust
====================

I used an OpenSUSE Leap 15.5 container to have clean environment.
I installed the patterns devel_basis and devel_C_C++ as well as
the packages gzip, python3 and qemu-extra. To use Python 3 as
`python` OpenSUSE requires this command:

```
# update-alternatives --install /usr/bin/python python /usr/bin/python3 20
```

Moreover, I created a user "ferris" to avoid working as root:

```
# useradd -c "Build User" -g "users"  -d "/home/ferris" --create-home "ferris"
```

Install RTEMS Tools
-------------------

I needed to install the RTEMS 6 tool chain for "sparc" and "riscv"
using the RTEMS source builder. I assume you how know how to
do this. I installed to prefix `/opt/rtems/6` and added it to the
PATH environment variable:

```
# su ferris
> echo "export PATH=/opt/rtems/6/bin:${PATH}" >>~/.bashrc
```

Install and Setup Rust
----------------------

I installed rust from the web-page:

```
# su ferris
> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /home/ferris/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory is located at:

  /home/ferris/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /home/ferris/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /home/ferris/.profile
  /home/ferris/.bashrc

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.
```

At this point it is the best to logout and login again.

```
# su ferris
> rustup update
> cargo --version
cargo 1.73.0 (9c4383fb5 2023-08-26)
> sparc-rtems6-gcc --version
sparc-rtems6-gcc (GCC) 12.3.1 20231012 (RTEMS 6, RSB 103006fc0bdc6eff7760cb74f15bc16ac4238087-modified, Newlib fbc5496)
[...]
```

Install RTEMS
-------------

```
# su ferris
> cd
> git clone git://git.rtems.org/rtems.git
> cd rtems
> cat >config.ini <<"EOF"
[sparc/leon3]
RTEMS_SMP = True 
[riscv/rv64imafdc] 
EOF
> ./waf configure --prefix=/opt/rtems/6
> ./waf
> ./waf install
```

I did run some RTEMS tests to make sure the installation
and the simulators 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
```

Rust Hello World with RTEMS on SPARC
====================================

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

```
# su ferris
> cd
> rustup toolchain add nightly
> rustup component add rust-src --toolchain=nightly
```

I created a simple RTEMS `init.c` to configure RTEMS in a
new directory:

```
# su ferris
> cd
> 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
```

I created a new Rust project (still in `~/example-rust`):

```
> cargo new --lib --vcs=none rust-sparc
> cd rust-sparc
> sed -i '/^#/ a \\n[lib]\ncrate-type = ["staticlib"]' Cargo.toml
```

I created the Rust application code (still in
`~/example-rust/rust-sparc`):

```
> cat >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, ...) -> !;
}

/// 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);
    } 
}

/// 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) -> ! {
    let mut message = "panic occured!";
    if let Some(s) = panic.payload().downcast_ref::<&str>() {
        message = *s;
    }
    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
```

I created a configuration file for Cargo (still in
`~/example-rust/rust-sparc`):

```
> mkdir .cargo
> cat >.cargo/config.toml <<"EOF"
# 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 = ["sparc-unknown-none-elf"]

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

Build and Run on SPARC
----------------------

I compiled the Rust source file into a static library (still in
`~/example-rust/rust-sparc`):

```
> cargo +nightly build --target=sparc-unknown-none-elf
```

It should create `~/example-rust/rust-sparc/target/sparc-unknown-none-elf/debug/librust_sparc.a`

I compiled the RTEMS `init.c` file and linked everything
together into an executable:

```
> cd ~/example-rust

> 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 -qnolinkcmds -T linkcmds.leon3 init_sparc.o -Lrust-sparc/target/sparc-unknown-none-elf/debug -lrust_sparc -orust-sparc.exe $(pkg-config --libs ${PKG_CONFIG_SPARC})
```

Then I was able to run the executable (still in
`~/example-rust`):

```
> rtems-run --rtems-bsp=leon3-sis rust-sparc.exe
```

Rust Hello World with RTEMS on RISC-V
=====================================

This is very similar to the SPARC variant above with tiny changes.
Moreover, one does not need a nightly build of the Rust tools.

I reused the directory `~/example-rust` and the file RTEMS
`init.c` which I created at the beginning of the SPARC
variant above. Next, I created a new Rust project:

```
# su ferris
> cd ~/example-rust
> cargo new --lib --vcs=none rust-riscv
> cd rust-riscv
> sed -i '/^#/ a \\n[lib]\ncrate-type = ["staticlib"]' Cargo.toml
```

I created the Rust application code (still in
`~/example-rust/rust-riscv`):

```
> cat >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) -> ! {
    let mut message = "panic occured!";
    if let Some(s) = panic.payload().downcast_ref::<&str>() {
        message = *s;
    }
    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
```

I created a configuration file for Cargo (still in
`~/example-rust/rust-riscv`):

```
> mkdir .cargo
> cat >.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 "

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

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

As a side node, to get an idea what hardware Rust supports
and some more information try these (or similar) commands:

  * `rustc --print target-list`
  * `rustc --target=riscv64gc-unknown-none-elf --print target-features`
  * `rustc --target=riscv64gc-unknown-none-elf  --print target-cpus`

Build and Run on RISC-V
-----------------------

First, I needed to download some additional files for this target:

```
> rustup target add riscv64gc-unknown-none-elf
```

I compiled the Rust source file into a static library (still in
`~/example-rust/rust-riscv`):

```
> cargo build --target=riscv64gc-unknown-none-elf
```

It should create
`target/riscv64gc-unknown-none-elf/debug/librust_riscv.a`

I compiled the RTEMS `init.c` file and linked everything
together into an executable:

```
> cd ~/example-rust

> 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 -Lrust-riscv/target/riscv64gc-unknown-none-elf/debug -lrust_riscv -orust_riscv.exe $(pkg-config --variable=ABI_FLAGS ${PKG_CONFIG_RISCV}) $(pkg-config --libs ${PKG_CONFIG_RISCV})
```

Then I was able to run the executable (still in
`~/example-rust`):

```
> rtems-run --rtems-bsp=rv64imafdc rust_riscv.exe
```

-- 
embedded brains GmbH & Co. KG
Herr Frank KÜHNDEL
Dornierstr. 4
82178 Puchheim
Germany
email: frank.kuehndel at embedded-brains.de
phone:  +49-89-18 94 741 - 23
mobile: +49-176-15 22 06 - 11

Registergericht: Amtsgericht München
Registernummer: HRA 117265
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/


More information about the users mailing list