diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 2d9f4d901bd8..7f412c5a1f4f 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -3039,6 +3039,17 @@ Retention: labels: - "area: Retention" +Rust: + status: maintained + maintainers: + - d3zd3z + files: + - cmake/modules/rust.cmake + - lib/rust/ + - samples/rust/ + labels: + - "area: Rust" + Samples: status: maintained maintainers: diff --git a/cmake/modules/rust.cmake b/cmake/modules/rust.cmake new file mode 100644 index 000000000000..6e24f1b197f5 --- /dev/null +++ b/cmake/modules/rust.cmake @@ -0,0 +1,221 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Rust make support + +# Zephyr targets are defined through Kconfig. We need to map these to +# an appropriate llbm target triple. This sets `RUST_TARGET` in the +# parent scope, or an error if the target is not yet supported by +# Rust. +function(_rust_map_target) + # Map Zephyr targets to LLVM targets. + if(CONFIG_CPU_CORTEX_M) + if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1) + set(RUST_TARGET "thumbv6m-none-eabi" PARENT_SCOPE) + elseif(CONFIG_CPU_CORTEX_M3) + set(RUST_TARGET "thumbv7m-none-eabi" PARENT_SCOPE) + elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7) + if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI) + set(RUST_TARGET "thumbv7em-none-eabihf" PARENT_SCOPE) + else() + set(RUST_TARGET "thumbv7em-none-eabi" PARENT_SCOPE) + endif() + elseif(CONFIG_CPU_CORTEX_M23) + set(RUST_TARGET "thumbv8m.base-none-eabi" PARENT_SCOPE) + elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55) + # Not a typo, Zephyr, uses ARMV7_M_ARMV8_M_FP to select the FP even on v8m. + if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI) + set(RUST_TARGET "thumbv8m.main-none-eabihf" PARENT_SCOPE) + else() + set(RUST_TARGET "thumbv8m.main-none-eabi" PARENT_SCOPE) + endif() + + # Todo: The M55 is thumbv8.1m.main-none-eabi, which can be added when Rust + # gain support for this target. + else() + message(FATAL_ERROR "Unknown Cortex-M target.") + endif() + else() + message(FATAL_ERROR "Rust: Add support for other target") + endif() +endfunction() + +function(_generate_clang_args BINDGEN_CLANG_ARGS) + # Get compiler arguments from Zephyr + zephyr_get_system_include_directories_for_lang(C system_includes) + zephyr_get_include_directories_for_lang(C includes) + zephyr_get_compile_definitions_for_lang(C definitions) + + # -imacros are needed but are part of zephyr_get_compile_options_for_lang() where many + # things are not supported by Clang. Maybe there is a better way than hard coding. + set(options "-imacros${AUTOCONF_H}") + + if(CONFIG_ENFORCE_ZEPHYR_STDINT) + list(APPEND options "-imacros${ZEPHYR_BASE}/include/zephyr/toolchain/zephyr_stdint.h") + endif() + + # Determine standard include directories of compiler. + # I hope someone knows a nicer way of doing this. + file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/empty.c) + + execute_process( + COMMAND ${CMAKE_C_COMPILER} -E -Wp,-v ${CMAKE_CURRENT_BINARY_DIR}/empty.c + OUTPUT_QUIET + ERROR_VARIABLE output + COMMAND_ERROR_IS_FATAL ANY + ) + + set(standard_includes "-nostdinc") + if(output MATCHES "#include <\.\.\.> search starts here:\n(.*)\nEnd of search list\.") + string(REGEX MATCHALL "[^ \n]+" paths "${CMAKE_MATCH_1}") + foreach(path ${paths}) + get_filename_component(path ${path} ABSOLUTE) + list(APPEND standard_includes "-isystem${path}") + endforeach() + else() + message(WARNING "Unable to determine compiler standard include directories.") + endif() + + # Not sure if a proper target should be provided as well to generate the correct bindings. + + # Generate file containing arguments for Clang. Note that the file is generated after the + # CMake configure stage as the variables contain generator expressions which cannot be + # evaluated right now. + file( + GENERATE + OUTPUT ${BINDGEN_CLANG_ARGS} + CONTENT "${standard_includes};${system_includes};${includes};${definitions};${options}" + ) +endfunction() + +function(_generate_rust_dts RUST_DTS) + execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/dts/gen_dts_rust.py + --edt-pickle ${CMAKE_BINARY_DIR}/zephyr/edt.pickle + --rust-out ${RUST_DTS} + COMMAND_ERROR_IS_FATAL ANY + ) + + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GEN_DTS_RUST_SCRIPT}) +endfunction() + +function(rust_cargo_application) + # For now, hard-code the Zephyr crate directly here. Once we have + # more than one crate, these should be added by the modules + # themselves. + set(LIB_RUST_CRATES zephyr zephyr-build zephyr-sys) + + _rust_map_target() + message(STATUS "Building Rust llvm target ${RUST_TARGET}") + + # TODO: Make sure RUSTFLAGS is not set. + + # TODO: Let this be configurable, or based on Kconfig debug? + set(RUST_BUILD_TYPE debug) + set(BUILD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}") + + set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust/target") + set(RUST_LIBRARY "${CARGO_TARGET_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}/librustapp.a") + set(SAMPLE_CARGO_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/rust/sample-cargo-config.toml") + + set(RUST_DTS "${CMAKE_CURRENT_BINARY_DIR}/rust/dts.rs") + set(BINDGEN_CLANG_ARGS "${CMAKE_CURRENT_BINARY_DIR}/rust/clang_args.txt") + set(BINDGEN_WRAP_STATIC_FNS "${CMAKE_CURRENT_BINARY_DIR}/rust/wrap_static_fns.c") + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rust") + + _generate_clang_args(${BINDGEN_CLANG_ARGS}) + _generate_rust_dts(${RUST_DTS}) + + # To get cmake to always invoke Cargo requires a bit of a trick. We make the output of the + # command a file that never gets created. This will cause cmake to always rerun cargo. We + # add the actual library as a BYPRODUCTS list of this command, otherwise, the first time the + # link will fail because it doesn't think it knows how to build the library. This will also + # cause the relink when the cargo command actually does rebuild the rust code. + set(DUMMY_FILE "${CMAKE_BINARY_DIR}/always-run-cargo.dummy") + + # For each module in zephyr-rs, add entry both to the .cargo/config template and for the + # command line, since either invocation will need to see these. + set(command_paths) + set(config_paths "") + message(STATUS "Processing crates: ${ZEPHYR_RS_MODULES}") + foreach(module IN LISTS LIB_RUST_CRATES) + message(STATUS "module: ${module}") + set(config_paths + "${config_paths}\ +${module}.path = \"${ZEPHYR_BASE}/lib/rust/${module}\" +") + list(APPEND command_paths + "--config" + "patch.crates-io.${module}.path=\\\"${ZEPHYR_BASE}/lib/rust/${module}\\\"" + ) + endforeach() + + # Write out a cargo config file that can be copied into `.cargo/config.toml` (or made a + # symlink) in the source directory to allow various IDE tools and such to work. The build we + # invoke will override these settings, in case they are out of date. Everything set here + # should match the arguments given to the cargo build command below. + file(WRITE ${SAMPLE_CARGO_CONFIG} " +# This is a generated sample .cargo/config.toml file from the Zephyr build. +# At the time of generation, this represented the settings needed to allow +# a `cargo build` command to compile the rust code using the current Zephyr build. +# If any settings in the Zephyr build change, this could become out of date. +[build] +target = \"${RUST_TARGET}\" +target-dir = \"${CARGO_TARGET_DIR}\" + +[env] +BUILD_DIR = \"${CMAKE_CURRENT_BINARY_DIR}\" +DOTCONFIG = \"${DOTCONFIG}\" +ZEPHYR_BASE = \"${ZEPHYR_BASE}\" +RUST_DTS = \"${RUST_DTS}\" +BINDGEN_CLANG_ARGS = \"${BINDGEN_CLANG_ARGS}\" +BINDGEN_WRAP_STATIC_FNS = \"${BINDGEN_WRAP_STATIC_FNS}\" + +[patch.crates-io] +${config_paths} +") + + # The library is built by invoking Cargo. + add_custom_command( + OUTPUT ${DUMMY_FILE} + BYPRODUCTS ${RUST_LIBRARY} ${BINDGEN_WRAP_STATIC_FNS} + COMMAND + ${CMAKE_EXECUTABLE} + env BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR} + DOTCONFIG=${DOTCONFIG} + ZEPHYR_BASE=${ZEPHYR_BASE} + RUST_DTS=${RUST_DTS} + BINDGEN_CLANG_ARGS=${BINDGEN_CLANG_ARGS} + BINDGEN_WRAP_STATIC_FNS="${BINDGEN_WRAP_STATIC_FNS}" + cargo build + # TODO: release flag if release build + # --release + + # Override the features according to the shield given. For a general case, + # this will need to come from a variable or argument. + # TODO: This needs to be passed in. + # --no-default-features + # --features ${SHIELD_FEATURE} + + # Set a replacement so that packages can just use `zephyr-sys` as a package + # name to find it. + ${command_paths} + --target ${RUST_TARGET} + --target-dir ${CARGO_TARGET_DIR} + COMMENT "Building Rust application" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + + add_custom_target(librustapp ALL + DEPENDS ${DUMMY_FILE} + ) + + target_link_libraries(app PUBLIC -Wl,--allow-multiple-definition ${RUST_LIBRARY}) + add_dependencies(app librustapp) + + target_sources(app PRIVATE + ${ZEPHYR_BASE}/lib/rust/main.c + ${ZEPHYR_BASE}/lib/rust/panic.c + ${BINDGEN_WRAP_STATIC_FNS} + ) +endfunction() diff --git a/cmake/modules/zephyr_default.cmake b/cmake/modules/zephyr_default.cmake index 7472331255b8..e9e00b97ef5e 100644 --- a/cmake/modules/zephyr_default.cmake +++ b/cmake/modules/zephyr_default.cmake @@ -112,6 +112,7 @@ list(APPEND zephyr_cmake_modules kconfig) list(APPEND zephyr_cmake_modules arch_v2) list(APPEND zephyr_cmake_modules soc_v1) list(APPEND zephyr_cmake_modules soc_v2) +list(APPEND zephyr_cmake_modules rust) foreach(component ${SUB_COMPONENTS}) if(NOT ${component} IN_LIST zephyr_cmake_modules) diff --git a/doc/develop/languages/index.rst b/doc/develop/languages/index.rst index 033b9084ac4f..b1b54355f1a4 100644 --- a/doc/develop/languages/index.rst +++ b/doc/develop/languages/index.rst @@ -8,3 +8,4 @@ Language Support c/index.rst cpp/index.rst + rust/index.rst diff --git a/doc/develop/languages/rust/index.rst b/doc/develop/languages/rust/index.rst new file mode 100644 index 000000000000..c7e8b586dc6c --- /dev/null +++ b/doc/develop/languages/rust/index.rst @@ -0,0 +1,163 @@ +.. _language_rust: + +Rust Language Support +##################### + +Rust is a systems programming language focused on safety, speed, and +concurrency. Designed to prevent common programming errors such as +null pointer dereferencing and buffer overflows, Rust emphasizes +memory safety without sacrificing performance. Its powerful type +system and ownership model ensure thread-safe programming, making it +an ideal choice for developing reliable and efficient low-level code. +Rust's expressive syntax and modern features make it a robust +alternative for developers working on embedded systems, operating +systems, and other performance-critical applications. + +Enabling Rust Support +********************* + +Currently, Zephyr supports applications written in Rust and C. The +enable Rust support, you must select the :kconfig:option:`CONFIG_RUST` +in the application configuration file. + +The rust toolchain is separate from the rest of the Zephyr SDK. It +is recommended to use the `rustup`_ tool to install the rust +toolchain. In addition to the base compiler, you will need to install +core libraries for the target(s) you wish to compile on. It is +easiest to determine what needs to be installed by trying a build. +The diagnostics from the rust compilation will indicate the rust +command needed to install the appropriate target support: + +.. _rustup: https://rustup.rs/ + +.. code-block:: + + $ west build ... + ... + error[E0463]: can't find crate for `core` + | + = note: the `thumbv7m-none-eabi` target may not be installed + = help: consider downloading the target with `rustup target add thumb7m-none-eabi` + +In this case, the given ``rustup`` command will install the needed +target support. The target needed will depend on both the board +selected, as well as certain configuration choices (such as whether +floating point is enabled). + +Writing a Rust Application +************************** + +See :zephyr_file:`samples/rust` for examples of an application. + +CMake files +----------- + +The application should contain a :file:`CMakeFiles.txt`, similar to +the following: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.20.0) + + find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + project(hello_world) + + rust_cargo_application() + +Cargo files +----------- + +Rust applications are built with Cargo. The Zephyr build system will +configure and invoke cargo in your application directory, with options +set so that it can find the Zephyr support libraries, and that the +output will be contained within the Zephyr build directory. + +The :file:`Cargo.toml` will need to have a ``[lib]`` section that sets +``crate-type = ["staticlib"]``, and will need to include ``zephyr = +"0.1.0"`` as a dependency. You can use crates.io and the Crate +ecosystem to include any other dependencies you need. Just make sure +that you use crates that support building with no-std. + +Application +----------- + +The application source itself should live in file:`src/lib.rs`. A few +things are needed. A minimal file would be: + +.. code-block:: rust + + #![no_std] + + extern crate zephyr; + + #[no_mangle] + extern "C" fn rust_main() { + } + +The ``no_std`` declaration is needed to prevent the code from +referencing the ``std`` library. The extern reference will cause the +zephyr crate to be brought in, even if nothing from it is used. +Practically, any meaningful Rust application on Zephyr will use +something from this crate, and this line is not necessary. Lastly, +the main declaration exports the main symbol so that it can be called +by C code. The build ``rust_cargo_application()`` cmake function will +include a small C file that will call into this from the C main +function. + +Zephyr Functionality +******************** + +The bindings to Zephyr for Rust are under development, and are +currently rather minimalistic. + +Bool Kconfig settings +--------------------- + +Boolean Kconfig settings can be used from within Rust code. Due to +design constraints by the Rust language, settings that affect +compilation must be determined before the build is made. In order to +use this in your application, you will need to use the +``zephyr-build`` crate, provided, to make these symbols available. + +To your ``Cargo.toml`` file, add the following: + +.. code-block:: toml + + [build-dependencies] + zephyr-build = "0.1.0" + +Then, you will need a ``build.rs`` file to call the support function. +The following will work: + +.. code-block:: rust + + fn main() { + zephyr_build::export_bool_kconfig(); + } + +At this point, it will be possible to use the ``cfg`` directive in +Rust on boolean Kconfig values. For example: + +.. code-block:: rust + + #[cfg(CONFIG_SCHED_DUMB)] + one_declaration; + + #[cfg(not(CONFIG_SCHED_DUMB)] + other_declaration; + +Other Kconfig settings +---------------------- + +All bool, numeric and string Kconfig settings are accessible from the +``zephyr::kconfig`` module. For example: + +.. code-block:: rust + + let ceiling = zephyr::kconfig::CONFIG_PRIORITY_CEILING - 1; + +Other functionality +------------------- + +Access to other functionality within zephyr is a work-in-progress, and +this document will be updated as that is done. diff --git a/lib/Kconfig b/lib/Kconfig index af6717bc22ec..b2fccd6722b6 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -21,6 +21,8 @@ source "lib/posix/Kconfig" source "lib/open-amp/Kconfig" +source "lib/rust/Kconfig" + source "lib/smf/Kconfig" source "lib/acpi/Kconfig" diff --git a/lib/rust/Kconfig b/lib/rust/Kconfig new file mode 100644 index 000000000000..c9b7b5eb68e3 --- /dev/null +++ b/lib/rust/Kconfig @@ -0,0 +1,14 @@ +# Rust configuration options +# +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +menu "Rust Language Support" + +config RUST + bool "Rust support for the application" + select EXPERIMENTAL + help + This option enables the use of applications written in Rust. + +endmenu diff --git a/lib/rust/main.c b/lib/rust/main.c new file mode 100644 index 000000000000..da6fc5548f8e --- /dev/null +++ b/lib/rust/main.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Linaro LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +/* This main is brought into the Rust application. */ + +#ifdef CONFIG_RUST + +extern void rust_main(void); + +int main(void) +{ + rust_main(); + return 0; +} + +#endif diff --git a/lib/rust/panic.c b/lib/rust/panic.c new file mode 100644 index 000000000000..b398b9b52226 --- /dev/null +++ b/lib/rust/panic.c @@ -0,0 +1,10 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +#include + +// Provide a symbol to call from Rust +void rust_panic() +{ + k_panic(); +} diff --git a/lib/rust/zephyr-build/Cargo.toml b/lib/rust/zephyr-build/Cargo.toml new file mode 100644 index 000000000000..c73a95e6e330 --- /dev/null +++ b/lib/rust/zephyr-build/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "zephyr-build" +version = "0.1.0" +edition = "2021" +description = """ +Build-time support for Rust-based applications that run on Zephyr. +Provides utilities for accessing Kconfig and devicetree information. +""" + +# These are needed at build time. +# Whether these need to be vendored is an open question. They are not +# used by the core Zephyr tree, but are needed by zephyr applications. +[dependencies] +regex = "1.10.3" diff --git a/lib/rust/zephyr-build/src/lib.rs b/lib/rust/zephyr-build/src/lib.rs new file mode 100644 index 000000000000..5ba759571ae3 --- /dev/null +++ b/lib/rust/zephyr-build/src/lib.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +// Pre-build code for zephyr module. + +// This module makes the values from the generated .config available as conditional compilation. +// Note that this only applies to the zephyr module, and the user's application will not be able to +// see these definitions. To make that work, this will need to be moved into a support crate which +// can be invoked by the user's build.rs. + +// This builds a program that is run on the compilation host before the code is compiled. It can +// output configuration settings that affect the compilation. + +use std::io::{BufRead, BufReader, Write}; +use std::env; +use std::fs::File; +use std::path::Path; + +use regex::Regex; + +/// Export boolean Kconfig entries. This must happen in any crate that wishes to access the +/// configuration settings. +pub fn export_bool_kconfig() { + let dotconfig = env::var("DOTCONFIG").expect("DOTCONFIG must be set by wrapper"); + + // Ensure the build script is rerun when the dotconfig changes. + println!("cargo:rerun-if-env-changed=DOTCONFIG"); + println!("cargo-rerun-if-changed={}", dotconfig); + + let config_y = Regex::new(r"^(CONFIG_.*)=y$").unwrap(); + + let file = File::open(&dotconfig).expect("Unable to open dotconfig"); + for line in BufReader::new(file).lines() { + let line = line.expect("reading line from dotconfig"); + if let Some(caps) = config_y.captures(&line) { + println!("cargo:rustc-cfg={}", &caps[1]); + } + } +} + +/// Capture bool, numeric and string kconfig values in a 'kconfig' module. +/// This is a little simplistic, and will make the entries numeric if they look like numbers. +/// Ideally, this would be built on the types of the values, but that will require more +/// introspection. +pub fn build_kconfig_mod() { + let dotconfig = env::var("DOTCONFIG").expect("DOTCONFIG must be set by wrapper"); + let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set"); + + // The assumption is that hex values are unsigned, and decimal are signed. + let config_hex = Regex::new(r"^(CONFIG_.*)=(0x[0-9a-fA-F]+)$").unwrap(); + let config_int = Regex::new(r"^(CONFIG_.*)=(-?[1-9][0-9]*)$").unwrap(); + // It is unclear what quoting might be used in the .config. + let config_str = Regex::new(r#"^(CONFIG_.*)=(".*")$"#).unwrap(); + let gen_path = Path::new(&outdir).join("kconfig.rs"); + + let mut f = File::create(&gen_path).unwrap(); + writeln!(&mut f, "pub mod kconfig {{").unwrap(); + + let file = File::open(&dotconfig).expect("Unable to open dotconfig"); + for line in BufReader::new(file).lines() { + let line = line.expect("reading line from dotconfig"); + if let Some(caps) = config_hex.captures(&line) { + writeln!(&mut f, " #[allow(dead_code)]").unwrap(); + writeln!(&mut f, " pub const {}: usize = {};", + &caps[1], &caps[2]).unwrap(); + } + if let Some(caps) = config_int.captures(&line) { + writeln!(&mut f, " #[allow(dead_code)]").unwrap(); + writeln!(&mut f, " pub const {}: isize = {};", + &caps[1], &caps[2]).unwrap(); + } + if let Some(caps) = config_str.captures(&line) { + writeln!(&mut f, " #[allow(dead_code)]").unwrap(); + writeln!(&mut f, " pub const {}: &'static str = {};", + &caps[1], &caps[2]).unwrap(); + } + } + writeln!(&mut f, "}}").unwrap(); +} diff --git a/lib/rust/zephyr-sys/Cargo.toml b/lib/rust/zephyr-sys/Cargo.toml new file mode 100644 index 000000000000..db22cbf08c21 --- /dev/null +++ b/lib/rust/zephyr-sys/Cargo.toml @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Zühlke Engineering AG +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "zephyr-sys" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +bindgen = { version = "0.69.4", features = ["experimental"] } diff --git a/lib/rust/zephyr-sys/bindgen_input.h b/lib/rust/zephyr-sys/bindgen_input.h new file mode 100644 index 000000000000..d0a827acb881 --- /dev/null +++ b/lib/rust/zephyr-sys/bindgen_input.h @@ -0,0 +1,6 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include diff --git a/lib/rust/zephyr-sys/build.rs b/lib/rust/zephyr-sys/build.rs new file mode 100644 index 000000000000..7bbe7b58ee6e --- /dev/null +++ b/lib/rust/zephyr-sys/build.rs @@ -0,0 +1,42 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +use std::{env, fs}; +use std::path::PathBuf; + +fn main() { + let input_header = env::current_dir().unwrap().join("bindgen_input.h"); + let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be set!")); + let zephyr_base = PathBuf::from(env::var("ZEPHYR_BASE").expect("ZEPHYR_BASE must be set!")); + let wrap_static_fns = PathBuf::from(env::var("BINDGEN_WRAP_STATIC_FNS").expect("BINDGEN_WRAP_STATIC_FNS must be set!")); + let clang_args_path = PathBuf::from(env::var("BINDGEN_CLANG_ARGS").expect("BINDGEN_CLANG_ARGS must be set!")); + let clang_args = fs::read_to_string(clang_args_path).expect("Failed to read BINDGEN_CLANG_ARGS file!"); + + let bindings = bindgen::Builder::default() + .use_core() + .layout_tests(false) + .detect_include_paths(false) + .wrap_static_fns(true) + .wrap_static_fns_path(wrap_static_fns) + .clang_args(clang_args.split(';')) + .header(input_header.to_str().unwrap()) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + + .allowlist_file(zephyr_base.join(".*").to_str().unwrap()) + .allowlist_file(".*/errno.h") + .blocklist_function("z_impl_.*") + .blocklist_var("K_SYSCALL_.*") + .blocklist_var("DT_.*") + .blocklist_var("CONFIG_.*") + .blocklist_var("Z_UTIL_.*") + + // Deprecated function, hopefully there is a more generic way of doing this. + .blocklist_function("sys_clock_timeout_end_calc") + + .generate() + .expect("Unable to generate bindings!"); + + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/lib/rust/zephyr-sys/src/lib.rs b/lib/rust/zephyr-sys/src/lib.rs new file mode 100644 index 000000000000..19b04b206a6a --- /dev/null +++ b/lib/rust/zephyr-sys/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(improper_ctypes)] + +use core::include; +use core::concat; +use core::env; + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/lib/rust/zephyr/Cargo.toml b/lib/rust/zephyr/Cargo.toml new file mode 100644 index 000000000000..2f9b8d7b6237 --- /dev/null +++ b/lib/rust/zephyr/Cargo.toml @@ -0,0 +1,23 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "zephyr" +version = "0.1.0" +edition = "2021" +description = """ +Functionality for Rust-based applications that run on Zephyr. +""" + +[dependencies] +zephyr-sys = "0.1.0" + +# Just for this experiment, probably the zephyr crate should come without dependencies. +bitmask-enum = "2.2.4" +int-enum = "1.1.2" + +# These are needed at build time. +# Whether these need to be vendored is an open question. They are not +# used by the core Zephyr tree, but are needed by zephyr applications. +[build-dependencies] +zephyr-build = "0.1.0" diff --git a/lib/rust/zephyr/build.rs b/lib/rust/zephyr/build.rs new file mode 100644 index 000000000000..f4345e95cc8c --- /dev/null +++ b/lib/rust/zephyr/build.rs @@ -0,0 +1,17 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +// Pre-build code for zephyr module. + +// This module makes the values from the generated .config available as conditional compilation. +// Note that this only applies to the zephyr module, and the user's application will not be able to +// see these definitions. To make that work, this will need to be moved into a support crate which +// can be invoked by the user's build.rs. + +// This builds a program that is run on the compilation host before the code is compiled. It can +// output configuration settings that affect the compilation. + +fn main() { + zephyr_build::export_bool_kconfig(); + zephyr_build::build_kconfig_mod(); +} diff --git a/lib/rust/zephyr/src/allocator.rs b/lib/rust/zephyr/src/allocator.rs new file mode 100644 index 000000000000..aff753e49a42 --- /dev/null +++ b/lib/rust/zephyr/src/allocator.rs @@ -0,0 +1,22 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +use core::alloc::{GlobalAlloc, Layout}; +use zephyr_sys::{k_aligned_alloc, k_free}; + +struct ZephyrAllocator {} + +#[global_allocator] +static ALLOCATOR: ZephyrAllocator = ZephyrAllocator {}; + +unsafe impl Sync for ZephyrAllocator {} + +unsafe impl GlobalAlloc for ZephyrAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + return k_aligned_alloc(layout.align(), layout.size()) as *mut u8; + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + return k_free(_ptr as *mut core::ffi::c_void); + } +} diff --git a/lib/rust/zephyr/src/devicetree.rs b/lib/rust/zephyr/src/devicetree.rs new file mode 100644 index 000000000000..451526012f82 --- /dev/null +++ b/lib/rust/zephyr/src/devicetree.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +include!(env!("RUST_DTS")); + +pub const fn root() -> &'static DtNode0 { &DT_NODE_0 } + +#[macro_export] +macro_rules! dt_alias { + ($alias:ident) => { $crate::devicetree::root().aliases.$alias }; +} + +#[macro_export] +macro_rules! device_dt_get { + ($node:expr) => { $node.device() }; +} diff --git a/lib/rust/zephyr/src/drivers.rs b/lib/rust/zephyr/src/drivers.rs new file mode 100644 index 000000000000..e9c1284f66fd --- /dev/null +++ b/lib/rust/zephyr/src/drivers.rs @@ -0,0 +1,5 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(CONFIG_GPIO)] +pub mod gpio; diff --git a/lib/rust/zephyr/src/drivers/gpio.rs b/lib/rust/zephyr/src/drivers/gpio.rs new file mode 100644 index 000000000000..febdd5de4c2d --- /dev/null +++ b/lib/rust/zephyr/src/drivers/gpio.rs @@ -0,0 +1,76 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +use bitmask_enum::bitmask; +use crate::errno::Errno; + +// Maybe this should be somehow wrapped to not expose zephyr_sys stuff to the application. +pub type GpioDtSpec = zephyr_sys::gpio_dt_spec; + +#[bitmask(u32)] +pub enum GpioFlags { + None = 0, + Input = zephyr_sys::GPIO_INPUT, + Output = zephyr_sys::GPIO_OUTPUT, + Disconnected = zephyr_sys::GPIO_DISCONNECTED, + InitLow = zephyr_sys::GPIO_OUTPUT_INIT_LOW, + InitHigh = zephyr_sys::GPIO_OUTPUT_INIT_HIGH, + InitLogical = zephyr_sys::GPIO_OUTPUT_INIT_LOGICAL, + OutputLow = zephyr_sys::GPIO_OUTPUT | zephyr_sys::GPIO_OUTPUT_INIT_LOW, + OutputHigh = zephyr_sys::GPIO_OUTPUT | zephyr_sys::GPIO_OUTPUT_INIT_HIGH, + OutputInactive = zephyr_sys::GPIO_OUTPUT | zephyr_sys::GPIO_OUTPUT_INIT_LOW | zephyr_sys::GPIO_OUTPUT_INIT_LOGICAL, + OutputActive = zephyr_sys::GPIO_OUTPUT | zephyr_sys::GPIO_OUTPUT_INIT_HIGH | zephyr_sys::GPIO_OUTPUT_INIT_LOGICAL, +} + +pub struct GpioPin { + gpio_dt_spec: GpioDtSpec, +} + +impl GpioPin { + pub fn new(gpio_dt_spec: GpioDtSpec) -> Self { + GpioPin { gpio_dt_spec } + } + + pub fn configure(&self, extra_flags: GpioFlags) -> Result<(), Errno> { + unsafe { + Errno::from(zephyr_sys::gpio_pin_configure_dt(&self.gpio_dt_spec, extra_flags.bits())) + } + } + + pub fn toggle(&self) -> Result<(), Errno> { + unsafe { + Errno::from(zephyr_sys::gpio_pin_toggle_dt(&self.gpio_dt_spec)) + } + } +} + +#[macro_export] +macro_rules! dt_gpio_ctrl_by_idx { + ($node:expr, $prop:ident, $idx:tt) => { $node.$prop.$idx.phandle } +} + +#[macro_export] +macro_rules! dt_gpio_pin_by_idx { + ($node:expr, $prop:ident, $idx:tt) => { $node.$prop.$idx.cell_pin }; +} + +#[macro_export] +macro_rules! dt_gpio_flags_by_idx { + ($node:expr, $prop:ident, $idx:tt) => { $node.$prop.$idx.cell_flags }; +} + +#[macro_export] +macro_rules! gpio_dt_spec_get_by_idx { + ($node:expr, $prop:ident, $idx:tt) => { + crate::drivers::gpio::GpioDtSpec { + port: device_dt_get!(dt_gpio_ctrl_by_idx!($node, $prop, $idx)), + pin: dt_gpio_pin_by_idx!($node, $prop, $idx) as u8, + dt_flags: dt_gpio_flags_by_idx!($node, $prop, $idx) as u16, + } + } +} + +#[macro_export] +macro_rules! gpio_dt_spec_get { + ($node:expr, $prop:ident) => { gpio_dt_spec_get_by_idx!($node, $prop, 0) } +} diff --git a/lib/rust/zephyr/src/errno.rs b/lib/rust/zephyr/src/errno.rs new file mode 100644 index 000000000000..b3e4c3213711 --- /dev/null +++ b/lib/rust/zephyr/src/errno.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +use int_enum::IntEnum; + +#[repr(u32)] +#[derive(Debug, PartialEq, IntEnum)] +pub enum Errno { + EPERM = zephyr_sys::EPERM, + ENOENT = zephyr_sys::ENOENT, + ESRCH = zephyr_sys::ESRCH, + EINTR = zephyr_sys::EINTR, + EIO = zephyr_sys::EIO, + ENXIO = zephyr_sys::ENXIO, + E2BIG = zephyr_sys::E2BIG, + ENOEXEC = zephyr_sys::ENOEXEC, + EBADF = zephyr_sys::EBADF, + ECHILD = zephyr_sys::ECHILD, + EAGAIN = zephyr_sys::EAGAIN, + ENOMEM = zephyr_sys::ENOMEM, + EACCES = zephyr_sys::EACCES, + EFAULT = zephyr_sys::EFAULT, + ENOTBLK = zephyr_sys::ENOTBLK, + EBUSY = zephyr_sys::EBUSY, + EEXIST = zephyr_sys::EEXIST, + EXDEV = zephyr_sys::EXDEV, + ENODEV = zephyr_sys::ENODEV, + ENOTDIR = zephyr_sys::ENOTDIR, + EISDIR = zephyr_sys::EISDIR, + EINVAL = zephyr_sys::EINVAL, + ENFILE = zephyr_sys::ENFILE, + EMFILE = zephyr_sys::EMFILE, + ENOTTY = zephyr_sys::ENOTTY, + ETXTBSY = zephyr_sys::ETXTBSY, + EFBIG = zephyr_sys::EFBIG, + ENOSPC = zephyr_sys::ENOSPC, + ESPIPE = zephyr_sys::ESPIPE, + EROFS = zephyr_sys::EROFS, + EMLINK = zephyr_sys::EMLINK, + EPIPE = zephyr_sys::EPIPE, + EDOM = zephyr_sys::EDOM, + ERANGE = zephyr_sys::ERANGE, + ENOMSG = zephyr_sys::ENOMSG, + EDEADLK = zephyr_sys::EDEADLK, + ENOLCK = zephyr_sys::ENOLCK, + ENOSTR = zephyr_sys::ENOSTR, + ENODATA = zephyr_sys::ENODATA, + ETIME = zephyr_sys::ETIME, + ENOSR = zephyr_sys::ENOSR, + EPROTO = zephyr_sys::EPROTO, + EBADMSG = zephyr_sys::EBADMSG, + ENOSYS = zephyr_sys::ENOSYS, + ENOTEMPTY = zephyr_sys::ENOTEMPTY, + ENAMETOOLONG = zephyr_sys::ENAMETOOLONG, + ELOOP = zephyr_sys::ELOOP, + EOPNOTSUPP = zephyr_sys::EOPNOTSUPP, + EPFNOSUPPORT = zephyr_sys::EPFNOSUPPORT, + ECONNRESET = zephyr_sys::ECONNRESET, + ENOBUFS = zephyr_sys::ENOBUFS, + EAFNOSUPPORT = zephyr_sys::EAFNOSUPPORT, + EPROTOTYPE = zephyr_sys::EPROTOTYPE, + ENOTSOCK = zephyr_sys::ENOTSOCK, + ENOPROTOOPT = zephyr_sys::ENOPROTOOPT, + ESHUTDOWN = zephyr_sys::ESHUTDOWN, + ECONNREFUSED = zephyr_sys::ECONNREFUSED, + EADDRINUSE = zephyr_sys::EADDRINUSE, + ECONNABORTED = zephyr_sys::ECONNABORTED, + ENETUNREACH = zephyr_sys::ENETUNREACH, + ENETDOWN = zephyr_sys::ENETDOWN, + ETIMEDOUT = zephyr_sys::ETIMEDOUT, + EHOSTDOWN = zephyr_sys::EHOSTDOWN, + EHOSTUNREACH = zephyr_sys::EHOSTUNREACH, + EINPROGRESS = zephyr_sys::EINPROGRESS, + EALREADY = zephyr_sys::EALREADY, + EDESTADDRREQ = zephyr_sys::EDESTADDRREQ, + EMSGSIZE = zephyr_sys::EMSGSIZE, + EPROTONOSUPPORT = zephyr_sys::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = zephyr_sys::ESOCKTNOSUPPORT, + EADDRNOTAVAIL = zephyr_sys::EADDRNOTAVAIL, + ENETRESET = zephyr_sys::ENETRESET, + EISCONN = zephyr_sys::EISCONN, + ENOTCONN = zephyr_sys::ENOTCONN, + ETOOMANYREFS = zephyr_sys::ETOOMANYREFS, + ENOTSUP = zephyr_sys::ENOTSUP, + EILSEQ = zephyr_sys::EILSEQ, + EOVERFLOW = zephyr_sys::EOVERFLOW, + ECANCELED = zephyr_sys::ECANCELED, +} + +impl Errno { + pub fn from(result: core::ffi::c_int) -> Result<(), Self> { + Self::from_result(result).map(|_| ()) + } + + pub fn from_result(result: core::ffi::c_int) -> Result { + if result >= 0 { + return Ok(result as i32); + } + + match Self::try_from(-result as u32) { + Ok(errno) => Err(errno), + _ => panic!("Unexpected value"), + } + } +} diff --git a/lib/rust/zephyr/src/kernel.rs b/lib/rust/zephyr/src/kernel.rs new file mode 100644 index 000000000000..83234be808cb --- /dev/null +++ b/lib/rust/zephyr/src/kernel.rs @@ -0,0 +1,27 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +use core::ffi::c_char; +use alloc::ffi::CString; + +#[macro_export] +macro_rules! printk { + ($msg:expr) => { + crate::kernel::printk($msg); + }; + ($fmt:expr, $($arg:tt)*) => { + crate::kernel::printk(::alloc::format!($fmt, $($arg)*).as_str()); + }; +} + +pub fn printk(msg: &str) { + let cstring = CString::new(msg).unwrap(); + + unsafe { + zephyr_sys::printk("%s\0".as_ptr() as *const c_char, cstring.as_ptr() as *const c_char); + } +} + +pub fn msleep(ms: i32) -> i32 { + unsafe { zephyr_sys::k_msleep(ms) } +} diff --git a/lib/rust/zephyr/src/lib.rs b/lib/rust/zephyr/src/lib.rs new file mode 100644 index 000000000000..fc030ff6da4e --- /dev/null +++ b/lib/rust/zephyr/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +//! Zephyr application support for Rust +//! +//! This crates provides the core functionality for applications written in Rust that run on top of +//! Zephyr. + +#![no_std] + +extern crate alloc; + +mod allocator; +mod panic; + +pub mod errno; +pub mod kernel; +pub mod drivers; +pub mod devicetree; + +// To me, it would feel more consistent if kconfig.rs was also generated using Python within the +// Zephyr build infrastructure instead of the zephyr-build crate. For the devicetree this was +// much easier as there is already the pickled EDT available. + +// Bring in the generated kconfig module +include!(concat!(env!("OUT_DIR"), "/kconfig.rs")); + +// Ensure that Rust is enabled. +#[cfg(not(CONFIG_RUST))] +compile_error!("CONFIG_RUST must be set to build Rust in Zephyr"); diff --git a/lib/rust/zephyr/src/panic.rs b/lib/rust/zephyr/src/panic.rs new file mode 100644 index 000000000000..1457b66c3103 --- /dev/null +++ b/lib/rust/zephyr/src/panic.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2024 Zühlke Engineering AG +// SPDX-License-Identifier: Apache-2.0 + +use alloc::ffi::CString; +use alloc::string::ToString; +use core::ffi::c_char; +use core::panic::PanicInfo; +use zephyr_sys::printk; + +extern "C" { + fn rust_panic() -> !; +} + +#[panic_handler] +unsafe fn panic(_info: &PanicInfo) -> ! { + if let Ok(message) = CString::new(_info.to_string()) { + printk("%s\n\0".as_ptr() as *const c_char, message.as_ptr()); + } + + rust_panic(); +} diff --git a/samples/rust/hello_rust/CMakeLists.txt b/samples/rust/hello_rust/CMakeLists.txt new file mode 100644 index 000000000000..dc45defc4651 --- /dev/null +++ b/samples/rust/hello_rust/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(hello_world) + +rust_cargo_application() diff --git a/samples/rust/hello_rust/Cargo.toml b/samples/rust/hello_rust/Cargo.toml new file mode 100644 index 000000000000..4058e2d9a009 --- /dev/null +++ b/samples/rust/hello_rust/Cargo.toml @@ -0,0 +1,16 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +# This must be rustapp for now. +name = "rustapp" +version = "0.1.0" +edition = "2021" +description = "A sample hello world application in Rust" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +zephyr = "0.1.0" diff --git a/samples/rust/hello_rust/prj.conf b/samples/rust/hello_rust/prj.conf new file mode 100644 index 000000000000..5af4669365c7 --- /dev/null +++ b/samples/rust/hello_rust/prj.conf @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RUST=y + +# Seems necessary for exception unwinding +CONFIG_CPP=y + +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_HEAP_MEM_POOL_SIZE=32768 diff --git a/samples/rust/hello_rust/src/lib.rs b/samples/rust/hello_rust/src/lib.rs new file mode 100644 index 000000000000..9aed2603d755 --- /dev/null +++ b/samples/rust/hello_rust/src/lib.rs @@ -0,0 +1,26 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +extern crate alloc; + +use zephyr::*; // Wildcard import to bring in nested macros, maybe there is a better way +use zephyr::drivers::gpio::{GpioPin, GpioFlags}; + +#[no_mangle] +extern "C" fn rust_main() { + printk!("Hello, world! {}\n", kconfig::CONFIG_BOARD); + + let gpio_pin = GpioPin::new(gpio_dt_spec_get!(dt_alias!(led0), gpios)); + + gpio_pin.configure(GpioFlags::OutputActive) + .expect("Failed to configure pin."); + + loop { + kernel::msleep(1000); + + gpio_pin.toggle() + .expect("Failed to toggle pin."); + } +} diff --git a/scripts/dts/gen_dts_rust.py b/scripts/dts/gen_dts_rust.py new file mode 100755 index 000000000000..a5adda4b9e9e --- /dev/null +++ b/scripts/dts/gen_dts_rust.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2024 Zühlke Engineering AG +# SPDX-License-Identifier: Apache-2.0 + +# This script uses edtlib and the devicetree data in the build directory +# to generate a Rust source file which contains devicetree data. +# +# For each node, a struct is defined and instantiated once as a constant +# containing all information and referencing child nodes and other +# nodes via phandles. For nodes with status "okay" a device() method +# is generated allowing to access the device object which can be used +# to interface with the device drivers. +# +# The provided information allows to define macros which behave similar +# as the macros we have in C to access devicetree information such as +# `GPIO_DT_SPEC_GET`. + +import re +import os +import sys +import pickle +import argparse + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'python-devicetree', + 'src')) + + +def parse_args(): + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("--rust-out", required=True, + help="path to write the Rust device tree file") + parser.add_argument("--edt-pickle", required=True, + help="path to read the pickled edtlib.EDT object from") + return parser.parse_args() + + +def main(): + args = parse_args() + + with open(args.edt_pickle, 'rb') as f: + edt = pickle.load(f) + + with open(args.rust_out, 'w') as f: + f.write(''.join(generate_dts_rust(edt))) + + +def generate_dts_rust(edt): + for node in edt.nodes: + if node.path == '/chosen': + yield from generate_chosen_node_struct(node) + elif node.path == '/aliases': + yield from generate_aliases_node_struct(node) + else: + yield from generate_normal_node_struct(node) + + for node in edt.nodes: + if node.path == '/chosen': + yield from generate_chosen_node_instance(node) + elif node.path == '/aliases': + yield from generate_aliases_node_instance(node) + else: + yield from generate_normal_node_instance(node) + + +def generate_chosen_node_struct(node): + yield f'#[allow(dead_code)]\n' + yield f'pub struct DtNode{node.dep_ordinal} {{\n' + + for chosen_name, chosen_node in node.edt.chosen_nodes.items(): + yield f' pub {replace_chars(chosen_name)}: &\'static DtNode{chosen_node.dep_ordinal},\n' + + yield '}\n\n' + + +def generate_aliases_node_struct(node): + yield f'#[allow(dead_code)]\n' + yield f'pub struct DtNode{node.dep_ordinal} {{\n' + + alias2node = {} + for node in node.edt.nodes: + for name in node.aliases: + alias2node[name] = node + + for alias_name, alias_node in alias2node.items(): + yield f' pub {replace_chars(alias_name)}: &\'static DtNode{alias_node.dep_ordinal},\n' + + yield '}\n\n' + + +def generate_normal_node_struct(node): + phandle_arrays = {} + + yield f'#[allow(dead_code)]\n' + yield f'pub struct DtNode{node.dep_ordinal} {{\n' + + for child_name, child_node in node.children.items(): + yield f' pub {replace_chars(child_name)}: &\'static DtNode{child_node.dep_ordinal},\n' + + for property_name, property_object in node.props.items(): + rust_type = None + property_type = property_object.spec.type + + if property_name == 'status': + continue + if property_type == 'boolean': + rust_type = 'bool' + if property_type == 'int': + rust_type = 'u32' + if property_type == 'string': + rust_type = '&\'static str' + if property_type == 'uint8-array': + rust_type = f'[u8; {len(property_object.val)}]' + if property_type == 'array': + rust_type = f'[u32; {len(property_object.val)}]' + if property_type == 'string-array': + rust_type = f'[&\'static str; {len(property_object.val)}]' + if property_type in ('phandle', 'path'): + rust_type = f'&\'static DtNode{property_object.val.dep_ordinal}' + if property_type == 'phandles': + rust_type = '(' + ', '.join(f'&\'static DtNode{n.dep_ordinal}' for n in property_object.val) + ',)' + if property_type == 'phandle-array': + pha_types = [] + for pha_entry in property_object.val: + pha_type = f'DtNode{node.dep_ordinal}Pha{len(phandle_arrays)}' + pha_types.append(pha_type) + phandle_arrays[pha_type] = pha_entry + rust_type = '(' + ', '.join('&\'static ' + pha_id for pha_id in pha_types) + ',)' + + if rust_type: + yield f' pub {replace_chars(property_name)}: {rust_type},\n' + else: + yield f' // {replace_chars(property_name)} ({property_type})\n' + + yield ' pub status: &\'static str,\n' + yield '}\n\n' + + for pha_type, pha_entry in phandle_arrays.items(): + yield f'#[allow(dead_code)]\n' + yield f'pub struct {pha_type} {{\n' + yield f' pub phandle: &\'static DtNode{pha_entry.controller.dep_ordinal},\n' + for cell_name in pha_entry.data.keys(): + yield f' pub cell_{replace_chars(cell_name)}: u32,\n' + yield '}\n\n' + + +def generate_chosen_node_instance(node): + yield f'// path("{node.path}")\n' + yield f'#[allow(dead_code)]\n' + yield f'const DT_NODE_{node.dep_ordinal}: DtNode{node.dep_ordinal} = DtNode{node.dep_ordinal} {{\n' + + for chosen_name, chosen_node in node.edt.chosen_nodes.items(): + yield f' {replace_chars(chosen_name)}: &DT_NODE_{chosen_node.dep_ordinal},\n' + + yield '};\n\n' + + +def generate_aliases_node_instance(node): + yield f'// path("{node.path}")\n' + yield f'#[allow(dead_code)]\n' + yield f'const DT_NODE_{node.dep_ordinal}: DtNode{node.dep_ordinal} = DtNode{node.dep_ordinal} {{\n' + + alias2node = {} + for node in node.edt.nodes: + for name in node.aliases: + alias2node[name] = node + + for alias_name, alias_node in alias2node.items(): + yield f' {replace_chars(alias_name)}: &DT_NODE_{alias_node.dep_ordinal},\n' + + yield '};\n\n' + + +def generate_normal_node_instance(node): + phandle_arrays = {} + + yield f'// path("{node.path}")\n' + for label in node.labels: + yield f'// nodelabel("{label}")\n' + for alias in node.aliases: + yield f'// alias("{alias}")\n' + if node.matching_compat: + yield f'// matching_compat("{node.matching_compat}")\n' + + yield f'#[allow(dead_code)]\n' + yield f'const DT_NODE_{node.dep_ordinal}: DtNode{node.dep_ordinal} = DtNode{node.dep_ordinal} {{\n' + + for child_name, child_node in node.children.items(): + yield f' {replace_chars(child_name)}: &DT_NODE_{child_node.dep_ordinal},\n' + + for property_name, property_object in node.props.items(): + property_type = property_object.spec.type + rust_value = None + + if property_name == 'status': + continue + if property_type == 'boolean': + rust_value = "true" if property_object.val else "false" + elif property_type == 'int': + rust_value = str(property_object.val) + elif property_type == 'string': + rust_value = f'"{property_object.val}"' + elif property_type in ('uint8-array', 'array'): + rust_value = '[' + ', '.join(str(val) for val in property_object.val) + ']' + elif property_type == 'string-array': + rust_value = '[' + ', '.join(f'"{val}"' for val in property_object.val) + ']' + elif property_type in ('phandle', 'path'): + rust_value = f'&DT_NODE_{property_object.val.dep_ordinal}' + elif property_type == 'phandles': + rust_value = '(' + ', '.join(f'&DT_NODE_{n.dep_ordinal}' for n in property_object.val) + ',)' + elif property_type == 'phandle-array': + pha_insts = [] + for pha_entry in property_object.val: + pha_inst = f'DT_NODE_{node.dep_ordinal}_PHA_{len(phandle_arrays)}' + pha_type = f'DtNode{node.dep_ordinal}Pha{len(phandle_arrays)}' + pha_insts.append('&' + pha_inst) + phandle_arrays[pha_inst] = (pha_type, pha_entry) + rust_value = '(' + ', '.join(pha_insts) + ',)' + + if rust_value: + yield f' {replace_chars(property_name)}: {rust_value},\n' + else: + yield f' // {replace_chars(property_name)} ({property_type})\n' + + yield f' status: "{node.status}",\n' + yield '};\n\n' + + for pha_inst, (pha_type, pha_entry) in phandle_arrays.items(): + if pha_entry.name: + yield f'// {pha_entry.name}\n' + yield f'#[allow(dead_code)]\n' + yield f'const {pha_inst}: {pha_type} = {pha_type} {{\n' + yield f' phandle: &DT_NODE_{pha_entry.controller.dep_ordinal},\n' + for cell_name, cell_value in pha_entry.data.items(): + yield f' cell_{replace_chars(cell_name)}: {cell_value},\n' + yield '};\n\n' + + if node.status == 'okay': + yield f'#[allow(dead_code)]\n' + yield f'impl DtNode{node.dep_ordinal} {{\n' + yield ' pub fn device(&self) -> *const zephyr_sys::device {\n' + yield f' unsafe {{ &zephyr_sys::__device_dts_ord_{node.dep_ordinal} }}\n' + yield ' }\n' + yield '}\n\n' + + +def replace_chars(text: str): + return re.sub(r'\W+', '_', text) + + +if __name__ == "__main__": + main()