diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c404355c..e18c84b43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,12 @@ jobs: run: | git clone https://github.com/arduino/ArduinoCore-API.git $MODULE_PATH/../ArduinoCore-API cp -rfp $MODULE_PATH/../ArduinoCore-API/api $MODULE_PATH/cores/arduino/ + apt install rustup + rustup default stable + rustup target add thumbv6m-none-eabi + rustup target add thumbv7em-none-eabihf + rustup target add thumbv7em-none-eabi + rustup target add thumbv7m-none-eabi - name: Build fade run: | diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9dd1fd55a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +rust/Cargo.lock diff --git a/CMakeLists.txt b/CMakeLists.txt index 484c3721d..85a18700d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,74 @@ if (CONFIG_ARDUINO_API) add_subdirectory(cores) add_subdirectory(libraries) zephyr_include_directories(${variant_dir}) + + # quote from https://github.com/zephyrproject-rtos/zephyr-lang-rust/blob/main/CMakeLists.txt + function (rust_target_arch RUST_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() + elseif(CONFIG_RISCV) + if(CONFIG_RISCV_ISA_RV64I) + # TODO: Should fail if the extensions don't match. + set(${RUST_TARGET} "riscv64imac-unknown-none-elf" PARENT_SCOPE) + elseif(CONFIG_RISCV_ISA_RV32I) + # TODO: We have multiple choices, try to pick the best. + set(${RUST_TARGET} "riscv32i-unknown-none-elf" PARENT_SCOPE) + else() + message(FATAL_ERROR "Rust: Unsupported riscv ISA") + endif() + elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "x86_64")) + set(${RUST_TARGET} "x86_64-unknown-none" PARENT_SCOPE) + elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64")) + set(${RUST_TARGET} "aarch64-unknown-none" PARENT_SCOPE) + else() + message(FATAL_ERROR "Rust: Add support for other target") + endif() + endfunction() + + rust_target_arch(RUST_TARGET_TRIPLE) + + set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rust) + set(RUST_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/rust) + set(RUST_LIB ${RUST_OUT_DIR}/target/${RUST_TARGET_TRIPLE}/release/libarduinocore_api_rust.a) + + add_custom_command( + OUTPUT ${RUST_LIB} + COMMAND ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${RUST_OUT_DIR}/target + cargo build --manifest-path ${RUST_CRATE_DIR}/Cargo.toml + --target ${RUST_TARGET_TRIPLE} --release + WORKING_DIRECTORY ${RUST_CRATE_DIR} + COMMENT "Building Rust staticlib for ${RUST_TARGET}" + VERBATIM + ) + + add_custom_target(arduinocore_api_rust_build ALL DEPENDS ${RUST_LIB}) + add_library(arduinocore_api_rust STATIC IMPORTED GLOBAL) + set_target_properties(arduinocore_api_rust PROPERTIES IMPORTED_LOCATION ${RUST_LIB}) + zephyr_link_libraries(arduinocore_api_rust) + endif() diff --git a/Kconfig b/Kconfig index 27f9fb3c4..93bcf5231 100644 --- a/Kconfig +++ b/Kconfig @@ -17,6 +17,11 @@ config ARDUINO_API if ARDUINO_API +config USE_ARDUINO_API_RUST_IMPLEMENTATION + bool "Use Rust implementation API core" + default y + select RUST + config QEMU_ICOUNT bool "QEMU icount mode" default n diff --git a/cores/arduino/CMakeLists.txt b/cores/arduino/CMakeLists.txt index 193fd3c12..18682e8c7 100644 --- a/cores/arduino/CMakeLists.txt +++ b/cores/arduino/CMakeLists.txt @@ -7,6 +7,7 @@ if(NOT DEFINED ARDUINO_BUILD_PATH) zephyr_sources(zephyrPrint.cpp) zephyr_sources(zephyrSerial.cpp) zephyr_sources(zephyrCommon.cpp) +zephyr_sources(apiCommon.cpp) if(DEFINED CONFIG_ARDUINO_ENTRY) zephyr_sources(main.cpp) diff --git a/cores/arduino/apiCommon.cpp b/cores/arduino/apiCommon.cpp new file mode 100644 index 000000000..cfb6d919f --- /dev/null +++ b/cores/arduino/apiCommon.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "zephyrInternal.h" + +extern "C" { + int32_t map_i32(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max); + uint16_t makeWord_w(uint16_t w); + uint16_t makeWord_hl(byte h, byte l); +} + +long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return map_i32(x, in_min, in_max, out_min, out_max); +} + +uint16_t makeWord(uint16_t w) { + return makeWord_w(w); +} +uint16_t makeWord(byte h, byte l) { + return makeWord_hl(h, l); +} diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 000000000..4e9bcd9cd --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "arduinocore_api_rust" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[lib] +crate-type = ["staticlib"] diff --git a/rust/src/common.rs b/rust/src/common.rs new file mode 100644 index 000000000..21fde6541 --- /dev/null +++ b/rust/src/common.rs @@ -0,0 +1,22 @@ +// Copyright (c) 2025 TOKITA Hiroshi +// SPDX-License-Identifier: Apache-2.0 + +#[unsafe(no_mangle)] +pub extern "C" fn map_i32( + x: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32 +) -> i32 { + let num = x.wrapping_sub(in_min).wrapping_mul(out_max.wrapping_sub(out_min)); + let den = in_max.wrapping_sub(in_min); + // Note: To keep compatibility, the panic when den=0 is left as is. + num / den.wrapping_add(out_min) +} + +#[unsafe(no_mangle)] +pub extern "C" fn makeWord_w(w: u16) -> u16 { + w +} + +#[unsafe(no_mangle)] +pub extern "C" fn makeWord_hl(h: u8, l: u8) -> u16 { + ((h as u16) << 8) | (l as u16) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 000000000..2f3e5e00a --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2025 TOKITA Hiroshi +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +mod common; +pub use common::*; + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_panic: &PanicInfo<'_>) -> ! { + loop {} +}