Skip to content

Commit ba4a7cf

Browse files
committed
Initial support for Rust on Zephyr
This is based off of the initial support merged into the [collab-rust](https://github.com/zephyrproject-rtos/zephyr/tree/collab-rust) branch on the main Zephyr branch, which includes two PRs, one adding initial support and the other adding bindgen. Currently the docs have been moved into the README.rst in the module, and the same is placed here, although twister doesn't currently run samples that are in modules. Signed-off-by: David Brown <[email protected]>
0 parents  commit ba4a7cf

22 files changed

+957
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Normal west builds will place the Rust target directory under the build directory. However,
2+
# sometimes IDEs and such will litter these target directories as well.
3+
target/
4+
5+
# None of this code is an application, so don't capture any of the dependencies in the lock file.
6+
Cargo.lock
7+
8+
# Cargo encourages a .cargo/config.toml file to symlink to a generated file. Don't save these.
9+
.cargo/

CMakeLists.txt

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
# Rust make support
4+
5+
set(RUST_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
6+
7+
# Zephyr targets are defined through Kconfig. We need to map these to
8+
# an appropriate llvm target triple. This sets `RUST_TARGET` in the
9+
# parent scope, or an error if the target is not yet supported by
10+
# Rust.
11+
function(_rust_map_target)
12+
# Map Zephyr targets to LLVM targets.
13+
if(CONFIG_CPU_CORTEX_M)
14+
if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1)
15+
set(RUST_TARGET "thumbv6m-none-eabi" PARENT_SCOPE)
16+
elseif(CONFIG_CPU_CORTEX_M3)
17+
set(RUST_TARGET "thumbv7m-none-eabi" PARENT_SCOPE)
18+
elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7)
19+
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
20+
set(RUST_TARGET "thumbv7em-none-eabihf" PARENT_SCOPE)
21+
else()
22+
set(RUST_TARGET "thumbv7em-none-eabi" PARENT_SCOPE)
23+
endif()
24+
elseif(CONFIG_CPU_CORTEX_M23)
25+
set(RUST_TARGET "thumbv8m.base-none-eabi" PARENT_SCOPE)
26+
elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55)
27+
# Not a typo, Zephyr, uses ARMV7_M_ARMV8_M_FP to select the FP even on v8m.
28+
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
29+
set(RUST_TARGET "thumbv8m.main-none-eabihf" PARENT_SCOPE)
30+
else()
31+
set(RUST_TARGET "thumbv8m.main-none-eabi" PARENT_SCOPE)
32+
endif()
33+
34+
# Todo: The M55 is thumbv8.1m.main-none-eabi, which can be added when Rust
35+
# gain support for this target.
36+
else()
37+
message(FATAL_ERROR "Unknown Cortex-M target.")
38+
endif()
39+
elseif(CONFIG_RISCV)
40+
if(CONFIG_RISCV_ISA_RV64I)
41+
# TODO: Should fail if the extensions don't match.
42+
set(RUST_TARGET "riscv64imac-unknown-none-elf" PARENT_SCOPE)
43+
elseif(CONFIG_RISCV_ISA_RV32I)
44+
# TODO: We have multiple choices, try to pick the best.
45+
set(RUST_TARGET "riscv32i-unknown-none-elf" PARENT_SCOPE)
46+
else()
47+
message(FATAL_ERROR "Rust: Unsupported riscv ISA")
48+
endif()
49+
else()
50+
message(FATAL_ERROR "Rust: Add support for other target")
51+
endif()
52+
endfunction()
53+
54+
function(get_include_dirs target dirs)
55+
get_target_property(include_dirs ${target} INTERFACE_INCLUDE_DIRECTORIES)
56+
if(include_dirs)
57+
set(${dirs} ${include_dirs} PARENT_SCOPE)
58+
else()
59+
set(${dirs} "" PARENT_SCOPE)
60+
endif()
61+
endfunction()
62+
63+
function(rust_cargo_application)
64+
# For now, hard-code the Zephyr crate directly here. Once we have
65+
# more than one crate, these should be added by the modules
66+
# themselves.
67+
set(LIB_RUST_CRATES zephyr zephyr-build zephyr-sys)
68+
69+
get_include_dirs(zephyr_interface include_dirs)
70+
71+
get_property(include_defines TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_DEFINITIONS)
72+
message(STATUS "Includes: ${include_dirs}")
73+
message(STATUS "Defines: ${include_defines}")
74+
75+
_rust_map_target()
76+
message(STATUS "Building Rust llvm target ${RUST_TARGET}")
77+
78+
# TODO: Make sure RUSTFLAGS is not set.
79+
80+
# TODO: Let this be configurable, or based on Kconfig debug?
81+
set(RUST_BUILD_TYPE debug)
82+
set(BUILD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}")
83+
84+
set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust/target")
85+
set(RUST_LIBRARY "${CARGO_TARGET_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}/librustapp.a")
86+
set(SAMPLE_CARGO_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/rust/sample-cargo-config.toml")
87+
88+
# The generated C binding wrappers. These are bindgen-generated wrappers for the inline functions
89+
# within Zephyr.
90+
set(WRAPPER_FILE "${CMAKE_CURRENT_BINARY_DIR}/rust/wrapper.c")
91+
92+
# To get cmake to always invoke Cargo requires a bit of a trick. We make the output of the
93+
# command a file that never gets created. This will cause cmake to always rerun cargo. We
94+
# add the actual library as a BYPRODUCTS list of this command, otherwise, the first time the
95+
# link will fail because it doesn't think it knows how to build the library. This will also
96+
# cause the relink when the cargo command actually does rebuild the rust code.
97+
set(DUMMY_FILE "${CMAKE_BINARY_DIR}/always-run-cargo.dummy")
98+
99+
# For each module in zephyr-rs, add entry both to the .cargo/config template and for the
100+
# command line, since either invocation will need to see these.
101+
set(command_paths)
102+
set(config_paths "")
103+
message(STATUS "Processing crates: ${ZEPHYR_RS_MODULES}")
104+
foreach(module IN LISTS LIB_RUST_CRATES)
105+
message(STATUS "module: ${module}")
106+
set(config_paths
107+
"${config_paths}\
108+
${module}.path = \"$CACHE{RUST_MODULE_DIR}/${module}\"
109+
")
110+
list(APPEND command_paths
111+
"--config"
112+
"patch.crates-io.${module}.path=\\\"$CACHE{RUST_MODULE_DIR}/${module}\\\""
113+
)
114+
endforeach()
115+
116+
# Write out a cargo config file that can be copied into `.cargo/config.toml` (or made a
117+
# symlink) in the source directory to allow various IDE tools and such to work. The build we
118+
# invoke will override these settings, in case they are out of date. Everything set here
119+
# should match the arguments given to the cargo build command below.
120+
file(WRITE ${SAMPLE_CARGO_CONFIG} "
121+
# This is a generated sample .cargo/config.toml file from the Zephyr build.
122+
# At the time of generation, this represented the settings needed to allow
123+
# a `cargo build` command to compile the rust code using the current Zephyr build.
124+
# If any settings in the Zephyr build change, this could become out of date.
125+
[build]
126+
target = \"${RUST_TARGET}\"
127+
target-dir = \"${CARGO_TARGET_DIR}\"
128+
129+
[env]
130+
BUILD_DIR = \"${CMAKE_CURRENT_BINARY_DIR}\"
131+
DOTCONFIG = \"${DOTCONFIG}\"
132+
ZEPHYR_DTS = \"${ZEPHYR_DTS}\"
133+
INCLUDE_DIRS = \"${include_dirs}\"
134+
INCLUDE_DEFINES = \"${include_defines}\"
135+
WRAPPER_FILE = \"${WRAPPER_FILE}\"
136+
137+
[patch.crates-io]
138+
${config_paths}
139+
")
140+
141+
# The library is built by invoking Cargo.
142+
add_custom_command(
143+
OUTPUT ${DUMMY_FILE}
144+
BYPRODUCTS ${RUST_LIBRARY} ${WRAPPER_FILE}
145+
COMMAND
146+
${CMAKE_EXECUTABLE}
147+
env BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
148+
DOTCONFIG=${DOTCONFIG}
149+
ZEPHYR_DTS=${ZEPHYR_DTS}
150+
INCLUDE_DIRS="${include_dirs}"
151+
INCLUDE_DEFINES="${include_defines}"
152+
WRAPPER_FILE="${WRAPPER_FILE}"
153+
cargo build
154+
# TODO: release flag if release build
155+
# --release
156+
157+
# Override the features according to the shield given. For a general case,
158+
# this will need to come from a variable or argument.
159+
# TODO: This needs to be passed in.
160+
# --no-default-features
161+
# --features ${SHIELD_FEATURE}
162+
163+
# Set a replacement so that packages can just use `zephyr-sys` as a package
164+
# name to find it.
165+
${command_paths}
166+
--target ${RUST_TARGET}
167+
--target-dir ${CARGO_TARGET_DIR}
168+
COMMENT "Building Rust application"
169+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
170+
)
171+
172+
# Be sure we don't try building this until all of the generated headers have been generated.
173+
add_custom_target(librustapp ALL
174+
DEPENDS ${DUMMY_FILE}
175+
# The variables, defined at the top level, don't seem to be accessible here.
176+
syscall_list_h_target
177+
driver_validation_h_target
178+
kobj_types_h_target
179+
)
180+
181+
target_link_libraries(app PUBLIC -Wl,--allow-multiple-definition ${RUST_LIBRARY})
182+
add_dependencies(app librustapp)
183+
184+
# Presumably, Rust applications will have no C source files, but cmake will require them.
185+
# Add an empty file so that this will build. The main will come from the rust library.
186+
target_sources(app PRIVATE $CACHE{RUST_MODULE_DIR}/main.c ${WRAPPER_FILE})
187+
endfunction()

Kconfig

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Rust configuration options
2+
#
3+
# Copyright (c) 2024 Linaro LTD
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
menu "Rust Language Support"
7+
8+
config RUST_SUPPORTED
9+
bool
10+
default y if (CPU_CORTEX_M || \
11+
(RISCV && !RISCV_ISA_RV32E && !RISCV_ISA_RV128I))
12+
help
13+
Selected for platforms that have support for Rust.
14+
15+
config RUST
16+
bool "Rust support for the application"
17+
depends on RUST_SUPPORTED
18+
select EXPERIMENTAL
19+
help
20+
This option enables the use of applications written in Rust.
21+
22+
endmenu

README.rst

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
.. _language_rust:
2+
3+
Rust Language Support
4+
#####################
5+
6+
Rust is a systems programming language focused on safety, speed, and concurrency. Designed to
7+
prevent common programming errors such as null pointer dereferencing and buffer overflows, Rust
8+
emphasizes memory safety without sacrificing performance. Its powerful type system and ownership
9+
model ensure thread-safe programming, making it an ideal choice for developing reliable and
10+
efficient low-level code. Rust's expressive syntax and modern features make it a robust alternative
11+
for developers working on embedded systems, operating systems, and other performance-critical
12+
applications.
13+
14+
.. toctree::
15+
:maxdepth: 2
16+
17+
docs/bindings.rst
18+
19+
Enabling Rust Support
20+
*********************
21+
22+
Currently, Zephyr supports applications written in Rust and C. The enable Rust support, you must
23+
select the :kconfig:option:`CONFIG_RUST` in the application configuration file.
24+
25+
The rust toolchain is separate from the rest of the Zephyr SDK. It is recommended to use the
26+
`rustup`_ tool to install the rust toolchain. In addition to the base compiler, you will need to
27+
install core libraries for the target(s) you wish to compile on. It is easiest to determine what
28+
needs to be installed by trying a build. The diagnostics from the rust compilation will indicate
29+
the rust command needed to install the appropriate target support:
30+
31+
.. _rustup: https://rustup.rs/
32+
33+
.. code-block:: console
34+
35+
$ west build ...
36+
...
37+
error[E0463]: can't find crate for `core`
38+
|
39+
= note: the `thumbv7m-none-eabi` target may not be installed
40+
= help: consider downloading the target with `rustup target add thumbv7m-none-eabi`
41+
42+
In this case, the given ``rustup`` command will install the needed target support. The target
43+
needed will depend on both the board selected, as well as certain configuration choices (such as
44+
whether floating point is enabled).
45+
46+
Writing a Rust Application
47+
**************************
48+
49+
See :zephyr_file:`samples/rust` for examples of an application.
50+
51+
CMake files
52+
-----------
53+
54+
The application should contain a :file:`CMakeLists.txt`, similar to the following:
55+
56+
.. code-block:: cmake
57+
58+
cmake_minimum_required(VERSION 3.20.0)
59+
60+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
61+
project(hello_world)
62+
63+
rust_cargo_application()
64+
65+
Cargo files
66+
-----------
67+
68+
Rust applications are built with Cargo. The Zephyr build system will configure and invoke cargo in
69+
your application directory, with options set so that it can find the Zephyr support libraries, and
70+
that the output will be contained within the Zephyr build directory.
71+
72+
The :file:`Cargo.toml` will need to have a ``[lib]`` section that sets ``crate-type =
73+
["staticlib"]``, and will need to include ``zephyr = "0.1.0"`` as a dependency. You can use
74+
crates.io and the Crate ecosystem to include any other dependencies you need. Just make sure that
75+
you use crates that support building with no-std.
76+
77+
Application
78+
-----------
79+
80+
The application source itself should live in :file:`src/lib.rs`. A few things are needed. A minimal
81+
file would be:
82+
83+
.. code-block:: rust
84+
85+
#![no_std]
86+
87+
extern crate zephyr;
88+
89+
#[no_mangle]
90+
extern "C" fn rust_main() {
91+
}
92+
93+
The ``no_std`` declaration is needed to prevent the code from referencing the ``std`` library. The
94+
extern reference will cause the zephyr crate to be brought in, even if nothing from it is used.
95+
Practically, any meaningful Rust application on Zephyr will use something from this crate, and this
96+
line is not necessary. Lastly, the main declaration exports the main symbol so that it can be
97+
called by C code. The build ``rust_cargo_application()`` cmake function will include a small C file
98+
that will call into this from the C main function.
99+
100+
Zephyr Functionality
101+
********************
102+
103+
The bindings to Zephyr for Rust are under development, and are currently rather minimalistic.
104+
105+
Bool Kconfig settings
106+
---------------------
107+
108+
Boolean Kconfig settings can be used from within Rust code. Due to design constraints by the Rust
109+
language, settings that affect compilation must be determined before the build is made. In order to
110+
use this in your application, you will need to use the ``zephyr-build`` crate, provided, to make
111+
these symbols available.
112+
113+
To your ``Cargo.toml`` file, add the following:
114+
115+
.. code-block:: toml
116+
117+
[build-dependencies]
118+
zephyr-build = "0.1.0"
119+
120+
Then, you will need a ``build.rs`` file to call the support function. The following will work:
121+
122+
.. code-block:: rust
123+
124+
fn main() {
125+
zephyr_build::export_bool_kconfig();
126+
}
127+
128+
At this point, it will be possible to use the ``cfg`` directive in Rust on boolean Kconfig values.
129+
For example:
130+
131+
.. code-block:: rust
132+
133+
#[cfg(CONFIG_SCHED_DUMB)]
134+
one_declaration;
135+
136+
#[cfg(not(CONFIG_SCHED_DUMB))]
137+
other_declaration;
138+
139+
Other Kconfig settings
140+
----------------------
141+
142+
All bool, numeric and string Kconfig settings are accessible from the ``zephyr::kconfig`` module.
143+
For example:
144+
145+
.. code-block:: rust
146+
147+
let ceiling = zephyr::kconfig::CONFIG_PRIORITY_CEILING - 1;
148+
149+
Other functionality
150+
-------------------
151+
152+
Access to other functionality within zephyr is a work-in-progress, and this document will be updated
153+
as that is done.

0 commit comments

Comments
 (0)