diff --git a/.gitignore b/.gitignore index 7f7bab74..b2aa2707 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,12 @@ vdso.lds # stm32cubemx **/CubeMX_Config/Drivers/ **/CubeMX_Config/MDK-ARM/ + +# Rust +target/ +Cargo.lock +*.rs.bk +*.pdb + +# rust-analyzer +**/target/rust-analyzer/ diff --git a/machines/qemu-virt-riscv64/.gitignore b/machines/qemu-virt-riscv64/.gitignore index 341f703a..39b52ea8 100644 --- a/machines/qemu-virt-riscv64/.gitignore +++ b/machines/qemu-virt-riscv64/.gitignore @@ -1,3 +1,16 @@ mnt.c romfs_data.c opensbi +build +/*.config +/*.lds +/*.sh +/rtconfig* +text.txt +fat +applications/* +/.config +/link.lds +/rtconfig.h +/rtconfig.py +/Kconfig \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/Kconfig b/machines/qemu-virt-riscv64/Kconfig index 98a4e778..61cfc28c 100644 --- a/machines/qemu-virt-riscv64/Kconfig +++ b/machines/qemu-virt-riscv64/Kconfig @@ -9,6 +9,7 @@ PKGS_DIR := packages source "$(RTT_DIR)/Kconfig" osource "$PKGS_DIR/Kconfig" rsource "driver/Kconfig" +rsource "rust/Kconfig" config BOARD_QEMU_VIRT_RV64 bool diff --git a/machines/qemu-virt-riscv64/rust/Kconfig b/machines/qemu-virt-riscv64/rust/Kconfig new file mode 100644 index 00000000..51366b7f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/Kconfig @@ -0,0 +1,26 @@ +menuconfig RT_USING_RUST + bool "Enable Rust component support" + default n + help + Enable Rust programming language support for RT-Thread. + This allows you to write RT-Thread components using Rust. + +if RT_USING_RUST + config RT_RUST_CORE + bool "Enable Rust Core Library" + default y + config RUST_DEBUG_BUILD + bool "Build Rust code in debug mode" + default n + help + Build Rust code with debug symbols and without optimizations. + This increases binary size but helps with debugging. + config RUST_INIT_COMPONENT + bool "Auto-initialize Rust component" + default y + help + Automatically initialize Rust component during RT-Thread startup. + + rsource "examples/Kconfig" + +endif \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/README.md b/machines/qemu-virt-riscv64/rust/README.md new file mode 100644 index 00000000..05652592 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/README.md @@ -0,0 +1,160 @@ +# RT-Thread Rust组件目录结构建议 + +## 一、目录结构 + + `rust/` 为以下结构: + +``` +rust/ +├── Kconfig # 组件配置 +├── SConscript # 构建脚本 +├── README.md # 组件说明 +├── docs/ # 文档 +│ ├── api_reference.md +│ ├── abi_compatibility.md +│ └── performance_report.md +├── core/ # 核心支持库 +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── lib.rs +│ │ ├── bindings/ # C接口绑定 +│ │ │ ├── kernel.rs # 内核API +│ │ │ ├── thread.rs # 线程API +│ │ │ ├── ipc.rs # IPC机制 +│ │ │ ├── memory.rs # 内存管理 +│ │ │ └── device.rs # 设备API +│ │ ├── rt_prelude.rs # 预导入模块 +│ │ ├── allocator.rs # 内存分配器 +│ │ ├── panic.rs # panic处理 +│ │ └── macros/ # 宏支持 +│ │ ├── thread_entry.rs # 线程入口宏 +│ │ └── msh_export.rs # Shell命令导出宏 +├── runtime/ # 运行时支持 +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── no_std_support.rs # no_std模式 +│ │ ├── start.rs # 启动代码 +│ │ └── lang_items.rs # 语言项 +│ └── linker/ +│ └── rust_module.ld # 链接脚本 +├── shell/ # Shell命令支持 +│ ├── Cargo.toml +│ └── src/ +│ └── commands.rs +├── examples/ # 示例代码 +│ ├── README.md +│ ├── applications/ # 应用示例 +│ │ ├── hello_world/ +│ │ ├── thread_sync/ +│ │ └── device_io/ +│ ├── components/ # 组件示例 +│ │ ├── logger/ +│ │ ├── sensor_driver/ +│ │ └── protocol_stack/ +│ └── modules/ # 内核模块示例 +│ ├── simple_module/ +│ └── device_module/ +├── tools/ # 工具脚本 +│ ├── build_rust.py +│ ├── gen_bindings.sh +│ └── cargo_wrapper.py +└── tests/ # 测试 + ├── integration/ + └── unit/ +``` + +## 二、各部分功能说明 + +### 2.1 核心支持库 (core/) + +**提供Rust本身的基础支持和RT-Thread系统服务绑定:** + +1. **bindings/** - C接口的安全封装 + - 按功能模块划分(kernel、thread、ipc、memory、device) + - 使用bindgen自动生成或手工编写 + - 提供类型安全的Rust接口 + +2. **rt_prelude.rs** - 预导入模块 + - 类似std::prelude,导入常用类型和trait + - 简化Rust代码编写 + +3. **allocator.rs** - 全局内存分配器 + - 实现`GlobalAlloc` trait + - 对接RT-Thread的内存管理系统 + +4. **macros/** - 宏定义 + - `rt_thread_main!` - 程序入口宏,标记Rust的main函数 + - `rt_component_export!` - 导出组件初始化入口的宏 + - `rt_app_export!` - 导出应用初始化入口的宏 + - `msh_cmd_export!` - 导出shell命令的宏 + - 简化在no_std模式下的开发 + +### 2.2 运行时支持 (runtime/) + +**解决no_std模式下的运行时问题:** + +- **no_std_support.rs** - no_std环境支持 +- **start.rs** - 启动代码,处理main函数到RT-Thread线程的转换 +- **lang_items.rs** - 必要的语言项实现 +- **linker/** - 链接脚本,用于模块加载 + +### 2.3 Shell命令支持 (shell/) + +**导出Rust命令到RT-Thread shell:** + +- 提供`MSH_CMD_EXPORT`宏的Rust版本 +- 自动处理参数解析 +- 与finsh组件集成 + +### 2.4 示例代码 (examples/) + +**包含三类示例:** + +1. **applications/** - 使用Rust编写应用 + - `hello_world/` - 基础示例 + - `thread_sync/` - 线程同步示例 + - `device_io/` - 设备IO示例 + +2. **components/** - 使用Rust编写组件/软件包 + - `logger/` - 日志组件 + - `sensor_driver/` - 传感器驱动 + - `protocol_stack/` - 协议栈 + +3. **modules/** - 使用Rust编写内核动态模块 + - `simple_module/` - 简单模块示例 + - `device_module/` - 设备驱动模块示例 + +## 三、构建系统集成 + +### 3.1 Kconfig配置示例 + +```kconfig +menuconfig RT_USING_RUST + bool "Rust Language Support" + default n +if RT_USING_RUST + config RT_RUST_CORE + bool "Enable Rust Core Library" + default y + config RT_RUST_RUNTIME + bool "Enable Rust Runtime Support" + default y + select RT_RUST_CORE + config RT_RUST_MSH_SUPPORT + bool "Enable Rust MSH Command Support" + default y + depends on RT_USING_FINSH + menu "Rust Examples" + config RT_RUST_EXAMPLES_APPS + bool "Build Application Examples" + default n + config RT_RUST_EXAMPLES_COMPONENTS + bool "Build Component Examples" + default n + config RT_RUST_EXAMPLES_MODULES + bool "Build Module Examples" + default n + depends on RT_USING_LWP && RT_USING_MODULE + endmenu +endif +``` \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/SConscript b/machines/qemu-virt-riscv64/rust/SConscript new file mode 100644 index 00000000..c7ef7659 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/SConscript @@ -0,0 +1,14 @@ +# for module compiling +import os +from building import * + +cwd = GetCurrentDir() +objs = [] +list = os.listdir(cwd) + +for d in list: + path = os.path.join(cwd, d) + if os.path.isfile(os.path.join(path, 'SConscript')): + objs = objs + SConscript(os.path.join(d, 'SConscript')) + +Return('objs') diff --git a/machines/qemu-virt-riscv64/rust/core/.gitignore b/machines/qemu-virt-riscv64/rust/core/.gitignore new file mode 100644 index 00000000..5f4fb259 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/.gitignore @@ -0,0 +1,37 @@ +# Rust build artifacts (now in build/rust) +/target/ +**/*.rs.bk +*.pdb + +# Cargo lock file (optional - uncomment if you want to exclude it) +# Cargo.lock + +# Build directories +/build/ +*.o +*.a +*.so +*.dylib +*.dll + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Backup files +*.bak +*.tmp +*.temp + +# Log files +*.log \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/Cargo.toml b/machines/qemu-virt-riscv64/rust/core/Cargo.toml new file mode 100644 index 00000000..4a4a0687 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rt-rust" +version = "0.1.0" +edition = "2021" + +[lib] +name = "rt_rust" +crate-type = ["rlib", "staticlib"] + +[features] +default = [] +smp = [] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/machines/qemu-virt-riscv64/rust/core/SConscript b/machines/qemu-virt-riscv64/rust/core/SConscript new file mode 100644 index 00000000..880f6bce --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/SConscript @@ -0,0 +1,71 @@ +import os +from building import * + +cwd = GetCurrentDir() + +sys.path.append(os.path.join(cwd, '../tools')) +from build_support import ( + detect_rust_target, + make_rustflags, + collect_features, + verify_rust_toolchain, + ensure_rust_target_installed, + cargo_build_staticlib, + clean_rust_build, +) +def _has(sym: str) -> bool: + try: + return bool(GetDepend([sym])) + except Exception: + return bool(GetDepend(sym)) + + +# Source files – MSH command glue +src = ['rust_cmd.c'] +LIBS = [] +LIBPATH = [] + +if GetOption('clean'): + # Register Rust artifacts for cleaning + rust_build_dir = clean_rust_build(Dir('#').abspath) + if os.path.exists(rust_build_dir): + print(f'Registering {rust_build_dir} for cleanup') + Clean('.', rust_build_dir) + else: + print('No rust build artifacts to clean') +else: + if verify_rust_toolchain(): + import rtconfig + + target = detect_rust_target(_has, rtconfig) + if not target: + print('Error: Unable to detect Rust target; please check configuration') + else: + print(f'Detected Rust target: {target}') + + # Optional hint if target missing + ensure_rust_target_installed(target) + + # Build mode and features + debug = bool(_has('RUST_DEBUG_BUILD')) + features = collect_features(_has) + + rustflags = make_rustflags(rtconfig, target) + rust_lib = cargo_build_staticlib( + rust_dir=cwd, target=target, features=features, debug=debug, rustflags=rustflags + ) + + if rust_lib: + LIBS = ['rt_rust'] + LIBPATH = [os.path.dirname(rust_lib)] + print('Rust library linked successfully') + else: + print('Warning: Failed to build Rust library') + else: + print('Warning: Rust toolchain not found') + print('Please install Rust from https://rustup.rs') + +# Define component group for SCons +group = DefineGroup('rust', src, depend=['RT_USING_RUST'], LIBS=LIBS, LIBPATH=LIBPATH) + +Return('group') diff --git a/machines/qemu-virt-riscv64/rust/core/rust_cmd.c b/machines/qemu-virt-riscv64/rust/core/rust_cmd.c new file mode 100644 index 00000000..62471ec9 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/rust_cmd.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-09-20 foxglove First version + * 2025-10-10 foxglove Clear Rust component MSH command registration + * + * Description: Rust component MSH command registration + * Provides access interface to Rust modular APIs + */ + +#include +#include +#include +#include + +extern int rust_init(void); +/* ============== Rust function declarations ============== */ + +/* Component initialization */ +static int rust_component_init(void) +{ + int ret = rust_init(); + if (ret == 0) + { + printf("Hello RT-Thread Rust!\n"); + } + return ret; +} +#ifdef RUST_INIT_COMPONENT +INIT_APP_EXPORT(rust_component_init); +#endif \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/allocator.rs b/machines/qemu-virt-riscv64/rust/core/src/allocator.rs new file mode 100644 index 00000000..2f29d643 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/allocator.rs @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread GlobalAlloc implementation + */ + +use crate::api::mem::{mem_alloc, mem_alloc_aligned, mem_free, mem_free_aligned, mem_realloc}; +use core::alloc::{GlobalAlloc, Layout}; +use crate::panic::panic_on_atomic_context; +use core::ptr; +use core::ffi::c_void; + +/// RT-Thread global allocator implementation +/// +/// This allocator provides a safe interface to RT-Thread's memory management +/// system, supporting both regular and aligned allocations. +pub struct RttAlloc; + +unsafe impl GlobalAlloc for RttAlloc { + /// Allocate memory with the given layout + /// + /// # Safety + /// This function is unsafe as required by the GlobalAlloc trait. + /// The caller must ensure proper usage according to GlobalAlloc requirements. + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + panic_on_atomic_context("alloc"); + let size = layout.size(); + let align = layout.align(); + + // Handle zero-sized allocations + if size == 0 { + return ptr::null_mut(); + } + + // Use aligned allocation if alignment is greater than default + // RT-Thread's default alignment is typically 4 or 8 bytes + if align > 8 { + match mem_alloc_aligned(size, align) { + Some(ptr) => ptr as *mut u8, + None => ptr::null_mut(), + } + } else { + match mem_alloc(size) { + Some(ptr) => ptr as *mut u8, + None => ptr::null_mut(), + } + } + } + + /// Deallocate memory at the given pointer with the given layout + /// + /// # Safety + /// This function is unsafe as required by the GlobalAlloc trait. + /// The caller must ensure the pointer was allocated by this allocator + /// and the layout matches the original allocation. + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + panic_on_atomic_context("dealloc"); + if ptr.is_null() { + return; + } + + let align = layout.align(); + + // Use aligned deallocation if the original allocation was aligned + if align > 8 { + mem_free_aligned(ptr as *mut c_void); + } else { + mem_free(ptr as *mut c_void); + } + } + + /// Reallocate memory + /// + /// # Safety + /// This function is unsafe as required by the GlobalAlloc trait. + /// The caller must ensure proper usage according to GlobalAlloc requirements. + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + panic_on_atomic_context("realloc"); + // Handle zero-sized new allocation + if new_size == 0 { + self.dealloc(ptr, layout); + return ptr::null_mut(); + } + + // Handle null pointer (equivalent to alloc) + if ptr.is_null() { + let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); + return self.alloc(new_layout); + } + + // For aligned allocations, we need to handle realloc manually + // since RT-Thread's rt_realloc may not preserve alignment + let align = layout.align(); + if align > 8 { + // Allocate new aligned memory + let new_ptr = match mem_alloc_aligned(new_size, align) { + Some(ptr) => ptr as *mut u8, + None => return ptr::null_mut(), + }; + + // Copy data from old to new + let copy_size = core::cmp::min(layout.size(), new_size); + ptr::copy_nonoverlapping(ptr, new_ptr, copy_size); + + // Free old memory + mem_free_aligned(ptr as *mut c_void); + + new_ptr + } else { + // Use RT-Thread's realloc for regular allocations + match mem_realloc(ptr as *mut c_void, new_size) { + Some(new_ptr) => new_ptr as *mut u8, + None => ptr::null_mut(), + } + } + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/base.rs b/machines/qemu-virt-riscv64/rust/core/src/api/base.rs new file mode 100644 index 00000000..11a4c3fb --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/base.rs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove latest version + */ +use crate::bindings::*; + +// Error type from rt-thread `c code` +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum RttCResult { + Ok = RT_EOK as isize, + Error = -(RT_ERROR as isize), + TimeOut = -(RT_ETIMEOUT as isize), + Full = -(RT_EFULL as isize), + Empty = -(RT_EEMPTY as isize), + NoMem = -(RT_ENOMEM as isize), + NoSys = -(RT_ENOSYS as isize), + Busy = -(RT_EBUSY as isize), + IO = -(RT_EIO as isize), + INTR = -(RT_EINTR as isize), + INVAL = -(RT_EINVAL as isize), + + NotValidCode = -100, +} +// NOTE Match to `enum Error` +impl From for RttCResult { + fn from(a: i32) -> Self { + let ret = match a { + 0 => RttCResult::Ok, + -1 => RttCResult::Error, + -2 => RttCResult::TimeOut, + -3 => RttCResult::Full, + -4 => RttCResult::Empty, + -5 => RttCResult::NoMem, + -6 => RttCResult::NoSys, + -7 => RttCResult::Busy, + -8 => RttCResult::IO, + -9 => RttCResult::INTR, + -10 => RttCResult::INVAL, + _ => RttCResult::NotValidCode, + }; + ret + } +} + +impl From for RttCResult { + fn from(a: i64) -> Self { + if a >= 0 { + RttCResult::Ok + } else { + match a { + -1 => RttCResult::Error, + -2 => RttCResult::TimeOut, + -3 => RttCResult::Full, + -4 => RttCResult::Empty, + -5 => RttCResult::NoMem, + -6 => RttCResult::NoSys, + -7 => RttCResult::Busy, + -8 => RttCResult::IO, + -9 => RttCResult::INTR, + -10 => RttCResult::INVAL, + _ => RttCResult::NotValidCode, + } + } + } +} + +pub fn is_eok(val: RttCResult) -> bool { + if let RttCResult::Ok = val { + true + } else { + false + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/interrupt.rs b/machines/qemu-virt-riscv64/rust/core/src/api/interrupt.rs new file mode 100644 index 00000000..6337e6e1 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/interrupt.rs @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove latest version + */ +use crate::bindings::*; + +#[cfg(not(feature = "smp"))] +unsafe fn irq_close() -> rt_base_t { + rt_hw_interrupt_disable() +} +#[cfg(feature = "smp")] +unsafe fn irq_close() -> rt_base_t { + rt_cpus_lock() +} + +#[cfg(not(feature = "smp"))] +unsafe fn irq_open(f: rt_base_t) { + rt_hw_interrupt_enable(f) +} +#[cfg(feature = "smp")] +unsafe fn irq_open(f: rt_base_t) { + rt_cpus_unlock(f) +} + +pub fn no_irq(f: F) -> T +where + F: FnOnce() -> T, +{ + let level; + let out; + unsafe { level = irq_close() } + out = f(); + unsafe { irq_open(level) } + out +} + +#[derive(Debug, Copy, Clone)] +pub struct InterruptFlag(rt_base_t); + +pub const INTERRUPT_FLAG_INIT: InterruptFlag = InterruptFlag { 0: 0 as _ }; + +pub fn interrupt_disable() -> InterruptFlag { + unsafe { InterruptFlag(irq_close()) } +} + +pub fn interrupt_enable(f: InterruptFlag) { + unsafe { irq_open(f.0) } +} + +pub fn interrupt_enter() { + unsafe { + rt_interrupt_enter(); + } +} + +pub fn interrupt_leave() { + unsafe { + rt_interrupt_leave(); + } +} + +pub fn is_irq_context() -> bool { + unsafe { rt_interrupt_get_nest() != 0 } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/libloading.rs b/machines/qemu-virt-riscv64/rust/core/src/api/libloading.rs new file mode 100644 index 00000000..58bb2c96 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/libloading.rs @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-14 foxglove libdl safe wrapper (libloading-like) + */ + +//! Minimal `libloading`-style safe wrapper around RT-Thread libdl. +//! Provides RAII `Library` and typed `Symbol` for dlsym. + +extern crate alloc; + +use core::ffi::{c_char, c_int, c_void}; +use core::ffi::CStr; +use core::marker::PhantomData; +use core::mem::forget; +use core::ops::Deref; + +use alloc::ffi::CString; +use alloc::vec::Vec; + +use crate::bindings::libc; + +pub use crate::bindings::libc::{RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NOW}; + +/// Error type for dynamic loading operations +pub enum DlError { + /// dlopen failed; contains last error C-string pointer + Open(*const c_char), + /// dlsym failed; contains last error C-string pointer + Symbol(*const c_char), + /// dlclose returned non-zero; contains return code + Close(c_int), +} + +impl core::fmt::Debug for DlError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } +} + +impl core::fmt::Display for DlError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + &DlError::Open(ptr) | &DlError::Symbol(ptr) => { + if ptr.is_null() { + write!(f, "[libdl open/sym error: none]") + } else { + unsafe { + // `ptr` is a raw C string pointer + let cstr = CStr::from_ptr(ptr); + match cstr.to_str() { + Ok(s) => write!(f, "{}", s), + Err(_) => write!(f, "[libdl error: invalid UTF-8]") + } + } + } + } + DlError::Close(rc) => write!(f, "dlclose rc={}", rc), + } + } +} + +/// RAII wrapper for a `dlopen` handle +pub struct Library { + handle: *mut c_void, +} + +impl Library { + /// Create a new library using default flags (`RTLD_NOW | RTLD_GLOBAL`). + /// Accepts a Rust string path and converts internally to a C string. + pub fn new(path: &str) -> Result { + // Convert to NUL-terminated C string; reject interior NULs + let cstr = match CString::new(path) { + Ok(s) => s, + Err(_) => { + // Interior NUL; no last_error available, return a neutral error + return Err(DlError::Open(core::ptr::null())); + } + }; + unsafe { Self::open_global(cstr.as_ptr()) } + } + /// Open a dynamic module with given flags + /// Safety: `path` must be a valid, NUL-terminated C string pointer + pub unsafe fn open(path: *const c_char, flags: c_int) -> Result { + let h = libc::dlopen(path, flags); + if h.is_null() { + Err(DlError::Open(libc::last_error_ptr())) + } else { + Ok(Library { handle: h }) + } + } + + /// Convenience open using `RTLD_NOW | RTLD_GLOBAL` + pub unsafe fn open_global(path: *const c_char) -> Result { + Self::open(path, RTLD_NOW | RTLD_GLOBAL) + } + + /// Resolve a typed symbol from the library using a C string pointer. + /// Safety: Caller must ensure `T` matches the actual symbol type and `symbol` is NUL-terminated. + pub unsafe fn get_ptr(&self, symbol: *const c_char) -> Result, DlError> { + let sym = libc::dlsym(self.handle, symbol); + if sym.is_null() { + Err(DlError::Symbol(libc::last_error_ptr())) + } else { + Ok(Symbol { + raw: sym, + _lib: PhantomData, + _ty: PhantomData, + }) + } + } + + /// Resolve a typed symbol from the library using a byte slice name. + /// If the slice is not NUL-terminated, a temporary buffer is allocated. + pub fn get(&self, symbol: &[u8]) -> Result, DlError> { + let (ptr, _guard_buf): (*const c_char, Option>) = if symbol.last().copied() == Some(0) { + (symbol.as_ptr() as *const c_char, None) + } else { + let mut buf = Vec::with_capacity(symbol.len() + 1); + buf.extend_from_slice(symbol); + buf.push(0); + (buf.as_ptr() as *const c_char, Some(buf)) + }; + // Call into dlsym; buffer lives until end of this function + let sym = unsafe { libc::dlsym(self.handle, ptr) }; + if sym.is_null() { + Err(DlError::Symbol(libc::last_error_ptr())) + } else { + Ok(Symbol { + raw: sym, + _lib: PhantomData, + _ty: PhantomData, + }) + } + } + + /// Check if handle is null + pub fn is_null(&self) -> bool { + self.handle.is_null() + } + + /// Consume and close the library explicitly + pub unsafe fn close(self) -> Result<(), DlError> { + let h = self.handle; + forget(self); + let rc = libc::dlclose(h); + if rc == 1 { + Ok(()) + } else { + Err(DlError::Close(rc)) + } + } + + /// Leak the handle out (caller manages lifetime) + pub unsafe fn into_raw(self) -> *mut c_void { + let h = self.handle; + forget(self); + h + } +} + +impl Drop for Library { + fn drop(&mut self) { + unsafe { + if !self.handle.is_null() { + let _ = libc::dlclose(self.handle); + self.handle = core::ptr::null_mut(); + } + } + } +} + +/// RAII wrapper for a symbol tied to a `Library` lifetime. +/// Stores the raw pointer and performs typed conversion at the use site. +pub struct Symbol<'lib, T> { + raw: *mut c_void, + _lib: PhantomData<&'lib Library>, + _ty: PhantomData, +} + +impl<'lib, T> Symbol<'lib, T> { + /// Check if the underlying pointer is null + pub fn is_null(&self) -> bool { + self.raw.is_null() + } + + /// Get the raw pointer as `*mut c_void` + pub fn as_raw(&self) -> *mut c_void { + self.raw + } + + /// Cast the raw pointer to a different pointer type + /// Safety: Caller must ensure `U` is the correct target type. + pub unsafe fn as_ptr(&self) -> *mut U { + self.raw as *mut U + } + + /// Convert to the typed value (e.g., function pointer) at use site + /// Safety: `T` must be the correct type for the symbol. + pub unsafe fn to_value(&self) -> T + where + T: Sized + Copy, + { + // Bitwise-copy the pointer value into a `T` without using `transmute`. + // This relies on `T` (e.g., extern "C" fn ...) having the same representation size. + let src: *const T = (&self.raw as *const *mut c_void) as *const T; + core::ptr::read(src) + } + + /// Consume the wrapper and return the raw pointer + pub fn into_raw(self) -> *mut c_void { + let p = self.raw; + forget(self); + p + } +} + +impl<'lib, T> Deref for Symbol<'lib, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + // Reinterpret the `raw` pointer storage as a value of type `T`. + // This is safe if `T` is a pointer-sized function pointer or compatible pointer type. + unsafe { &*((&self.raw as *const *mut c_void) as *const T) } + } +} + +/// Fetch last libc error C-string pointer (for printing) +#[inline] +pub fn last_error() -> *const c_char { + libc::last_error_ptr() +} diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/mem.rs b/machines/qemu-virt-riscv64/rust/core/src/api/mem.rs new file mode 100644 index 00000000..55e13408 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/mem.rs @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove memory management API + */ + +use crate::bindings::*; +use core::ffi::c_void; + +/// Safe memory allocation +/// +/// # Arguments +/// * `bytes` - Number of bytes to allocate +/// +/// # Returns +/// * `Some(ptr)` - Valid pointer to allocated memory +/// * `None` - Allocation failed +pub fn mem_alloc(bytes: usize) -> Option<*mut c_void> { + if bytes == 0 { + return None; + } + + let ptr = unsafe { rt_malloc(bytes as rt_size_t) }; + if ptr.is_null() { + None + } else { + Some(ptr) + } +} + +/// Safe memory allocation with zero initialization +/// +/// # Arguments +/// * `count` - Number of elements +/// * `size` - Size of each element in bytes +/// +/// # Returns +/// * `Some(ptr)` - Valid pointer to zero-initialized memory +/// * `None` - Allocation failed +pub fn mem_calloc(count: usize, size: usize) -> Option<*mut c_void> { + if count == 0 || size == 0 { + return None; + } + + let ptr = unsafe { rt_calloc(count as rt_size_t, size as rt_size_t) }; + if ptr.is_null() { + None + } else { + Some(ptr) + } +} + +/// Safe aligned memory allocation +/// +/// # Arguments +/// * `bytes` - Number of bytes to allocate +/// * `align` - Alignment requirement (must be power of 2) +/// +/// # Returns +/// * `Some(ptr)` - Valid aligned pointer to allocated memory +/// * `None` - Allocation failed or invalid alignment +pub fn mem_alloc_aligned(bytes: usize, align: usize) -> Option<*mut c_void> { + if bytes == 0 || align == 0 || !align.is_power_of_two() { + return None; + } + + let ptr = unsafe { rt_malloc_align(bytes as rt_size_t, align as rt_size_t) }; + if ptr.is_null() { + None + } else { + Some(ptr) + } +} + +/// Safe memory reallocation +/// +/// # Arguments +/// * `ptr` - Existing pointer (can be null) +/// * `new_size` - New size in bytes +/// +/// # Returns +/// * `Some(ptr)` - Valid pointer to reallocated memory +/// * `None` - Reallocation failed +pub fn mem_realloc(ptr: *mut c_void, new_size: usize) -> Option<*mut c_void> { + if new_size == 0 { + if !ptr.is_null() { + mem_free(ptr); + } + return None; + } + + let new_ptr = unsafe { rt_realloc(ptr, new_size as rt_size_t) }; + if new_ptr.is_null() { + None + } else { + Some(new_ptr) + } +} + +/// Safe memory deallocation +/// +/// # Arguments +/// * `ptr` - Pointer to memory to free (null pointers are safely ignored) +pub fn mem_free(ptr: *mut c_void) { + if !ptr.is_null() { + unsafe { rt_free(ptr) }; + } +} + +/// Safe aligned memory deallocation +/// +/// # Arguments +/// * `ptr` - Pointer to aligned memory to free (null pointers are safely ignored) +pub fn mem_free_aligned(ptr: *mut c_void) { + if !ptr.is_null() { + unsafe { rt_free_align(ptr) }; + } +} + +/// Unsafe raw memory allocation (for compatibility) +/// +/// # Safety +/// Caller must ensure proper error handling and memory management +pub unsafe fn mem_alloc_raw(bytes: usize) -> *mut c_void { + rt_malloc(bytes as rt_size_t) +} + +/// Memory allocation result type for better error handling +pub type MemResult = Result; + +/// Memory allocation error types +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MemError { + /// Out of memory + OutOfMemory, + /// Invalid size (zero or too large) + InvalidSize, + /// Invalid alignment (not power of 2) + InvalidAlignment, + /// Null pointer passed where valid pointer expected + NullPointer, +} + +/// Safe typed memory allocation +/// +/// # Type Parameters +/// * `T` - Type to allocate +/// +/// # Returns +/// * `Ok(ptr)` - Valid pointer to allocated memory for type T +/// * `Err(error)` - Allocation error +pub fn mem_alloc_typed() -> MemResult<*mut T> { + let size = core::mem::size_of::(); + if size == 0 { + return Err(MemError::InvalidSize); + } + + match mem_alloc(size) { + Some(ptr) => Ok(ptr as *mut T), + None => Err(MemError::OutOfMemory), + } +} + +/// Safe typed array allocation +/// +/// # Type Parameters +/// * `T` - Element type +/// +/// # Arguments +/// * `count` - Number of elements +/// +/// # Returns +/// * `Ok(ptr)` - Valid pointer to allocated array +/// * `Err(error)` - Allocation error +pub fn mem_alloc_array(count: usize) -> MemResult<*mut T> { + if count == 0 { + return Err(MemError::InvalidSize); + } + + let size = core::mem::size_of::() + .checked_mul(count) + .ok_or(MemError::InvalidSize)?; + + match mem_alloc(size) { + Some(ptr) => Ok(ptr as *mut T), + None => Err(MemError::OutOfMemory), + } +} + +/// Safe typed array allocation with zero initialization +/// +/// # Type Parameters +/// * `T` - Element type +/// +/// # Arguments +/// * `count` - Number of elements +/// +/// # Returns +/// * `Ok(ptr)` - Valid pointer to zero-initialized array +/// * `Err(error)` - Allocation error +pub fn mem_calloc_array(count: usize) -> MemResult<*mut T> { + if count == 0 { + return Err(MemError::InvalidSize); + } + + let size = core::mem::size_of::(); + match mem_calloc(count, size) { + Some(ptr) => Ok(ptr as *mut T), + None => Err(MemError::OutOfMemory), + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/mod.rs b/machines/qemu-virt-riscv64/rust/core/src/api/mod.rs new file mode 100644 index 00000000..ef979835 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/mod.rs @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove API module + */ +pub mod base; +pub mod interrupt; +pub mod mem; +pub mod thread; +pub mod mutex; +pub mod sem; +pub mod queue; +pub mod libloading; + + +pub use base::*; +pub use interrupt::*; +pub use mem::*; +pub use thread::*; +pub use mutex::*; +pub use sem::*; +pub use queue::*; +pub use libloading::*; diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/mutex.rs b/machines/qemu-virt-riscv64/rust/core/src/api/mutex.rs new file mode 100644 index 00000000..372db600 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/mutex.rs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Mutex API + */ +use crate::api::RttCResult; +use crate::bindings::*; +use core::ptr; +use alloc::ffi::CString; + +pub type APIRawMutex = rt_mutex_t; + +#[inline] +pub fn mutex_create(name: &str) -> Option { + let s = CString::new(name).unwrap(); + let raw; + unsafe { + raw = rt_mutex_create(s.as_ptr(), 1); + } + if raw == ptr::null_mut() { + None + } else { + Some(raw) + } +} + +#[inline] +pub fn mutex_delete(handle: APIRawMutex) { + unsafe { + rt_mutex_delete(handle); + } +} + +#[inline] +pub fn mutex_take(handle: APIRawMutex, tick: isize) -> RttCResult { + unsafe { rt_mutex_take(handle, tick as _).into() } +} + +#[inline] +pub fn mutex_release(handle: APIRawMutex) { + unsafe { + rt_mutex_release(handle); + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/queue.rs b/machines/qemu-virt-riscv64/rust/core/src/api/queue.rs new file mode 100644 index 00000000..373c8a14 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/queue.rs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Queue API + */ + +use crate::api::RttCResult; +use core::ffi::c_void; +use crate::bindings::*; +use core::ptr; +use alloc::ffi::CString; + +pub type APIRawQueue = rt_mq_t; + +#[inline] +pub(crate) fn queue_create(name: &str, len: u64, message_size: u64) -> Option { + let s = CString::new(name).unwrap(); + let raw; + unsafe { raw = rt_mq_create(s.as_ptr(), message_size, len, 0) } + if raw == ptr::null_mut() { + None + } else { + Some(raw) + } +} + +#[inline] +pub(crate) fn queue_send_wait( + handle: APIRawQueue, + msg: *const c_void, + msg_size: u64, + tick: i32, +) -> RttCResult { + unsafe { rt_mq_send_wait(handle, msg, msg_size, tick).into() } +} + +#[inline] +pub(crate) fn queue_receive_wait( + handle: APIRawQueue, + msg: *mut c_void, + msg_size: u64, + tick: i32, +) -> RttCResult { + unsafe { rt_mq_recv(handle, msg, msg_size, tick).into() } +} + +#[inline] +pub(crate) fn queue_delete(handle: APIRawQueue) { + unsafe { + rt_mq_delete(handle); + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/sem.rs b/machines/qemu-virt-riscv64/rust/core/src/api/sem.rs new file mode 100644 index 00000000..c668f459 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/sem.rs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Semaphore API + */ +use crate::api::RttCResult; +use crate::bindings::*; +use core::ptr; +use alloc::ffi::CString; + +pub type APIRawSem = rt_sem_t; + +#[inline] +pub(crate) fn semaphore_create(name: &str) -> Option { + let s = CString::new(name).unwrap(); + let raw; + unsafe { + raw = rt_sem_create(s.as_ptr(), 0, 0); + } + return if raw == ptr::null_mut() { + None + } else { + Some(raw) + }; +} + +#[inline] +pub(crate) fn semaphore_try_take(handle: APIRawSem) -> RttCResult { + unsafe { rt_sem_trytake(handle).into() } +} + +#[inline] +pub(crate) fn semaphore_take(handle: APIRawSem, tick: u32) -> RttCResult { + unsafe { rt_sem_take(handle, tick).into() } +} + +#[inline] +pub(crate) fn semaphore_release(handle: APIRawSem) -> RttCResult { + unsafe { rt_sem_release(handle).into() } +} + +#[inline] +pub(crate) fn semaphore_delete(handle: APIRawSem) { + unsafe { + let _ = rt_sem_delete(handle); + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/api/thread.rs b/machines/qemu-virt-riscv64/rust/core/src/api/thread.rs new file mode 100644 index 00000000..7b967d54 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/api/thread.rs @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Thread API + */ +use super::base::*; +use crate::bindings::*; +use core::ptr; +use core::ffi::c_void; +use alloc::ffi::CString; + +// Thread handle, defined by c code +pub type APIRawThread = rt_thread_t; + +// Thread entry function, Defined by rtt c code +pub type ThreadEntry = extern "C" fn(parameter: *mut c_void); + +// Create a new thread +// Return None:OOM Some():thread handle +#[inline] +pub fn thread_create( + name: &str, + entry: ThreadEntry, + param: *mut c_void, + stack_size: u32, + priority: u8, + tick: u32, +) -> Option { + let name = CString::new(name).unwrap(); + let raw; + unsafe { + raw = rt_thread_create( + name.as_ptr(), + entry, + param, + stack_size as crate::bindings::librt::rt_size_t, + priority, + tick, + ); + } + if raw == ptr::null_mut() { + None + } else { + Some(raw) + } +} + +// Delete a thread from system +#[inline] +pub fn thread_delete(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_delete(th).into() } +} + +// Get current thread +#[inline] +pub fn thread_self() -> Option { + let ret; + unsafe { + ret = rt_thread_self(); + } + if ret == ptr::null_mut() { + None + } else { + Some(ret) + } +} + +// Startup a thread +#[inline] +pub fn thread_startup(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_startup(th).into() } +} + +// Thread have a sleep +#[inline] +pub fn thread_delay(ticks: usize) -> RttCResult { + unsafe { rt_thread_delay(ticks as _).into() } +} + +// Thread have a ms sleep +#[inline] +pub fn thread_m_delay(ms: i32) -> RttCResult { + unsafe { rt_thread_mdelay(ms as _).into() } +} + +// Yield +#[inline] +pub fn thread_yield() { + unsafe { + rt_thread_yield(); + } +} + +// Suspend a thread +#[inline] +pub fn thread_suspend(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_suspend(th).into() } +} + +// Resume a thread +#[inline] +pub fn thread_resume(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_resume(th).into() } +} diff --git a/machines/qemu-virt-riscv64/rust/core/src/bindings/libc.rs b/machines/qemu-virt-riscv64/rust/core/src/bindings/libc.rs new file mode 100644 index 00000000..b01a7ffd --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/bindings/libc.rs @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author foxglove + * 2025-09-15 foxglove 1.0 version + * 2025-09-23 foxglove 1.1 version, add libdl bindings + */ + +//! C standard library API bindings +//! +//! Provides Rust wrappers for standard C library functions + +#![allow(non_camel_case_types)] + +pub use core::ffi::{c_char, c_int, c_void, c_long, c_ulong}; + +// RT-Thread libdl typically supports these flags; define minimally used ones +pub const RTLD_LAZY: c_int = 0x00000; // Lazy function call binding +pub const RTLD_NOW: c_int = 0x00001; // Immediate function call binding +pub const RTLD_GLOBAL: c_int = 0x10000; // Make symbols globally available +pub const RTLD_LOCAL: c_int = 0x00000; // Default local +// fcntl.h interent constants +pub const O_RDONLY: c_int = 0x0000; // open for reading only +pub const O_WRONLY: c_int = 0x0001; // open for writing only +pub const O_RDWR: c_int = 0x0002; // open for reading and writing +pub const O_APPEND: c_int = 0x0008; // writes done at eof +pub const O_CREAT: c_int = 0x0100; // create and open file +pub const O_TRUNC: c_int = 0x0200; // open and truncate +pub const O_EXCL: c_int = 0x0400; // open only if file doesn't already exist +pub type size_t = c_ulong; +pub type ssize_t = c_long; +pub type off_t = c_long; +// lseek whence constants +pub const SEEK_SET: c_int = 0; +pub const SEEK_CUR: c_int = 1; +pub const SEEK_END: c_int = 2; +// ============== time functions ============== +pub type time_t = c_long; +pub type suseconds_t = c_int; +#[repr(C)] +pub struct timeval { + pub tv_sec: time_t, + pub tv_usec: suseconds_t, +} + +// ============== libdl functions ============== +unsafe extern "C" { + pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void; + pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void; + pub fn dlclose(handle: *mut c_void) -> c_int; + pub fn dlerror() -> *const c_char; +} +unsafe extern "C" { + pub fn open(path: *const c_char, oflag: c_int, ...) -> c_int; + pub fn close(fd: c_int) -> c_int; + pub fn read(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t; + pub fn write(fd: c_int, buf: *const c_void, count: size_t) -> ssize_t; + pub fn lseek(fd: c_int, offset: off_t, whence: c_int) -> off_t; + pub fn fsync(fd: c_int) -> c_int; + pub fn ftruncate(fd: c_int, length: off_t) -> c_int; +} + +// ============== time functions ============== +unsafe extern "C" { + // Use local types instead of crate-root paths to avoid E0412 + pub fn gettimeofday(tp: *mut timeval, tz: *mut c_void) -> c_int; +} + +/// Helper: get last libdl error C-string pointer +/// Safe wrapper around `dlerror()` returning the raw pointer for printing. +#[inline] +pub fn last_error_ptr() -> *const c_char { + unsafe { dlerror() } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/bindings/librt.rs b/machines/qemu-virt-riscv64/rust/core/src/bindings/librt.rs new file mode 100644 index 00000000..c209e102 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/bindings/librt.rs @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author foxglove + * 2025-09-15 foxglove 1.0 version + */ + +//! RT-Thread OS API bindings +//! +//! Provides Rust wrappers for RT-Thread kernel and device driver APIs + +#![allow(non_camel_case_types)] + +use core::ffi::{c_char, c_int, c_uint, c_void, c_long, c_ulong, c_uchar}; + +// RT-Thread basic type definitions +pub type rt_base_t = c_long; +pub type rt_ubase_t = c_ulong; +pub type rt_err_t = rt_base_t; +pub type rt_uint32_t = c_uint; +pub type rt_int32_t = c_int; +pub type rt_uint8_t = c_uchar; +pub type rt_tick_t = rt_uint32_t; +pub type rt_size_t = rt_ubase_t; + +pub type rt_thread_t = *mut c_void; +pub type rt_sem_t = *mut c_void; +pub type rt_mutex_t = *mut c_void; +pub type rt_device_t = *mut c_void; +pub type rt_mq_t = *mut c_void; + +// RT-Thread error codes +pub const RT_EOK: rt_err_t = 0; +pub const RT_ERROR: rt_err_t = 1; +pub const RT_ETIMEOUT: rt_err_t = 2; +pub const RT_EFULL: rt_err_t = 3; +pub const RT_EEMPTY: rt_err_t = 4; +pub const RT_ENOMEM: rt_err_t = 5; +pub const RT_ENOSYS: rt_err_t = 6; +pub const RT_EBUSY: rt_err_t = 7; +pub const RT_EIO: rt_err_t = 8; +pub const RT_EINTR: rt_err_t = 9; +pub const RT_EINVAL: rt_err_t = 10; + +// ============== Kernel object management ============== +unsafe extern "C" { + pub fn rt_object_get_type(object: *mut c_void) -> u8; + pub fn rt_object_find(name: *const c_char, object_type: u8) -> *mut c_void; +} + +// ============== Thread management ============== +unsafe extern "C" { + pub fn rt_thread_create( + name: *const c_char, + entry: extern "C" fn(*mut c_void), + parameter: *mut c_void, + stack_size: rt_size_t, + priority: rt_uint8_t, + tick: rt_tick_t, + ) -> rt_thread_t; + + pub fn rt_thread_delete(thread: rt_thread_t) -> rt_err_t; + pub fn rt_thread_startup(thread: rt_thread_t) -> rt_err_t; + pub fn rt_thread_self() -> rt_thread_t; + pub fn rt_thread_yield() -> rt_err_t; + pub fn rt_thread_delay(tick: rt_tick_t) -> rt_err_t; + pub fn rt_thread_mdelay(ms: rt_uint32_t) -> rt_err_t; + pub fn rt_thread_suspend(thread: rt_thread_t) -> rt_err_t; + pub fn rt_thread_resume(thread: rt_thread_t) -> rt_err_t; +} + +// ============== Semaphore management ============== +unsafe extern "C" { + pub fn rt_sem_create(name: *const c_char, value: rt_uint32_t, flag: rt_uint8_t) -> rt_sem_t; + pub fn rt_sem_delete(sem: rt_sem_t) -> rt_err_t; + pub fn rt_sem_take(sem: rt_sem_t, time: rt_tick_t) -> rt_err_t; + pub fn rt_sem_trytake(sem: rt_sem_t) -> rt_err_t; + pub fn rt_sem_release(sem: rt_sem_t) -> rt_err_t; +} + +// ============== Mutex management ============== +unsafe extern "C" { + pub fn rt_mutex_create(name: *const c_char, flag: rt_uint8_t) -> rt_mutex_t; + pub fn rt_mutex_delete(mutex: rt_mutex_t) -> rt_err_t; + pub fn rt_mutex_take(mutex: rt_mutex_t, time: rt_tick_t) -> rt_err_t; + pub fn rt_mutex_release(mutex: rt_mutex_t) -> rt_err_t; +} +// ============== Message queue management ============== +unsafe extern "C" { + pub fn rt_mq_create(name: *const c_char, msg_size: rt_size_t, max_msgs: rt_size_t, flag: rt_uint8_t) -> rt_mq_t; + pub fn rt_mq_send(mq: rt_mq_t, buffer: *const c_void, size: rt_size_t) -> rt_err_t; + pub fn rt_mq_send_wait(mq: rt_mq_t, buffer: *const c_void, size: rt_size_t, timeout: rt_int32_t) -> rt_err_t; + pub fn rt_mq_recv(mq: rt_mq_t, buffer: *mut c_void, size: rt_size_t, timeout: rt_int32_t) -> rt_base_t; + pub fn rt_mq_delete(mq: rt_mq_t) -> rt_err_t; + pub fn rt_mq_detach(mq: rt_mq_t) -> rt_err_t; +} +// ============== Memory management ============== +unsafe extern "C" { + pub fn rt_malloc(size: rt_size_t) -> *mut c_void; + pub fn rt_free(ptr: *mut c_void); + pub fn rt_realloc(ptr: *mut c_void, newsize: rt_size_t) -> *mut c_void; + pub fn rt_calloc(count: rt_size_t, size: rt_size_t) -> *mut c_void; + pub fn rt_malloc_align(size: rt_size_t, align: rt_size_t) -> *mut c_void; + pub fn rt_free_align(ptr: *mut c_void); +} + +// ============== Device management ============== +unsafe extern "C" { + pub fn rt_device_find(name: *const c_char) -> rt_device_t; + pub fn rt_device_open(dev: rt_device_t, oflag: u16) -> rt_err_t; + pub fn rt_device_close(dev: rt_device_t) -> rt_err_t; + pub fn rt_device_read( + dev: rt_device_t, + pos: c_ulong, + buffer: *mut c_void, + size: rt_size_t, + ) -> rt_size_t; + pub fn rt_device_write( + dev: rt_device_t, + pos: c_ulong, + buffer: *const c_void, + size: rt_size_t, + ) -> rt_size_t; + pub fn rt_device_control(dev: rt_device_t, cmd: c_int, arg: *mut c_void) -> rt_err_t; +} + +// ============== System tick ============== +unsafe extern "C" { + pub fn rt_tick_get() -> rt_tick_t; + pub fn rt_tick_from_millisecond(ms: c_int) -> rt_tick_t; +} + +// ============== Debug output ============== +unsafe extern "C" { + pub fn rt_kprintf(fmt: *const u8, ...) -> c_int; + pub fn rt_kputs(str: *const u8) -> c_int; +} + +// ============== Interrupt management ============== +unsafe extern "C" { + pub fn rt_hw_interrupt_disable() -> rt_base_t; + pub fn rt_hw_interrupt_enable(level: rt_base_t); + pub fn rt_cpus_lock() -> rt_base_t; + pub fn rt_cpus_unlock(level: rt_base_t); + pub fn rt_interrupt_enter(); + pub fn rt_interrupt_leave(); + pub fn rt_interrupt_get_nest() -> u8; +} + +/// Safe RT-Thread memory allocation +pub fn rt_safe_malloc(size: usize) -> Option<*mut c_void> { + if size == 0 { + None + } else { + let ptr = unsafe { rt_malloc(size as rt_size_t) }; + if ptr.is_null() { + None + } else { + Some(ptr) + } + } +} + +/// Safe RT-Thread memory deallocation +pub fn rt_safe_free(ptr: *mut c_void) { + if !ptr.is_null() { + unsafe { rt_free(ptr) } + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/bindings/mod.rs b/machines/qemu-virt-riscv64/rust/core/src/bindings/mod.rs new file mode 100644 index 00000000..3a10a576 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/bindings/mod.rs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Bindings module + */ +pub mod libc; +pub mod librt; + +// Explicitly re-export only the needed items from librt to avoid namespace pollution. +// Basic RT-Thread types +pub use librt::{ + rt_base_t, rt_ubase_t, rt_err_t, rt_uint32_t, rt_int32_t, rt_uint8_t, + rt_tick_t, rt_size_t, rt_thread_t, rt_sem_t, rt_mutex_t, rt_device_t, rt_mq_t +}; + +// RT-Thread error codes +pub use librt::{ + RT_EOK, RT_ERROR, RT_ETIMEOUT, RT_EFULL, RT_EEMPTY, RT_ENOMEM, + RT_ENOSYS, RT_EBUSY, RT_EIO, RT_EINTR, RT_EINVAL +}; + +// Thread management functions +pub use librt::{ + rt_thread_create, rt_thread_delete, rt_thread_startup, rt_thread_self, + rt_thread_yield, rt_thread_delay, rt_thread_mdelay, rt_thread_suspend, rt_thread_resume +}; + +// Semaphore management functions +pub use librt::{ + rt_sem_create, rt_sem_delete, rt_sem_take, rt_sem_trytake, rt_sem_release +}; + +// Mutex management functions +pub use librt::{ + rt_mutex_create, rt_mutex_delete, rt_mutex_take, rt_mutex_release +}; + +// Message queue management functions +pub use librt::{ + rt_mq_create, rt_mq_send, rt_mq_send_wait, rt_mq_recv, rt_mq_delete, rt_mq_detach +}; + +// Memory management functions +pub use librt::{ + rt_malloc, rt_free, rt_realloc, rt_calloc, rt_malloc_align, rt_free_align, + rt_safe_malloc, rt_safe_free +}; + +// Device management functions +pub use librt::{ + rt_device_find, rt_device_open, rt_device_close, rt_device_read, + rt_device_write, rt_device_control +}; + +// System tick functions +pub use librt::{ + rt_tick_get, rt_tick_from_millisecond +}; + +// Debug output functions +pub use librt::{ + rt_kprintf, rt_kputs +}; + +// Interrupt management functions +pub use librt::{ + rt_hw_interrupt_disable, rt_hw_interrupt_enable, rt_cpus_lock, rt_cpus_unlock, + rt_interrupt_enter, rt_interrupt_leave, rt_interrupt_get_nest +}; + +// Object management functions +pub use librt::{ + rt_object_get_type, rt_object_find +}; diff --git a/machines/qemu-virt-riscv64/rust/core/src/fs.rs b/machines/qemu-virt-riscv64/rust/core/src/fs.rs new file mode 100644 index 00000000..ca3e5dce --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/fs.rs @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove micro rust log component + */ +use alloc::string::{String, ToString}; +use alloc::ffi::CString; +use crate::RTResult; +use crate::RTTError::{FileCloseErr, FileFlushErr, FileOpenErr, FileReadErr, FileSeekErr, FileSetLengthErr, FileWriteErr}; +use crate::bindings::libc; +use core::ffi::{c_void, c_int}; + +pub struct File { + pub fd: i32, +} + +pub struct OpenOptions { + pub path: String, + pub read: bool, + pub write: bool, + pub create: bool, + pub append: bool, + pub truncate: bool, +} + +impl OpenOptions { + pub fn new() -> Self { + Self { path: String::new(), read: false, write: false, create: false, append: false, truncate: false } + } + + pub fn read(&mut self, read: bool) -> &mut Self { self.read = read; self } + pub fn write(&mut self, write: bool) -> &mut Self { self.write = write; self } + pub fn create(&mut self, create: bool) -> &mut Self { self.create = create; self } + pub fn append(&mut self, append: bool) -> &mut Self { self.append = append; self } + pub fn truncate(&mut self, truncate: bool) -> &mut Self { self.truncate = truncate; self } + + fn flags(&self) -> c_int { + let mut flags: c_int = 0; + flags |= if self.read && self.write { libc::O_RDWR } else if self.write { libc::O_WRONLY } else { libc::O_RDONLY }; + if self.create { flags |= libc::O_CREAT; } + if self.append { flags |= libc::O_APPEND; } + if self.truncate { flags |= libc::O_TRUNC; } + flags + } + + pub fn open(&mut self, path: &str) -> RTResult { + self.path = path.to_string(); + let cpath = match CString::new(self.path.clone()) { Ok(s) => s, Err(_) => return Err(FileOpenErr) }; + let oflags = self.flags(); + let fd = unsafe { libc::open(cpath.as_ptr(), oflags, 0o666 as c_int) }; + if fd < 0 { Err(FileOpenErr) } else { Ok(File { fd }) } + } +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { libc::close(self.fd); } + } +} + +impl File { + pub fn read_to_string(&self) -> RTResult { + let mut buf = [0u8; 256]; + let mut out = String::new(); + self.seek(0)?; + loop { + let n = self.read(&mut buf)?; + if n == 0 { break; } + let s = alloc::string::String::from_utf8_lossy(&buf[..n]).into_owned(); + out.push_str(&s); + } + Ok(out) + } + + pub fn write_all(&self, buf: &str) -> RTResult<()> { + let mut written = 0usize; + let bytes = buf.as_bytes(); + while written < bytes.len() { + let n = self.write(&bytes[written..])?; + written += n; + } + Ok(()) + } + + pub fn read(&self, buf: &mut [u8]) -> RTResult { + let n = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len() as libc::size_t) }; + if n < 0 { Err(FileReadErr) } else { Ok(n as usize) } + } + + pub fn write(&self, buf: &[u8]) -> RTResult { + let n = unsafe { libc::write(self.fd, buf.as_ptr() as *const c_void, buf.len() as libc::size_t) }; + if n < 0 { Err(FileWriteErr) } else { Ok(n as usize) } + } + + pub fn seek(&self, offset: i64) -> RTResult { + let n = unsafe { libc::lseek(self.fd, offset as libc::off_t, libc::SEEK_SET) }; + if n < 0 { Err(FileSeekErr) } else { Ok(n) } + } + + pub fn flush(&self) -> RTResult<()> { + let n = unsafe { libc::fsync(self.fd) }; + if n < 0 { Err(FileFlushErr) } else { Ok(()) } + } + + pub fn set_len(&self, len: i64) -> RTResult<()> { + let n = unsafe { libc::ftruncate(self.fd, len as libc::off_t) }; + if n < 0 { Err(FileSetLengthErr) } else { Ok(()) } + } + + pub fn close(&self) -> RTResult<()> { + let n = unsafe { libc::close(self.fd) }; + if n < 0 { Err(FileCloseErr) } else { Ok(()) } + } +} diff --git a/machines/qemu-virt-riscv64/rust/core/src/init.rs b/machines/qemu-virt-riscv64/rust/core/src/init.rs new file mode 100644 index 00000000..5d9d29d4 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/init.rs @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-09-15 foxglove 1.0 version + */ + +//! Rust component initialization module +//! +//! Handles the initialization of the Rust component within RT-Thread + +use crate::println; + +/// Component initialization function +/// This function is called during RT-Thread system initialization +#[no_mangle] +pub extern "C" fn rust_init() -> i32 { + // Test the print! and println! macros + println!("[rust_init]: Rust component initialized!"); + 0 +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/lib.rs b/machines/qemu-virt-riscv64/rust/core/src/lib.rs new file mode 100644 index 00000000..56e40b6c --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/lib.rs @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-09-15 foxglove 1.0 version + * 2025-09-25 foxglove 1.1 version + * 2025-10-10 foxglove latest version + */ + +//! RT-THREAD & RUST +//! +//! A simple and easy-to-use system support library +//! that provides basic functions and FS, NET and DEVICE. +//! +//! You can use this library on embedded devices that support rt-thread + +#![no_std] +#![feature(alloc_error_handler)] +#![feature(linkage)] +#![allow(dead_code)] + +pub extern crate alloc; + +#[doc = "Alloc by rt-thread"] +#[global_allocator] +static GLOBAL: allocator::RttAlloc = allocator::RttAlloc; +pub mod api; +pub mod bindings; + +pub mod init; +pub mod allocator; +pub mod mutex; +pub mod out; +pub mod fs; +pub mod panic; +pub mod param; +pub mod queue; +pub mod sem; +pub mod thread; +pub mod time; +pub mod libloader; + +mod prelude; +pub use prelude::v1::*; + +// Re-export initialization function +pub use init::rust_init; + +// TODO: review this enum +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum RTTError { + ThreadStartupErr, + MutexTakeTimeout, + SemaphoreTakeTimeout, + QueueSendTimeout, + QueueReceiveTimeout, + OutOfMemory, + + DeviceNotFound, + DeviceOpenFailed, + DeviceCloseFailed, + DeviceReadFailed, + DeviceWriteFailed, + DeviceTransFailed, + DeviceConfigFailed, + DeviceSetRxCallBackFailed, + DeviceSetTxCallBackFailed, + + FileOpenErr, + FileCloseErr, + FileReadErr, + FileWriteErr, + FileSeekErr, + FileFlushErr, + FileDeleteErr, + FileSetLengthErr, + FileSyncErr, + + FuncUnDefine, +} + +pub type RTResult = Result; \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/libloader.rs b/machines/qemu-virt-riscv64/rust/core/src/libloader.rs new file mode 100644 index 00000000..a95c6aec --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/libloader.rs @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-14 foxglove High-level libdl facade (libloader-style) + */ + +//! High-level dynamic loader facade built on `api::libloading`. +//! +//! This module mirrors a minimal subset of `libloader`/`libloading` usage +//! patterns for embedded `no_std` by operating on C strings. +//! It provides convenience helpers and re-exports for common flags. + +use core::ffi::{c_char, c_int}; +pub use crate::api::libloading as libloading; +pub use libloading::{RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NOW, last_error}; +use libloading::{DlError, Library, Symbol}; +use crate::println; + +/// Open a module with `RTLD_NOW | RTLD_GLOBAL` convenience flags. +/// +/// Safety: `path` must be a valid NUL-terminated C string pointer. +pub unsafe fn dl_open(path: *const c_char) -> Result { + Library::open_global(path) +} + +/// Open a module with custom flags. +/// +/// Safety: `path` must be a valid NUL-terminated C string pointer. +pub unsafe fn dl_open_with_flags(path: *const c_char, flags: c_int) -> Result { + Library::open(path, flags) +} + +/// Resolve a typed symbol from a library. +/// +/// Safety: Caller must ensure `T` matches the actual symbol type. +pub unsafe fn dl_sym<'lib, T>(lib: &'lib Library, symbol: *const c_char) -> Result, DlError> { + lib.get_ptr(symbol) +} + +/// Print the last libdl error using RT-Thread `printf`. +pub fn dl_print_last_error() { + println!("libdl error: {}", libloading::DlError::Open(last_error())); +} + +/// Use a library for a scoped operation; the library is closed on drop. +/// +/// Safety: `path` must be a valid NUL-terminated C string pointer. +pub unsafe fn with_library(path: *const c_char, flags: c_int, f: F) -> Result +where + F: FnOnce(&Library) -> R, +{ + let lib = Library::open(path, flags)?; + let result = f(&lib); + // Drop will handle closing; return the result + Ok(result) +} + +#[macro_export] +/// ## Get a function from a dynamic link library +/// *You need `use libloader::libloading` first* +/// * `lib_path`: the path of DLL +/// * `fn_name`: The function name from dll +/// * `call_name`: The call function name of `fn_name` +/// * `ret`: return type of the function **if the function don't have return value, use "()" instead** +/// * `(value: type)`: **(variadic argument)** The arguments of the function from dll +/// +macro_rules! get_libfn { + ($lib_path: expr, $fn_name: expr, $call_name: ident, $ret: ty, $($v: ident: $t:ty),*) => { + pub fn $call_name($($v: $t),*) -> $ret { + let lib = $crate::libloader::libloading::Library::new($lib_path).unwrap(); + let func: $crate::libloader::libloading::Symbol $ret> = lib.get($fn_name.as_bytes()).unwrap(); + func($($v,)*) + } + }; + ($lib_path: expr, $fn_name: expr, $call_name:ident, $ret: ty) => { + pub fn $call_name() -> $ret { + let lib = $crate::libloader::libloading::Library::new($lib_path).unwrap(); + let func: $crate::libloader::libloading::Symbol $ret> = lib.get($fn_name.as_bytes()).unwrap(); + func() + } + }; +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/mutex.rs b/machines/qemu-virt-riscv64/rust/core/src/mutex.rs new file mode 100644 index 00000000..700f6ea1 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/mutex.rs @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Mutex implementation + */ +use crate::api::*; +use crate::{panic::panic_on_atomic_context, RTTError}; +use alloc::fmt; +pub use alloc::sync::{Arc, Weak}; +use core::cell::UnsafeCell; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::*; + +const RT_WAITING_FOREVER: isize = -1; + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +pub struct Mutex +where + M: RawMutexOps + Sized, +{ + mutex: M, + data: UnsafeCell, +} + +// Impl for Default Mutex +impl Mutex { + pub fn new(t: T) -> Result { + Ok(Mutex { + mutex: SleepMutex::create("Unnamed")?, + data: UnsafeCell::new(t), + }) + } + + pub fn new_with_name(t: T, name: &str) -> Result { + Ok(Mutex { + mutex: SleepMutex::create(name)?, + data: UnsafeCell::new(t), + }) + } + + pub fn try_lock(&self, max_wait: isize) -> Result, RTTError> { + self.mutex.take(max_wait)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } + + pub fn lock(&self) -> Result, RTTError> { + self.mutex.take(RT_WAITING_FOREVER)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } +} + +// Impl for all Mutex +impl Mutex { + pub fn spec_new(t: T) -> Result { + Ok(Mutex { + mutex: M::create("Unnamed")?, + data: UnsafeCell::new(t), + }) + } + + pub fn spec_new_with_name(t: T, name: &str) -> Result { + Ok(Mutex { + mutex: M::create(name)?, + data: UnsafeCell::new(t), + }) + } + + pub fn spec_try_lock(&self, max_wait: isize) -> Result, RTTError> { + self.mutex.take(max_wait)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } + + pub fn spec_lock(&self) -> Result, RTTError> { + self.mutex.take(RT_WAITING_FOREVER)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Mutex address: {:?}", self.mutex) + } +} + +pub struct MutexGuard<'a, M: RawMutexOps + 'a, T: ?Sized + 'a> { + __mutex: &'a M, + __data: &'a UnsafeCell, +} + +impl<'mutex, M: RawMutexOps, T: ?Sized> Deref for MutexGuard<'mutex, M, T> { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.__data.get() } + } +} + +impl<'mutex, M: RawMutexOps, T: ?Sized> DerefMut for MutexGuard<'mutex, M, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.__data.get() } + } +} + +impl<'a, M: RawMutexOps, T: ?Sized> Drop for MutexGuard<'a, M, T> { + fn drop(&mut self) { + self.__mutex.release(); + } +} + +pub type CommonMutex = SleepMutex; +pub struct SleepMutex(APIRawMutex); + +impl fmt::Debug for SleepMutex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +pub struct AtomicMutex(UnsafeCell, AtomicBool); + +pub trait RawMutexOps: Sized { + fn create(name: &str) -> Result; + fn take(&self, max_wait: isize) -> Result<(), RTTError>; + fn release(&self); + fn drop(&mut self); +} + +impl RawMutexOps for AtomicMutex { + fn create(_name: &str) -> Result { + Ok(AtomicMutex { + 0: UnsafeCell::new(INTERRUPT_FLAG_INIT), + 1: AtomicBool::new(false), + }) + } + + fn take(&self, max_wait: isize) -> Result<(), RTTError> { + let f; + unsafe { + f = self.0.get(); + *f = interrupt_disable(); + } + let ret = if max_wait == RT_WAITING_FOREVER { + while self + .1 + .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + while self.1.load(Ordering::Relaxed) {} + } + Ok(()) + } else { + if self + .1 + .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Ok(()) + } else { + unsafe { + interrupt_enable(*f); + } + Err(RTTError::MutexTakeTimeout) + } + }; + + return ret; + } + + fn release(&self) { + self.1.store(false, Ordering::Release); + let f; + unsafe { + f = self.0.get(); + interrupt_enable(*f); + } + } + + fn drop(&mut self) {} +} + +impl RawMutexOps for SleepMutex { + fn create(name: &str) -> Result { + panic_on_atomic_context("create"); + mutex_create(name) + .ok_or(RTTError::OutOfMemory) + .map(|m| SleepMutex(m)) + } + + fn take(&self, max_wait: isize) -> Result<(), RTTError> { + panic_on_atomic_context("take"); + let ret = mutex_take(self.0, max_wait); + if !is_eok(ret) { + return Err(RTTError::MutexTakeTimeout); + } + Ok(()) + } + + fn release(&self) { + panic_on_atomic_context("release"); + mutex_release(self.0); + } + + fn drop(&mut self) { + panic_on_atomic_context("drop"); + mutex_delete(self.0); + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/out.rs b/machines/qemu-virt-riscv64/rust/core/src/out.rs new file mode 100644 index 00000000..3a3b2d65 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/out.rs @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Output implementation + */ +use crate::bindings::librt::rt_kprintf; +use core::fmt::{self, Write}; +use core::cmp::min; + +fn up_cast(a: usize, b: usize) -> usize { + let r = a / b; + return if a % b == 0 { r } else { r + 1 }; +} + +fn puts(str: &str, kp: fn(s: *const u8)) { + let str = str.as_bytes(); + let mut buf = [0 as u8; 129]; + for i in 0..up_cast(str.len(), 128) { + let end = min(128, str.len() - i * 128); + for j in 0..end { + buf[j] = str[(j + i * 128) as usize]; + } + buf[end] = 0; + kp(buf.as_ptr()) + } +} + +struct StdOut; + +impl fmt::Write for StdOut { + fn write_str(&mut self, s: &str) -> fmt::Result { + fn rtt_kputs(s: *const u8) { + unsafe { + // Use kprintf with "%s" format to avoid dependency on rt_kputs symbol + rt_kprintf(b"%s\0".as_ptr(), s); + } + } + puts(s, rtt_kputs); + Ok(()) + } +} + +pub fn _print(args: fmt::Arguments) { + unsafe { + StdOut.write_fmt(args).unwrap_unchecked(); + } +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ({ + $crate::out::_print(format_args!($($arg)*)); + }); +} + +#[macro_export] +macro_rules! println { + () => ({ + $crate::out::_print(format_args!("\n")); + }); + ($($arg:tt)*) => ({ + $crate::out::_print(format_args!("{}\n", format_args!($($arg)*))); + }); +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/panic.rs b/machines/qemu-virt-riscv64/rust/core/src/panic.rs new file mode 100644 index 00000000..6c42c376 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/panic.rs @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Panic implementation + */ +//! Panic handling for RT-Thread Rust runtime +//! +//! This module provides panic handling functionality for the RT-Thread Rust runtime, +//! including context-aware panic handling and the global panic handler. + +use crate::print; + +/// Check if we're in an atomic context and panic if so +/// +/// This function checks if the current execution context is an interrupt context +/// and panics with a descriptive message if it is, as certain operations are +/// not safe to perform in interrupt contexts. +/// +/// # Arguments +/// +/// * `s` - A string slice describing the operation that triggered the check +pub fn panic_on_atomic_context(s: &str) { + use crate::api::is_irq_context; + if is_irq_context() { + panic!("In irq context {}", s); + } +} + +/// Global panic handler for the RT-Thread Rust runtime +/// +/// This function is called when a panic occurs in the Rust code. It prints +/// the panic information and then calls the weak `__rust_panic` function +/// to handle the actual panic behavior. +/// +/// # Arguments +/// +/// * `info` - Panic information containing details about the panic +#[panic_handler] +#[inline(never)] +fn panic(info: &core::panic::PanicInfo) -> ! { + print!("{:}", info); + __rust_panic() +} + +/// Weak panic implementation +/// +/// This is a weak symbol that can be overridden by user code to provide +/// custom panic behavior. The default implementation simply loops forever. +/// +/// # Safety +/// +/// This function is marked as `unsafe` because it's a no-mangle function +/// that can be overridden by external code. +#[linkage = "weak"] +#[unsafe(no_mangle)] +fn __rust_panic() -> ! { + // Default weak panic handler: intentionally loops forever to halt execution. + // Override this function for custom panic behavior. + print!("Entered weak panic handler: system will halt."); + loop {} +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/param.rs b/machines/qemu-virt-riscv64/rust/core/src/param.rs new file mode 100644 index 00000000..3bae8a85 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/param.rs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Parameter module + */ + +use alloc::vec::Vec; +use core::iter::IntoIterator; +use core::ops::Deref; + +#[derive(Debug)] +pub struct ParamItem(&'static [u8]); + +impl Deref for ParamItem { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + return self.0; + } +} + +impl ParamItem { + pub fn new(raw: &'static [u8]) -> Self { + ParamItem(raw) + } +} + +pub type Param = as IntoIterator>::IntoIter; \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/prelude/mod.rs b/machines/qemu-virt-riscv64/rust/core/src/prelude/mod.rs new file mode 100644 index 00000000..f9cdb82f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/prelude/mod.rs @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Prelude module + */ +#[path = "no_std.rs"] +pub mod v1; diff --git a/machines/qemu-virt-riscv64/rust/core/src/prelude/no_std.rs b/machines/qemu-virt-riscv64/rust/core/src/prelude/no_std.rs new file mode 100644 index 00000000..9de1e30c --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/prelude/no_std.rs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Prelude module + */ +pub use core::cell::RefCell; +pub use core::cell::UnsafeCell; +pub use core::cmp::*; +pub use core::fmt; +pub use core::ptr::write_bytes; +pub use core::iter::Iterator; +pub use core::marker::PhantomData; +pub use core::mem; +pub use core::num::Wrapping; +pub use core::ops::Range; +pub use core::ops::{Deref, DerefMut}; + +pub use alloc::boxed::Box; +pub use alloc::rc::Rc; +pub use alloc::sync::{Arc, Weak}; + +pub use alloc::collections; +pub use alloc::string; +pub use alloc::vec; diff --git a/machines/qemu-virt-riscv64/rust/core/src/queue.rs b/machines/qemu-virt-riscv64/rust/core/src/queue.rs new file mode 100644 index 00000000..1be3f404 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/queue.rs @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Queue implementation + */ +//! Passing information between threads + +use crate::api::*; +use crate::{panic::panic_on_atomic_context, RTTError}; +use core::cell::UnsafeCell; +use core::marker::PhantomData; +use core::mem::{size_of, MaybeUninit}; +use core::ffi::c_void; + +const RT_WAITING_FOREVER: isize = -1; + +unsafe impl Send for Queue where T: Send {} +unsafe impl Sync for Queue where T: Send {} + +#[derive(Debug)] +pub struct Queue { + queue: APIRawQueue, + item_type: PhantomData>, +} + +impl Queue { + pub fn new(max_size: usize) -> Result, RTTError> { + queue_create("Unnamed", max_size as _, size_of::() as _) + .ok_or(RTTError::OutOfMemory) + .map(|m| Queue { + queue: m, + item_type: PhantomData, + }) + } + + pub fn new_with_name(name: &str, max_size: usize) -> Result, RTTError> { + queue_create(name, max_size as _, size_of::() as _) + .ok_or(RTTError::OutOfMemory) + .map(|m| Queue { + queue: m, + item_type: PhantomData, + }) + } + + pub fn try_send(&self, item: T) -> Result<(), (RTTError, T)> { + self._send(item, 0) + } + + pub fn send(&self, item: T, max_wait: i32) -> Result<(), (RTTError, T)> { + panic_on_atomic_context("send"); + self._send(item, max_wait) + } + + pub fn send_wait_forever(&self, item: T) -> Result<(), (RTTError, T)> { + panic_on_atomic_context("send_wait_forever"); + self._send(item, RT_WAITING_FOREVER as _) + } + + fn _send(&self, item: T, max_wait: i32) -> Result<(), (RTTError, T)> { + let inner = MaybeUninit::new(item); + let ret = queue_send_wait( + self.queue, + inner.as_ptr() as *const c_void, + size_of::() as _, + max_wait, + ); + return if !is_eok(ret) { + unsafe { Err((RTTError::QueueSendTimeout, inner.assume_init())) } + } else { + Ok(()) + }; + } + + pub fn try_recv(&self) -> Result { + self._receive(0) + } + + pub fn recv(&self, max_wait: i32) -> Result { + panic_on_atomic_context("recv"); + self._receive(max_wait) + } + + pub fn recv_wait_forever(&self) -> Result { + panic_on_atomic_context("recv_wait_forever"); + self._receive(RT_WAITING_FOREVER as _) + } + + fn _receive(&self, max_wait: i32) -> Result { + let mut inner = MaybeUninit::::uninit(); + let ret = queue_receive_wait( + self.queue, + inner.as_mut_ptr() as *mut c_void, + size_of::() as _, + max_wait, + ); + return if is_eok(ret) { + Ok(unsafe { inner.assume_init() }) + } else { + Err(RTTError::QueueReceiveTimeout) + }; + } +} + +impl Drop for Queue { + fn drop(&mut self) { + queue_delete(self.queue); + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/sem.rs b/machines/qemu-virt-riscv64/rust/core/src/sem.rs new file mode 100644 index 00000000..c5376020 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/sem.rs @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Semaphore implementation + */ +#![allow(dead_code)] + +use crate::api::*; +use crate::{panic::panic_on_atomic_context, RTTError}; +use core::cell::UnsafeCell; +use core::marker::PhantomData; + +const RT_WAITING_FOREVER: i32 = -1; + +unsafe impl Send for Semaphore {} +unsafe impl Sync for Semaphore {} + +pub struct Semaphore { + data: PhantomData<*const UnsafeCell>, + sem: APIRawSem +} + +impl Semaphore { + pub fn new() -> Result { + Self::new_with_name("unknown") + } + + pub fn new_with_name(name: &str) -> Result { + panic_on_atomic_context("new_with_name"); + semaphore_create(name) + .ok_or(RTTError::OutOfMemory) + .map(|m| Semaphore { + data: Default::default(), + sem: m + }) + } + + pub fn try_take(&self) -> Result<(), RTTError> { + let m = semaphore_try_take(self.sem); + if !is_eok(m) { + return Err(RTTError::SemaphoreTakeTimeout); + } + Ok(()) + } + + pub fn take_wait_forever(&self) -> Result<(), RTTError> { + panic_on_atomic_context("sem take_wait_forever"); + let ret = semaphore_take(self.sem, RT_WAITING_FOREVER as u32); + + if !is_eok(ret) { + return Err(RTTError::SemaphoreTakeTimeout); + } + + Ok(()) + } + + pub fn take(&self, max_wait: i32) -> Result<(), RTTError> { + panic_on_atomic_context("sem take"); + let ret = semaphore_take(self.sem, max_wait as u32); + + if !is_eok(ret) { + return Err(RTTError::SemaphoreTakeTimeout); + } + + Ok(()) + } + + pub fn release(&self) { + semaphore_release(self.sem); + } +} + +impl Drop for Semaphore { + fn drop(&mut self) { + semaphore_delete(self.sem) + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/thread.rs b/machines/qemu-virt-riscv64/rust/core/src/thread.rs new file mode 100644 index 00000000..a5a216b3 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/thread.rs @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Thread implementation + */ +use crate::alloc::boxed::Box; +use crate::api::*; +use crate::{RTResult, RTTError}; +use alloc::string::String; +use core::mem; +use core::ffi::c_void; + +#[derive(Debug)] +pub struct Thread(APIRawThread); + +impl Thread { + pub fn delay(tick: usize) { + let _ = thread_delay(tick); + } + + pub fn ms_delay(ms: usize) { + let _ = thread_m_delay(ms as i32); + } + + pub fn new() -> ThreadBuilder { + ThreadBuilder { + th_name: "uname".into(), + th_stack_size: 4096, + th_priority: 10, + th_ticks: 10, + } + } + + pub fn r#yield() { + thread_yield(); + } + + pub fn delete_thread(th: Self) { + let _ = thread_delete(th.0); + } + + pub fn delete(&self) { + let _ = thread_delete(self.0); + } + + unsafe fn spawn_inner( + name: String, + stack_size: u32, + priority: u8, + ticks: u32, + func: Box, + ) -> Result { + let func = Box::new(func); + let param = &*func as *const _ as *mut _; + + extern "C" fn thread_func(param: *mut c_void) { + unsafe { + let run = Box::from_raw(param as *mut Box); + run(); + } + } + + let th_handle = thread_create( + name.as_ref(), + thread_func, + param, + stack_size, + priority, + ticks, + ) + .ok_or(RTTError::OutOfMemory)?; + + let ret = match Self::_startup(th_handle) { + Ok(_) => { + mem::forget(func); + Ok(Thread(th_handle)) + } + Err(e) => Err(e), + }; + + return ret; + } + + fn _startup(th: APIRawThread) -> Result<(), RTTError> { + let ret = thread_startup(th); + return if is_eok(ret) { + Ok(()) + } else { + Err(RTTError::ThreadStartupErr) + }; + } + + pub fn spawn( + name: String, + stack_size: u32, + priority: u8, + ticks: u32, + func: F, + ) -> RTResult + where + F: FnOnce() -> () + Send + 'static, + { + unsafe { Self::spawn_inner(name, stack_size, priority, ticks, Box::new(func)) } + } +} + +unsafe impl Send for Thread {} + +pub struct ThreadBuilder { + th_name: String, + th_stack_size: u32, + th_priority: u8, + th_ticks: u32, +} + +impl ThreadBuilder { + pub fn name(&mut self, name: &str) -> &mut Self { + self.th_name = name.into(); + self + } + + pub fn stack_size(&mut self, stack_size: u32) -> &mut Self { + self.th_stack_size = stack_size; + self + } + + pub fn priority(&mut self, priority: u8) -> &mut Self { + self.th_priority = priority; + self + } + + pub fn ticks(&mut self, ticks: u32) -> &mut Self { + self.th_ticks = ticks; + self + } + + pub fn start(&self, func: F) -> RTResult + where + F: FnOnce() -> (), + F: Send + 'static, + { + Thread::spawn( + self.th_name.clone(), + self.th_stack_size, + self.th_priority, + self.th_ticks, + func, + ) + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/core/src/time.rs b/machines/qemu-virt-riscv64/rust/core/src/time.rs new file mode 100644 index 00000000..c9096e1f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/core/src/time.rs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Time implementation + */ +use crate::api::*; +use crate::bindings::libc; +use core::time::Duration; + +pub fn sleep(time: Duration) { + let mut time = time.as_millis(); + const MAX_DELAY: u128 = i32::MAX as u128; + const MAX_DELAY_P1: u128 = i32::MAX as u128 + 1; + loop { + match time { + 1..=MAX_DELAY => { + let _ = thread_m_delay(time as i32); + return; + } + 0 => return, + MAX_DELAY_P1..=u128::MAX => { + let _ = thread_m_delay(i32::MAX); + time -= i32::MAX as u128; + } + } + } +} + +pub fn get_time() -> Duration { + let mut tv = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + unsafe { + libc::gettimeofday(&mut tv, core::ptr::null_mut()); + } + Duration::new(tv.tv_sec as u64, tv.tv_usec as u32 * 1000) +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/0.introduction/examples-readme.md b/machines/qemu-virt-riscv64/rust/docs/0.introduction/examples-readme.md new file mode 100644 index 00000000..6ebee67a --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/0.introduction/examples-readme.md @@ -0,0 +1,194 @@ +# RT-Thread Rust Examples + +This directory contains comprehensive examples demonstrating various aspects of Rust development for RT-Thread. + +## Directory Structure + +``` +examples/ +├── applications/ # User application examples +│ ├── hello_world/ # Basic parameter handling example +│ ├── thread_sync/ # Thread synchronization example +│ ├── fs_operations/ # File system operations +│ ├── ipc_demo/ # Inter-process communication (mutex, semaphore, queue) +│ └── dynamic_lib/ # Dynamic library loading example +├── components/ # RT-Thread component examples +│ ├── logger/ # Logging component +│ ├── sensor_driver/ # Component registry example +│ └── protocol_stack/ # Protocol stack component +└── modules/ # Dynamic module examples + ├── simple_module/ # Basic dynamic module + ├── device_module/ # Device driver module + └── dynamic_lib/ # Dynamic library module +``` + +## Applications + +### hello_world +Basic example demonstrating parameter handling and RT-Thread integration. +- **Command**: `rust_param_demo` +- **Features**: Parameter parsing, basic output + +### thread_sync +Thread synchronization and management example. +- **Command**: `rust_thread_demo` +- **Features**: Thread creation, synchronization + +### fs_operations +File system operations demonstration. +- **Command**: `rust_fs_demo` +- **Features**: File I/O, directory operations + +### ipc_demo +Inter-process communication mechanisms. +- **Command**: `rust_ipc_demo [mutex|semaphore|queue]` +- **Features**: + - Mutex synchronization + - Semaphore signaling + - Message queue communication + +### dynamic_lib +Dynamic library loading and usage. +- **Command**: `rust_loadlib_demo` +- **Features**: Dynamic module loading, symbol resolution + +## Components + +### logger +Logging component with different log levels. +- **Features**: Debug, info, warning, error logging + +### sensor_driver +Component registry system demonstration. +- **Features**: Component registration, lifecycle management + +### protocol_stack +Network protocol stack component. +- **Command**: `protocol_demo` +- **Features**: Layered protocol processing + +## Modules + +### simple_module +Basic dynamic module template. +- **Type**: Dynamic library (cdylib) +- **Features**: Module initialization/cleanup + +### device_module +Device driver module example. +- **Command**: `device_demo` +- **Features**: Device initialization, I/O operations + +### dynamic_lib +Advanced dynamic library with symbol export. +- **Features**: C-compatible exports, runtime loading + +## Building Examples + +### Prerequisites +- RT-Thread with Rust support enabled +- Rust toolchain configured for target architecture +- SCons build system + +### Build Commands + +```bash +# Build all examples +scons --rust-examples + +# Build specific category +scons --rust-apps # Applications only +scons --rust-components # Components only +scons --rust-modules # Modules only + +# Build individual example +scons --rust-example=hello_world +``` + +### Configuration + +Enable Rust examples in RT-Thread configuration: + +``` +RT-Thread Components ---> + Rust Support ---> + [*] Enable Rust Examples + [*] Build Applications + [*] Build Components + [*] Build Modules +``` + +## Usage + +1. **Flash the firmware** with Rust examples enabled +2. **Connect to RT-Thread shell** via serial console +3. **Run examples** using the command names listed above + +Example session: +``` +msh /> rust_ipc_demo mutex +=== Mutex Demo === +Mutex counter: 1 +Mutex counter: 2 +... + +msh /> protocol_demo +=== Protocol Stack Component Demo === +Added layer: Physical to TCP/IP +Added layer: Data Link to TCP/IP +... +``` + +## Development Guidelines + +### Adding New Examples + +1. **Choose appropriate category** (applications/components/modules) +2. **Create directory structure**: + ``` + category/example_name/ + ├── Cargo.toml + ├── src/ + │ └── lib.rs + └── README.md (optional) + ``` +3. **Configure dependencies** with correct relative paths +4. **Update build scripts** if necessary +5. **Document the example** in this README + +### Best Practices + +- Use `#![no_std]` for embedded compatibility +- Include proper copyright headers +- Use `macro_main_use` for shell commands +- Handle errors gracefully +- Provide clear documentation +- Follow RT-Thread naming conventions + +## Troubleshooting + +### Common Issues + +1. **Build failures**: Check Cargo.toml paths and dependencies +2. **Runtime errors**: Verify RT-Thread configuration +3. **Missing commands**: Ensure examples are built and flashed + +### Debug Tips + +- Use `rt_rust::println!` for debugging output +- Check RT-Thread logs for initialization errors +- Verify memory allocation settings for complex examples + +## Contributing + +When contributing new examples: + +1. Follow the existing directory structure +2. Include comprehensive documentation +3. Test on target hardware +4. Update this README with new examples +5. Ensure compatibility with RT-Thread standards + +## License + +All examples are licensed under Apache-2.0, consistent with RT-Thread licensing. \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/1.tools/tools-readme-cn.md b/machines/qemu-virt-riscv64/rust/docs/1.tools/tools-readme-cn.md new file mode 100644 index 00000000..9ca28c2d --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/1.tools/tools-readme-cn.md @@ -0,0 +1,87 @@ +# RT-Thread to Rust Feature Configuration System + +这个文档说明如何使用示例配置文件来管理 RT-Thread 组件配置与 Rust features 之间的映射关系。 + +## 概述 + +配置系统允许你通过示例配置文件来扩展 RT-Thread 组件配置项与 Rust features 的映射关系,并确保只有依赖了相关组件的应用才会启用对应的 features,用户可以根据需要添加新的组件配置映射。这使得系统更加模块化和可扩展。 + +## 核心组件 + + +### 1. 配置映射表 (CONFIG_FEATURE_MAP) + +位于 `tools/feature_config_examples.py` 中的 `CONFIG_FEATURE_MAP` 定义了 RT-Thread 组件配置项到 Rust features 的基础映射关系: + +```python +CONFIG_FEATURE_MAP = { + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, +} +``` + +### 2. 依赖检查机制 + +系统会自动检查应用程序的 `Cargo.toml` 文件,确保只有依赖了相关组件的应用才会启用对应的 features。 + +## 如何添加新的组件 + +### 使用示例配置文件 + +1. 查看 `feature_config_examples.py` 中的示例配置 +2. 根据需要修改示例配置文件,添加你需要的组件配置映射 +3. 系统会自动加载示例配置文件中的映射 +4. 确保在应用程序的 `Cargo.toml` 中添加了相关组件的依赖 + +## 配置项格式 + +每个配置项包含以下字段: + +- `feature`: 要启用的 Rust feature 名称 +- `dependencies`: 可选,应用程序必须依赖的组件列表 +- `description`: 可选,配置项的描述 + +## 示例场景 + +1. 在 RT-Thread 的 Kconfig 中添加 `RUST_LOG_COMPONENT` 配置项 +2. 在 `feature_config_examples.py` 中添加对应的映射: + +```python +CONFIG_FEATURE_MAP.update({ + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, +}) +``` + +3. 在需要日志功能的 Rust 应用的 `Cargo.toml` 中: + +```toml +[features] +enable-log = ["em_component_log/enable-log"] + +[dependencies] +em_component_log = { path = "PATH/TO/components/log" } +``` + +## 故障排除 + +### 常见问题 + +1. **Feature 未启用**: 检查应用是否依赖了相关组件 +2. **构建失败**: 确保应用的 `Cargo.toml` 中定义了相应的 features +3. **配置不生效**: 验证 RT-Thread 配置项是否正确启用 +4. **示例配置未加载**: 检查 `feature_config_examples.py` 文件是否存在且语法正确 + +### 调试步骤 + +1. 检查构建日志中的配置加载信息 +2. 验证示例配置文件的语法 +3. 检查构建日志中的 feature 启用信息 +4. 验证应用的 `Cargo.toml` 配置 +5. 确认 RT-Thread 配置项状态 \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/1.tools/tools-readme.md b/machines/qemu-virt-riscv64/rust/docs/1.tools/tools-readme.md new file mode 100644 index 00000000..8e015ef4 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/1.tools/tools-readme.md @@ -0,0 +1,86 @@ +# RT-Thread to Rust Feature Configuration System + +This document explains how to use example configuration files to manage the mapping relationship between RT-Thread component configurations and Rust features. + +## Overview + +The configuration system allows you to extend the mapping relationship between RT-Thread component configuration items and Rust features through example configuration files, ensuring that only applications that depend on related components will enable corresponding features. Users can add new component configuration mappings as needed. This makes the system more modular and extensible. + +## Core Components + +### 1. Configuration Mapping Table (CONFIG_FEATURE_MAP) + +The `CONFIG_FEATURE_MAP` located in `tools/feature_config_examples.py` defines the basic mapping relationship from RT-Thread component configuration items to Rust features: + +```python +CONFIG_FEATURE_MAP = { + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, +} +``` + +### 2. Dependency Check Mechanism + +The system automatically checks the application's `Cargo.toml` file to ensure that only applications that depend on related components will enable corresponding features. + +## How to Add New Components + +### Using Example Configuration Files + +1. View the example configurations in `feature_config_examples.py` +2. Modify the example configuration file as needed, adding the component configuration mappings you require +3. The system will automatically load the mappings from the example configuration file +4. Ensure that related component dependencies are added to the application's `Cargo.toml` + +## Configuration Item Format + +Each configuration item contains the following fields: + +- `feature`: The name of the Rust feature to enable +- `dependencies`: Optional, list of components that the application must depend on +- `description`: Optional, description of the configuration item + +## Example Scenario + +1. Add the `RUST_LOG_COMPONENT` configuration item in RT-Thread's Kconfig +2. Add the corresponding mapping in `feature_config_examples.py`: + +```python +CONFIG_FEATURE_MAP.update({ + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, +}) +``` + +3. In the `Cargo.toml` of a Rust application that needs logging functionality: + +```toml +[features] +enable-log = ["em_component_log/enable-log"] + +[dependencies] +em_component_log = { path = "PATH/TO/components/log" } +``` + +## Troubleshooting + +### Common Issues + +1. **Feature not enabled**: Check if the application depends on the related components +2. **Build failure**: Ensure that the corresponding features are defined in the application's `Cargo.toml` +3. **Configuration not taking effect**: Verify that the RT-Thread configuration item is correctly enabled +4. **Example configuration not loaded**: Check if the `feature_config_examples.py` file exists and has correct syntax + +### Debugging Steps + +1. Check the configuration loading information in the build logs +2. Verify the syntax of the example configuration file +3. Check the feature enabling information in the build logs +4. Verify the application's `Cargo.toml` configuration +5. Confirm the RT-Thread configuration item status \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/2.applications/application-readme-cn.md b/machines/qemu-virt-riscv64/rust/docs/2.applications/application-readme-cn.md new file mode 100644 index 00000000..91b055db --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/2.applications/application-readme-cn.md @@ -0,0 +1,127 @@ +# RT-Thread Rust 示例用户应用程序 + +这个目录包含了一系列用于演示 RT-Thread 中 Rust 编程功能的示例用户应用程序和组件。这些示例展示了如何在 RT-Thread 实时操作系统中使用 Rust 语言进行系统编程。 + +## 目录结构 + +``` +example_usrapp/ +├── SConscript # SCons 构建脚本 +├── fs/ # 文件系统操作示例 +├── loadlib/ # 动态库加载示例 +├── mutex/ # 互斥锁同步示例 +├── param/ # 参数处理示例 +├── queue/ # 消息队列示例 +├── semaphore/ # 信号量同步示例 +├── thread/ # 线程管理示例 +└── tools/ # 构建工具和配置文件 + ├── build_usrapp.py # 用户应用构建脚本 + ├── feature_config_examples.py # 特性配置示例 + └── README_cn.md # 工具说明文档 +``` + +## 构建和运行 + +### 前提条件 + +1. 确保 RT-Thread 配置中启用了 Rust 支持 (`RT_USING_RUST`) +2. 安装 Rust 工具链和相关依赖 +3. 配置正确的交叉编译环境 + +### 构建步骤 + +1. 在 RT-Thread 项目根目录执行: + ```bash + scons -j$(nproc) + ``` + +2. 构建系统会自动: + - 检测启用的 RT-Thread 配置 + - 根据配置启用相应的 Rust 特性 + - 编译所有示例应用为静态库 + - 链接到最终的固件镜像 + +### 运行示例 + +在 RT-Thread 系统启动后,可以通过命令行运行各个示例: + +```bash +# 查看所有可用示例 +help + +# 运行线程示例 +rust_thread_demo + +# 运行文件系统示例 +rust_file_demo + +# 其他示例类似... +``` + +## 技术特性 + +### 依赖管理 + +所有示例应用都依赖以下核心组件: +- `rt-rust`: RT-Thread Rust 绑定库 +- `macro_main`: 主函数宏支持 + +`fs`示例还依赖日志组件: +- `em_component_log`: 日志组件 + +### 特性配置 + +- 支持条件编译特性 +- 自动特性检测和启用 +- 模块化的功能配置 + +### 构建系统集成 + +- 与 SCons 构建系统完全集成 +- 支持清理和增量构建 +- 自动依赖管理 + +## 开发指南 + +### 添加新示例 + +1. 在 `example_usrapp/` 下创建新目录 +2. 添加 `Cargo.toml` 配置文件 +3. 在 `src/lib.rs` 中实现示例代码 +4. 使用 `#[macro_main_use]` 宏定义入口点 + +### 示例模板 + +```rust +#![no_std] + +use macro_main::macro_main_use; +use rt_rust::param::Param; +use rt_rust::println; + +#[macro_main_use( + name = "your_demo_name", + component = "是否为组件示例", + app = "是否为用户应用示例", + cmd = true, + desc = "Your demo description." +)] +fn main(_param: Param) { + println!("Hello from your demo!"); + // 你的示例代码... +} +``` + +## 故障排除 + +### 常见问题 + +1. **编译失败**: 检查 Rust 工具链是否正确安装 +2. **链接错误**: 确认交叉编译目标配置正确 +3. **运行时错误**: 检查栈大小和内存配置 + +### 调试建议 + +1. 使用 `println!` 进行基本调试输出 +2. 启用日志特性获取详细信息 +3. 检查 RT-Thread 配置项是否正确启用 diff --git a/machines/qemu-virt-riscv64/rust/docs/2.applications/application-readme.md b/machines/qemu-virt-riscv64/rust/docs/2.applications/application-readme.md new file mode 100644 index 00000000..5a60514d --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/2.applications/application-readme.md @@ -0,0 +1,127 @@ +# RT-Thread Rust Example User Applications + +This directory contains a series of example user applications and components that demonstrate Rust programming capabilities in RT-Thread. These examples showcase how to use the Rust programming language for system programming in the RT-Thread real-time operating system. + +## Directory Structure + +``` +example_usrapp/ +├── SConscript # SCons build script +├── fs/ # File system operation examples +├── loadlib/ # Dynamic library loading examples +├── mutex/ # Mutex synchronization examples +├── param/ # Parameter handling examples +├── queue/ # Message queue examples +├── semaphore/ # Semaphore synchronization examples +├── thread/ # Thread management examples +└── tools/ # Build tools and configuration files + ├── build_usrapp.py # User application build script + ├── feature_config_examples.py # Feature configuration examples + └── README_cn.md # Tool documentation (Chinese) +``` + +## Build and Run + +### Prerequisites + +1. Ensure Rust support is enabled in RT-Thread configuration (`RT_USING_RUST`) +2. Install Rust toolchain and related dependencies +3. Configure the correct cross-compilation environment + +### Build Steps + +1. Execute in the RT-Thread project root directory: + ```bash + scons -j$(nproc) + ``` + +2. The build system will automatically: + - Detect enabled RT-Thread configurations + - Enable corresponding Rust features based on configuration + - Compile all example applications as static libraries + - Link to the final firmware image + +### Running Examples + +After RT-Thread system startup, you can run various examples through the command line: + +```bash +# View all available examples +help + +# Run thread example +rust_thread_demo + +# Run file system example +rust_file_demo + +# Other examples similarly... +``` + +## Technical Features + +### Dependency Management + +All example applications depend on the following core components: +- `rt-rust`: RT-Thread Rust binding library +- `macro_main`: Main function macro support + +The `fs` example also depends on the logging component: +- `em_component_log`: Logging component + +### Feature Configuration + +- Support for conditional compilation features +- Automatic feature detection and enabling +- Modular functionality configuration + +### Build System Integration + +- Fully integrated with SCons build system +- Support for clean and incremental builds +- Automatic dependency management + +## Development Guide + +### Adding New Examples + +1. Create a new directory under `example_usrapp/` +2. Add `Cargo.toml` configuration file +3. Implement example code in `src/lib.rs` +4. Use `#[macro_main_use]` macro to define entry point + +### Example Template + +```rust +#![no_std] + +use macro_main::macro_main_use; +use rt_rust::param::Param; +use rt_rust::println; + +#[macro_main_use( + name = "your_demo_name", + component = "Whether it's a component example", + app = "Whether it's a user application example", + cmd = true, + desc = "Your demo description." +)] +fn main(_param: Param) { + println!("Hello from your demo!"); + // Your example code... +} +``` + +## Troubleshooting + +### Common Issues + +1. **Compilation failure**: Check if Rust toolchain is correctly installed +2. **Linking errors**: Confirm cross-compilation target configuration is correct +3. **Runtime errors**: Check stack size and memory configuration + +### Debugging Suggestions + +1. Use `println!` for basic debug output +2. Enable logging features to get detailed information +3. Check if RT-Thread configuration items are correctly enabled \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/3.components/component-readme-cn.md b/machines/qemu-virt-riscv64/rust/docs/3.components/component-readme-cn.md new file mode 100644 index 00000000..ff48fc85 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/3.components/component-readme-cn.md @@ -0,0 +1,227 @@ +# Example Component - RT-Thread Rust 组件示例 + +## 概述 + +`example_component` 是一个基于 RT-Thread 的 Rust 组件示例,展示了如何在 RT-Thread 系统中实现、注册和管理 Rust 组件。该项目采用统一的组件注册架构,避免了组件重复注册问题,并支持基于 RT-Thread 配置的条件编译。 + +## 项目结构 + +``` +example_component/ +├── SConscript # SCons 构建脚本 +├── component_registry/ # 统一组件注册程序 +│ ├── Cargo.toml # 注册程序配置 +│ └── src/ +│ └── lib.rs # 注册程序实现 +├── log/ # 日志组件示例 +│ ├── Cargo.toml # 日志组件配置 +│ └── src/ +│ ├── lib.rs # 日志组件实现 +│ └── logging.rs # 日志功能模块 +└── tools/ # 构建工具 + ├── build_component.py # 组件构建工具 + └── feature_config_component.py # 特性配置 +``` + +## 核心特性 + +### 1. 统一组件注册架构 + +- **避免重复注册**: 通过 `component_registry` 统一管理所有组件的注册,避免多个组件重复注册导致的冲突 +- **条件编译支持**: 根据 RT-Thread 配置选择性编译和注册组件 +- **模块化设计**: 每个组件独立实现,通过注册程序统一管理 + +### 2. 特性配置系统 + +- **基于 RT-Thread 配置**: 特性启用完全基于 RT-Thread 的配置项,无需额外配置 +- **自动特性映射**: 构建系统自动将 RT-Thread 配置映射为 Rust 特性 +- **依赖检查**: 自动检查组件依赖关系,确保构建一致性 + +### 3. 构建系统集成 + +- **SCons 集成**: 与 RT-Thread 的 SCons 构建系统完全集成 +- **增量构建**: 支持清理和增量构建 +- **交叉编译**: 支持 RISC-V 等目标架构的交叉编译 + +## 组件详解 + +### Component Registry (组件注册程序) + +`component_registry` 是整个架构的核心,负责统一管理所有组件的注册。 + +#### 主要功能 + +1. **统一入口点**: 提供 `rust_component_registry` 作为所有组件的统一注册入口 +2. **条件编译**: 只在相应特性启用时才包含和注册对应组件 +3. **避免冲突**: 确保每个组件只被注册一次 + + +### Log Component (日志组件) + +`log` 组件提供了 Rust 风格的日志功能,包括多个日志级别和命令行接口。 + +#### 主要功能 + +1. **多级别日志**: 支持 INFO、WARN、ERROR、DEBUG、TRACE 等日志级别 +2. **命令行接口**: 提供 `log_info`、`log_warn`、`log_error` 等命令 +3. **条件编译**: 只在 `enable-log` 特性启用时编译 + +#### 使用示例 + +```rust +// 在代码中使用日志宏 +info!("这是一条信息日志"); +warn!("这是一条警告日志"); +error!("这是一条错误日志"); +``` + +#### 命令行使用 + +```bash +# 在 RT-Thread 命令行中使用 +msh> log_info # 打印信息日志 +msh> log_warn # 打印警告日志 +msh> log_error # 打印错误日志 +``` + +## 特性配置 + +### 配置映射 + +在 `tools/feature_config_component.py` 中定义了 RT-Thread 配置到 Rust 特性的映射: + +```python +CONFIG_COMPONENT_FEATURE_MAP = { + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependency': 'em_component_log', + 'description': 'Enable Rust log component integration' + } +} +``` + +### 启用组件 + +要启用日志组件,需要在 RT-Thread 配置中设置: + +```c +// 通过 menuconfig 设置 +Enable Rust component support -> Auto-initialize Rust log component +``` + +## 开发指南 + +### 添加新组件 + +1. **创建组件目录** + ```bash + cargo new --lib example_component/new_component + cd example_component/new_component + ``` + +2. **创建 Cargo.toml** + ```toml + [package] + name = "em_component_new" + version = "0.1.0" + edition = "2021" + + [lib] + name = "em_component_new" + crate-type = ["staticlib"] + + [dependencies] + rt-rust = { path = "../../rust" } + macro_main = { path = "../../rust/macro-main" } + + [features] + default = [] + enable-new = [] + ``` + +3. **实现组件功能** + ```rust + // src/new.rs + // 新组件的实现代码 + + // src/lib.rs + #![no_std] + // 导出需要的库 + ``` + +4. **更新特性配置** + ```python + # 在 tools/feature_config_component.py 中添加 + CONFIG_COMPONENT_FEATURE_MAP = { + # ... 现有配置 ... + 'RUST_NEW_COMPONENT': { + 'feature': 'enable-new', + 'dependency': 'em_component_new', + 'description': 'Enable new component integration' + } + } + ``` + +5. **更新注册程序** + ```toml + # 在 component_registry/Cargo.toml 中添加 + [dependencies] + em_component_new = { path = "../new_component", optional = true } + + [features] + enable-new = ["em_component_new", "em_component_new/enable-new"] + ``` + + ```rust + // 在 component_registry/src/lib.rs 中注册 + #[cfg(feature = "enable-new")] + use em_component_new; + + fn component_registry_main() { + #[cfg(feature = "enable-new")] + { + println!("Component registry: New component enabled"); + } + // ... 其他组件 ... + } + ``` + +### 组件开发最佳实践 + +1. **使用条件编译**: 所有功能都应该用 `#[cfg(feature = "...")]` 包装 +2. **避免直接注册**: 不要在组件中直接使用组件注册宏,让 `component_registry` 统一管理 +3. **提供命令接口**: 为组件功能提供命令行接口,方便测试和使用 +4. **文档完整**: 为每个组件提供详细的文档和使用示例 + +## 故障排除 + +### 常见问题 + +1. **编译失败** + - 检查 Rust 工具链是否正确安装 + - 确认目标架构 (如 `riscv64imac-unknown-none-elf`) 已安装 + - 检查依赖路径是否正确 + +2. **特性未启用** + - 确认 RT-Thread 配置中已设置相应的宏定义 + - 检查 `feature_config_component.py` 中的映射配置 + - 查看构建日志中的特性启用信息 + +3. **链接错误** + - 确认所有依赖组件都已正确构建 + - 检查 `Cargo.toml` 中的依赖配置 + - 验证库文件路径和名称 + +## 依赖关系 + +``` +RT-Thread Config + ↓ +Feature Mapping + ↓ +Rust Features + ↓ +Component Registry ← Individual Components + ↓ +RT-Thread System +``` diff --git a/machines/qemu-virt-riscv64/rust/docs/3.components/component-readme.md b/machines/qemu-virt-riscv64/rust/docs/3.components/component-readme.md new file mode 100644 index 00000000..df1f7126 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/3.components/component-readme.md @@ -0,0 +1,226 @@ +# Example Component - RT-Thread Rust Component Example + +## Overview + +`example_component` is a Rust component example based on RT-Thread, demonstrating how to implement, register, and manage Rust components in RT-Thread systems. This project adopts a unified component registration architecture that avoids component duplicate registration issues and supports conditional compilation based on RT-Thread configuration. + +## Project Structure + +``` +example_component/ +├── SConscript # SCons build script +├── component_registry/ # Unified component registry +│ ├── Cargo.toml # Registry configuration +│ └── src/ +│ └── lib.rs # Registry implementation +├── log/ # Log component example +│ ├── Cargo.toml # Log component configuration +│ └── src/ +│ ├── lib.rs # Log component implementation +│ └── logging.rs # Logging functionality module +└── tools/ # Build tools + ├── build_component.py # Component build tool + └── feature_config_component.py # Feature configuration +``` + +## Core Features + +### 1. Unified Component Registration Architecture + +- **Avoid Duplicate Registration**: Unified management of all component registrations through `component_registry`, avoiding conflicts caused by multiple component duplicate registrations +- **Conditional Compilation Support**: Selectively compile and register components based on RT-Thread configuration +- **Modular Design**: Each component is implemented independently and managed uniformly through the registry + +### 2. Feature Configuration System + +- **Based on RT-Thread Configuration**: Feature enablement is entirely based on RT-Thread configuration items, requiring no additional configuration +- **Automatic Feature Mapping**: Build system automatically maps RT-Thread configuration to Rust features +- **Dependency Checking**: Automatically checks component dependencies to ensure build consistency + +### 3. Build System Integration + +- **SCons Integration**: Fully integrated with RT-Thread's SCons build system +- **Incremental Build**: Supports clean and incremental builds +- **Cross Compilation**: Supports cross-compilation for target architectures like RISC-V + +## Component Details + +### Component Registry + +`component_registry` is the core of the entire architecture, responsible for unified management of all component registrations. + +#### Main Functions + +1. **Unified Entry Point**: Provides `rust_component_registry` as the unified registration entry for all components +2. **Conditional Compilation**: Only includes and registers corresponding components when respective features are enabled +3. **Avoid Conflicts**: Ensures each component is registered only once + +### Log Component + +The `log` component provides Rust-style logging functionality, including multiple log levels and command-line interface. + +#### Main Functions + +1. **Multi-level Logging**: Supports INFO, WARN, ERROR, DEBUG, TRACE and other log levels +2. **Command-line Interface**: Provides commands like `log_info`, `log_warn`, `log_error` +3. **Conditional Compilation**: Only compiles when `enable-log` feature is enabled + +#### Usage Examples + +```rust +// Using log macros in code +info!("This is an info log"); +warn!("This is a warning log"); +error!("This is an error log"); +``` + +#### Command-line Usage + +```bash +# Using in RT-Thread command line +msh> log_info # Print info log +msh> log_warn # Print warning log +msh> log_error # Print error log +``` + +## Feature Configuration + +### Configuration Mapping + +The mapping from RT-Thread configuration to Rust features is defined in `tools/feature_config_component.py`: + +```python +CONFIG_COMPONENT_FEATURE_MAP = { + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependency': 'em_component_log', + 'description': 'Enable Rust log component integration' + } +} +``` + +### Enabling Components + +To enable the log component, set in RT-Thread configuration: + +```c +// Set through menuconfig +Enable Rust component support -> Auto-initialize Rust log component +``` + +## Development Guide + +### Adding New Components + +1. **Create Component Directory** + ```bash + cargo new --lib example_component/new_component + cd example_component/new_component + ``` + +2. **Create Cargo.toml** + ```toml + [package] + name = "em_component_new" + version = "0.1.0" + edition = "2021" + + [lib] + name = "em_component_new" + crate-type = ["staticlib"] + + [dependencies] + rt-rust = { path = "../../rust" } + macro_main = { path = "../../rust/macro-main" } + + [features] + default = [] + enable-new = [] + ``` + +3. **Implement Component Functionality** + ```rust + // src/new.rs + // New component implementation code + + // src/lib.rs + #![no_std] + // Export required libraries + ``` + +4. **Update Feature Configuration** + ```python + # Add to tools/feature_config_component.py + CONFIG_COMPONENT_FEATURE_MAP = { + # ... existing configuration ... + 'RUST_NEW_COMPONENT': { + 'feature': 'enable-new', + 'dependency': 'em_component_new', + 'description': 'Enable new component integration' + } + } + ``` + +5. **Update Registry** + ```toml + # Add to component_registry/Cargo.toml + [dependencies] + em_component_new = { path = "../new_component", optional = true } + + [features] + enable-new = ["em_component_new", "em_component_new/enable-new"] + ``` + + ```rust + // Register in component_registry/src/lib.rs + #[cfg(feature = "enable-new")] + use em_component_new; + + fn component_registry_main() { + #[cfg(feature = "enable-new")] + { + println!("Component registry: New component enabled"); + } + // ... other components ... + } + ``` + +### Component Development Best Practices + +1. **Use Conditional Compilation**: All functionality should be wrapped with `#[cfg(feature = "...")]` +2. **Avoid Direct Registration**: Don't use component registration macros directly in components, let `component_registry` manage uniformly +3. **Provide Command Interface**: Provide command-line interfaces for component functionality for easy testing and usage +4. **Complete Documentation**: Provide detailed documentation and usage examples for each component + +## Troubleshooting + +### Common Issues + +1. **Compilation Failure** + - Check if Rust toolchain is correctly installed + - Confirm target architecture (e.g., `riscv64imac-unknown-none-elf`) is installed + - Check if dependency paths are correct + +2. **Features Not Enabled** + - Confirm corresponding macro definitions are set in RT-Thread configuration + - Check mapping configuration in `feature_config_component.py` + - Review feature enablement information in build logs + +3. **Linking Errors** + - Confirm all dependent components are correctly built + - Check dependency configuration in `Cargo.toml` + - Verify library file paths and names + +## Dependency Relationships + +``` +RT-Thread Config + ↓ +Feature Mapping + ↓ +Rust Features + ↓ +Component Registry ← Individual Components + ↓ +RT-Thread System +``` \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/4.modules/modules-readme.md b/machines/qemu-virt-riscv64/rust/docs/4.modules/modules-readme.md new file mode 100644 index 00000000..501a2721 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/4.modules/modules-readme.md @@ -0,0 +1,47 @@ +# Writing a Rust Dynamic Library Usable by RT-Thread + +1. Set up the project +Create a new `cargo` project as usual. There are some flags to tell `cargo` to generate a system library instead of a regular Rust target. + +```toml +[lib] +name = "your_crate" +crate-type = ["cdylib"] # generate a dynamic library +# crate-type = ["staticlib"] # generate a static library +``` + +Add the following to `config.toml` to specify the target platform: + +```toml +[build] +target = "your_target" +``` + +2. Enable RT-Thread dynamic module loading +To load dynamic modules in RT-Thread, enable it in `Kconfig`: +``` +RT-Thread Components → C/C++ and POSIX layer + → POSIX (Portable Operating System Interface) layer + → Enable dynamic module APIs, dlopen()/dlsym()/dlclose() etc +``` + +3. Enable the filesystem +To place dynamic modules into RT-Thread, enable a filesystem in `Kconfig`: +``` +RT-Thread online packages → system packages + → lwext4: an excellent choice of ext2/3/4 filesystem for microcontrollers. +``` + +## Using the rust library + +Add `rt_rust` dependency in `Cargo.toml`: + +```toml +[dependencies] +rt_rust = { path = "PATH/TO/rust/rt-rust" } +``` + +Currently the macro-main library is not supported because it causes entry detection issues. + +## References +- [The Embedded Rust Book](https://xxchang.github.io/book/interoperability/rust-with-c.html#extern-c) \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/docs/4.modules/modules-readme_cn.md b/machines/qemu-virt-riscv64/rust/docs/4.modules/modules-readme_cn.md new file mode 100644 index 00000000..f9630d31 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/4.modules/modules-readme_cn.md @@ -0,0 +1,47 @@ +# 编写RT-Thread可以使用的rust动态库 + +1. 设置项目 +像往常一样创建一个新的`cargo`项目。有一些标志可以告诉`cargo`去生成一个系统库,而不是常规的rust目标文件。 + +```toml +[lib] +name = "your_crate" +crate-type = ["cdylib"] # 生成动态链接库 +# crate-type = ["staticlib"] # 生成静态链接库 +``` + +在 `config.toml` 中添加以下内容,以指定目标平台 + +```toml +[build] +target = "your_target +``` + +2. 开启 RT-Thread 加载动态模块功能 +为了能够在 RT-Thread 中加载动态模块,需要在 `Kconfig` 中开启 +``` +RT-Thread Components → C/C++ and POSIX layer + → POSIX (Portable Operating System Interface) layer + → Enable dynamic module APIs, dlopen()/dlsym()/dlclose() etc +``` + +3. 打开文件系统 +为了能够将动态模块放入 RT-Thread 中,需要在 `Kconfig` 中开启文件系统。 + +``` +RT-Thread online packages → system packages + → lwext4: an excellent choice of ext2/3/4 filesystem for microcontrollers. +``` + +## rust库的使用 + +在 `Cargo.toml` 中添加 `rt_rust` 依赖 + +```toml +[dependencies] +rt_rust = { path = "PATH/TO/rust/rt-rust" } +``` + +目前不支持macro-main库的使用,因为它会导致入口识别错误。 +## 参考资料 +- [The Embedded Rust Book](https://xxchang.github.io/book/interoperability/rust-with-c.html#extern-c) diff --git a/machines/qemu-virt-riscv64/rust/docs/5.rt-macro/rt-macros-readme-cn.md b/machines/qemu-virt-riscv64/rust/docs/5.rt-macro/rt-macros-readme-cn.md new file mode 100644 index 00000000..541bf6ba --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/5.rt-macro/rt-macros-readme-cn.md @@ -0,0 +1,30 @@ +# macro-main + +## 功能描述 + +由于在没有Rust标准库的支持下,也就是`no_std`模式下,是无法使用main函数的。于是通过过程宏的方式对编写的代码进行重构,通过使用C ABI调用格式的main函数,就可以正常使用main函数。并自动把命令或应用段信息落到指定链接段,让 RT-Thread 在启动或命令表中发现并调用。 + +* 入口封装:生成一个 extern "C" 的入口函数,把 C 风格 argv 转成 Vec ,再调用原始 Rust 函数。 +* Component 段:生成的函数指针并落到 `.rti_fn.4` 段,作为**组件**初始化入口。 +* App 段:生成的函数指针并落到 `.rti_fn.6` 段,作为**应用**初始化入口。 +* 拓展export说明(部分未测试实现): + * 预初始化入口:`.rti_fn.2` 段 + * 设备初始化入口:`.rti_fn.3` 段 + * 组件初始化入口:`.rti_fn.4` 段(已测试) + * 环境初始化入口:`.rti_fn.5` 段 + * 应用初始化入口:`.rti_fn.6` 段(已测试) +* MSH命令导出:生成命令描述结构体,并把该结构体落到 `FSymTab` 段,同时把 相关信息落到 `.rodata.name` 段。这样能扫描到并注册为 MSH 命令。 + +## 使用方法 + +1. 在 Rust 代码中引入 `rt_macros` 过程宏。 +2. 根据需求在 `main` 函数上添加注解。 +3. 根据需求注册为组件或应用。 + * 组件:#[rt_component_export(name = "rust_component_registry")]。 + * 应用:#[rt_app_export(name = "rust_app_example")]。 +4. 编译 Rust 代码,生成的可执行文件中就会包含 `main` 函数的入口。 +5. 当C代码需要调用Rust函数时,需要在C代码中声明Rust函数的原型,并且使用 `extern "C"` 来指定调用约定。例如:`extern "C" void rust_function_name(void);` + +## 参考实现 + +- [RUST support for rt-thread](https://github.com/rust-for-rtthread/rtt_rust) diff --git a/machines/qemu-virt-riscv64/rust/docs/5.rt-macro/rt-macros-readme.md b/machines/qemu-virt-riscv64/rust/docs/5.rt-macro/rt-macros-readme.md new file mode 100644 index 00000000..a604bd94 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/docs/5.rt-macro/rt-macros-readme.md @@ -0,0 +1,34 @@ + + + + +**macro-main** + +- Provides a way to use `main` in `no_std` mode by rewriting code through a procedural macro. It exposes a C ABI–style `main` entry and automatically places command or application metadata into designated linker sections so RT-Thread can discover and invoke it at startup or via the command table. + +**Features** + +- Entry wrapper: Generates an `extern "C"` entry function, converts C-style `argv` to `Vec`, then calls the original Rust function. +- Component section: Places the generated function pointer into the `.rti_fn.4` section as a component initialization entry. +- App section: Places the generated function pointer into the `.rti_fn.6` section as an application initialization entry. +- Extended export notes (partially untested implementations): + - Pre-initialization entry: `.rti_fn.2` section + - Device initialization entry: `.rti_fn.3` section + - Component initialization entry: `.rti_fn.4` section (tested) + - Environment initialization entry: `.rti_fn.5` section + - Application initialization entry: `.rti_fn.6` section (tested) +- MSH command export: Generates a command descriptor struct, places it into the `FSymTab` section, and stores related information in the `.rodata.name` section so it can be scanned and registered as an MSH command. + +**Usage** + +- Import the `rt_macros` procedural macro in your Rust code. +- Add the `#[rt_main_export]` attribute to the `main` function. +- Register as a component or an application as needed. + - Component: `#[rt_component_export(name = "rust_component_registry")]` + - Application: `#[rt_app_export(name = "rust_app_example")]` +- Compile the Rust code; the resulting executable will contain the `main` function entry. +- When C code needs to call Rust functions, declare the Rust function prototypes in C code and use `extern "C"` to specify the calling convention. For example: `extern "C" void rust_function_name(void);` + +## References + +[RUST support for rt-thread](https://github.com/rust-for-rtthread/rtt_rust) \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/Kconfig b/machines/qemu-virt-riscv64/rust/examples/Kconfig new file mode 100644 index 00000000..cd186908 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/Kconfig @@ -0,0 +1,104 @@ +menuconfig RT_USING_RUST_EXAMPLES + bool "Enable Rust Examples" + depends on RT_USING_RUST + default n + help + Enable Rust example applications, components, and modules. + +if RT_USING_RUST_EXAMPLES + + config RT_RUST_BUILD_ALL_EXAMPLES + bool "Build All Examples" + default n + help + Build all available Rust examples. + + menu "Application Examples" + config RT_RUST_BUILD_APPLICATIONS + bool "Build Application Examples" + default y + depends on RT_USING_FINSH + help + Build Rust application examples. + + if RT_RUST_BUILD_APPLICATIONS + config RT_RUST_EXAMPLE_FS + bool "File System Example" + default n + help + File system operations example. + + config RT_RUST_EXAMPLE_LOADLIB + bool "Dynamic Library Loading Example" + default n + depends on RT_USING_MODULE + help + Dynamic library loading and usage example. + + config RT_RUST_EXAMPLE_MUTEX + bool "Mutex Example" + default y + help + Mutex synchronization example. + + config RT_RUST_EXAMPLE_PARAM + bool "Parameter Example" + default y + help + Basic parameter handling example. + + config RT_RUST_EXAMPLE_QUEUE + bool "Queue Example" + default y + help + Message queue example. + + config RT_RUST_EXAMPLE_SEMAPHORE + bool "Semaphore Example" + default y + help + Semaphore synchronization example. + + config RT_RUST_EXAMPLE_THREAD + bool "Thread Example" + default y + help + Thread creation and management example. + endif + endmenu + + menu "Component Examples" + config RT_RUST_BUILD_COMPONENTS + bool "Build Component Examples" + default y + help + Build Rust component examples. + + if RT_RUST_BUILD_COMPONENTS + config RUST_LOG_COMPONENT + bool "Auto-initialize Rust log component" + default y + help + Automatically initialize Rust log component during RT-Thread startup. + endif + endmenu + + menu "Module Examples" + config RT_RUST_BUILD_MODULES + bool "Build Module Examples" + default n + depends on RT_USING_MODULE + help + Build Rust dynamic module examples. + + if RT_RUST_BUILD_MODULES + config RT_RUST_MODULE_SIMPLE_MODULE + bool "Simple Module" + default y + help + Basic dynamic module template. + + endif + endmenu + +endif \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/SConscript b/machines/qemu-virt-riscv64/rust/examples/SConscript new file mode 100644 index 00000000..29d9b211 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/SConscript @@ -0,0 +1,18 @@ +# RT-Thread building script for Rust examples + +import os +from building import * + +Import('rtconfig') + +cwd = GetCurrentDir() +group = [] +list = os.listdir(cwd) + +subdirs = ['application', 'component', 'modules'] + +for subdir in subdirs: + if subdir in list: + group = group + SConscript(os.path.join(subdir, 'SConscript')) + +Return('group') \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/application/SConscript b/machines/qemu-virt-riscv64/rust/examples/application/SConscript new file mode 100644 index 00000000..c6c4de3d --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/SConscript @@ -0,0 +1,70 @@ +import os +import sys +from building import * + +cwd = GetCurrentDir() + +# Import usrapp build module and build support +sys.path.append(os.path.join(cwd, '../../tools')) +from build_usrapp import build_example_usrapp +from build_support import clean_rust_build + + +def _has(sym: str) -> bool: + try: + return bool(GetDepend([sym])) + except Exception: + return bool(GetDepend(sym)) + + +def load_extended_feature_configs(): + """ + Load extended feature configurations if available. + This allows users to add custom configuration mappings. + """ + try: + from feature_config_examples import setup_all_example_features + setup_all_example_features() + print("Extended feature configurations loaded successfully.") + except ImportError: + print("Using default feature configurations.") + +group = [] + +if not _has('RT_RUST_BUILD_APPLICATIONS'): + Return('group') + +# Load extended feature configurations +load_extended_feature_configs() + +src = [] +LIBS = [] +LIBPATH = [] +LINKFLAGS = "" + +if GetOption('clean'): + app_build_dir = clean_rust_build(Dir('#').abspath, "example_usrapp") + if os.path.exists(app_build_dir): + print(f'Registering {app_build_dir} for cleanup') + Clean('.', app_build_dir) + else: + print('No example_usrapp build artifacts to clean') +else: + import rtconfig + LIBS, LIBPATH, LINKFLAGS = build_example_usrapp( + cwd=cwd, + has_func=_has, + rtconfig=rtconfig, + build_root=os.path.join(Dir('#').abspath, "build", "example_usrapp") + ) + +group = DefineGroup( + 'example_usrapp', + src, + depend=['RT_USING_RUST'], + LIBS=LIBS, + LIBPATH=LIBPATH, + LINKFLAGS=LINKFLAGS +) + +Return('group') \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/application/fs/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/fs/Cargo.toml new file mode 100644 index 00000000..2556b5f0 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/fs/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "em_fs" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_fs" +crate-type = ["staticlib"] + +[features] +default = [] +enable-log = ["em_component_log/enable-log"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } +em_component_log = { path = "../../component/log"} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/application/fs/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/fs/src/lib.rs new file mode 100644 index 00000000..83fde74a --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/fs/src/lib.rs @@ -0,0 +1,73 @@ + +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-20 foxglove Rust file opration test. + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +use rt_macros::msh_cmd_export; +use rt_rust::{fs, println}; +use rt_rust::param::Param; +use em_component_log::{info, error}; + +#[msh_cmd_export(name = "rust_file_demo", desc = "Rust example app.")] +fn main(_param: Param) { + println!("[rust_file_test] start"); + + let mut file = match fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .append(false) + .truncate(true) + .open("test.txt") + { + Ok(f) => { + info!("open test.txt ok"); + f + } + Err(e) => { + error!("open error: {:?}", e); + return; + } + }; + + if let Err(e) = file.write_all("Hello from FS wrapper!\n") { + error!("write_all error: {:?}", e); + return; + } + info!("write_all done"); + + if let Err(e) = file.flush() { + error!("flush error: {:?}", e); + } else { + info!("flush ok"); + } + + match file.read_to_string() { + Ok(s) => info!("read_back: {}", s), + Err(e) => error!("read_to_string error: {:?}", e), + } + + if let Err(e) = file.set_len(5) { + error!("truncate error: {:?}", e); + } else { + info!("truncate to 5 ok"); + } + + if let Err(e) = file.close() { + error!("close error: {:?}", e); + } else { + info!("close ok"); + } + + info!("[rust_file_test] end"); +} diff --git a/machines/qemu-virt-riscv64/rust/examples/application/loadlib/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/loadlib/Cargo.toml new file mode 100644 index 00000000..1e1065f9 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/loadlib/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "em_loadlib" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_loadlib" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/application/loadlib/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/loadlib/src/lib.rs new file mode 100644 index 00000000..afdcc13a --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/loadlib/src/lib.rs @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove load library example + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +use rt_macros::msh_cmd_export; +use rt_rust::println; +use rt_rust::get_libfn; +use core::ffi::{c_int, c_char}; +use rt_rust::param::Param; + +#[msh_cmd_export(name = "rust_dl_demo", desc = "Rust dynamic library demo")] +fn main(_param: Param) { + println!("\n=== Dynamic Library Demo ==="); + + get_libfn!("/hello.mo", "main", my_hello, ()); + my_hello(); + + get_libfn!("/libmylib.mo", "rust_mylib_add", my_add, c_int, a: c_int, b: c_int); + let s = my_add(15, 20); + println!("my_add(15, 20) = {}", s); + + get_libfn!("/libmylib.mo", "rust_mylib_println", my_println, (), s: *const c_char); + my_println(b"rustlib: Hello World\0".as_ptr() as *const c_char); +} diff --git a/machines/qemu-virt-riscv64/rust/examples/application/mutex/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/mutex/Cargo.toml new file mode 100644 index 00000000..188ad1ba --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/mutex/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "em_mutex" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_mutex" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } diff --git a/machines/qemu-virt-riscv64/rust/examples/application/mutex/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/mutex/src/lib.rs new file mode 100644 index 00000000..b9d1ebfa --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/mutex/src/lib.rs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove mutex test demo + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +use alloc::sync::Arc; +use core::time::Duration; +use rt_macros::msh_cmd_export; +use rt_rust::mutex::Mutex; +use rt_rust::param::Param; +use rt_rust::println; +use rt_rust::thread; +use rt_rust::time; + +#[msh_cmd_export(name = "rust_mutex_demo", desc = "Rust example app.")] +fn main(_param: Param) { + let counter = Arc::new(Mutex::new(0).unwrap()); + let run = move || loop { + time::sleep(Duration::new(2, 0)); + { + let mut c = counter.lock().unwrap(); + *c += 1; + println!("{}", *c); + } + }; + + let _ = thread::Thread::new() + .name("thread 1") + .stack_size(2048) + .start(run.clone()); + time::sleep(Duration::new(1, 0)); + let _ = thread::Thread::new() + .name("thread 2") + .stack_size(2048) + .start(run.clone()); +} diff --git a/machines/qemu-virt-riscv64/rust/examples/application/param/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/param/Cargo.toml new file mode 100644 index 00000000..aa3daae8 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/param/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "em_param" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_param" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } + diff --git a/machines/qemu-virt-riscv64/rust/examples/application/param/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/param/src/lib.rs new file mode 100644 index 00000000..8b0b2ed1 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/param/src/lib.rs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove param test demo + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +use alloc::string::String; +use rt_macros::{rt_app_export, msh_cmd_export}; +use rt_rust::param::Param; +use rt_rust::println; + +// 演示新的模块化宏接口 - 使用 msh_cmd_export 导出参数测试命令 +#[msh_cmd_export(name = "rust_param_demo", desc = "Rust parameter demo")] +fn main(param: Param) { + for i in param { + println!("{}", String::from_utf8_lossy(&*i)) + } +} diff --git a/machines/qemu-virt-riscv64/rust/examples/application/queue/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/queue/Cargo.toml new file mode 100644 index 00000000..4ea390d1 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/queue/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "em_queue" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_queue" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } diff --git a/machines/qemu-virt-riscv64/rust/examples/application/queue/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/queue/src/lib.rs new file mode 100644 index 00000000..09e2ac09 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/queue/src/lib.rs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove queue test demo + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +use alloc::string::String; +use alloc::sync::Arc; +use core::time::Duration; +use rt_macros::msh_cmd_export; +use rt_rust::queue::Queue; +use rt_rust::param::Param; +use rt_rust::println; +use rt_rust::thread; +use rt_rust::time; + +#[msh_cmd_export(name = "rust_queue_demo", desc = "Rust example app.")] +fn main(_param: Param) { + let send = Arc::new(Queue::new(2).unwrap()); + let recv = send.clone(); + + let _ = thread::Thread::new() + .name("thread 1") + .stack_size(1024) + .start(move || { + loop { + time::sleep(Duration::new(1, 0)); + send.send(String::from("msg"), 0).unwrap(); + } + }); + time::sleep(Duration::new(1, 0)); + let _ = thread::Thread::new() + .name("thread 2") + .stack_size(1024) + .start(move || { + loop { + println!("waiting!"); + let a = recv.recv_wait_forever().unwrap(); + println!("recv {}", a); + } + }); +} + diff --git a/machines/qemu-virt-riscv64/rust/examples/application/semaphore/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/semaphore/Cargo.toml new file mode 100644 index 00000000..16aa6701 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/semaphore/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "em_sem" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_sem" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } diff --git a/machines/qemu-virt-riscv64/rust/examples/application/semaphore/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/semaphore/src/lib.rs new file mode 100644 index 00000000..00a25337 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/semaphore/src/lib.rs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove semaphore test demo + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +use alloc::sync::Arc; +use core::time::Duration; +use rt_macros::msh_cmd_export; +use rt_rust::sem::Semaphore; +use rt_rust::param::Param; +use rt_rust::println; +use rt_rust::thread; +use rt_rust::time; + +#[msh_cmd_export(name = "rust_sem_demo", desc = "Rust example app.")] +fn main(_param: Param) { + let send = Arc::new(Semaphore::new().unwrap()); + let recv = send.clone(); + + let _ = thread::Thread::new() + .name("thread 1") + .stack_size(1024) + .start(move || { + loop { + time::sleep(Duration::new(1, 0)); + send.release() + } + }); + time::sleep(Duration::new(1, 0)); + let _ = thread::Thread::new() + .name("thread 2") + .stack_size(1024) + .start(move || { + loop { + println!("waiting!"); + recv.take_wait_forever().unwrap(); + println!("recv a sem!") + } + }); +} + diff --git a/machines/qemu-virt-riscv64/rust/examples/application/thread/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/application/thread/Cargo.toml new file mode 100644 index 00000000..a85e275c --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/thread/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "em_thread" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_thread" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } diff --git a/machines/qemu-virt-riscv64/rust/examples/application/thread/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/application/thread/src/lib.rs new file mode 100644 index 00000000..2063db90 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/application/thread/src/lib.rs @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove thread test demo + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +use core::time::Duration; +use rt_macros::msh_cmd_export; +use rt_rust::param::Param; +use rt_rust::println; +use rt_rust::thread; +use rt_rust::time; + +#[msh_cmd_export(name = "rust_thread_demo", desc = "Rust example app.")] +fn main(_param: Param) { + let _ = thread::Thread::new() + .name("thread 1") + .stack_size(1024) + .start(move || { + loop { + println!("thread a will sleep 1s"); + time::sleep(Duration::new(1, 0)); + } + }); + + let _ = thread::Thread::new() + .name("thread 2") + .stack_size(1024) + .start(move || { + loop { + println!("thread b will sleep 3s"); + time::sleep(Duration::new(3, 0)); + } + }); +} diff --git a/machines/qemu-virt-riscv64/rust/examples/component/SConscript b/machines/qemu-virt-riscv64/rust/examples/component/SConscript new file mode 100644 index 00000000..87aec2be --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/component/SConscript @@ -0,0 +1,71 @@ +import os +import sys +from building import * + +cwd = GetCurrentDir() + +# Import component build module and build support +sys.path.append(os.path.join(cwd, '../../tools')) +from build_component import build_example_component +from build_support import clean_rust_build + +# Load feature configurations +try: + from feature_config_component import setup_all_component_features + setup_all_component_features() +except ImportError: + print("Warning: Could not load component feature configurations") + + +def _has(sym: str) -> bool: + """Helper function to check if a configuration symbol is enabled.""" + try: + return bool(GetDepend([sym])) + except Exception: + return bool(GetDepend(sym)) + + +# Early return if Rust or log component is not enabled +if not _has('RT_USING_RUST'): + group = [] + Return('group') + +if not _has('RUST_LOG_COMPONENT'): + group = [] + Return('group') + +# Source files – component glue code if needed +src = [] +LIBS = [] +LIBPATH = [] +LINKFLAGS = "" + +# Handle clean operation +if GetOption('clean'): + comp_build_dir = clean_rust_build(Dir('#').abspath, "example_component") + if os.path.exists(comp_build_dir): + print(f'Registering {comp_build_dir} for cleanup') + Clean('.', comp_build_dir) + else: + print('No example_component build artifacts to clean') +else: + # Build the component using the extracted build module + import rtconfig + LIBS, LIBPATH, LINKFLAGS = build_example_component( + cwd=cwd, + has_func=_has, + rtconfig=rtconfig, + build_root=os.path.join(Dir('#').abspath, "build", "example_component") + ) + +# Define component group for SCons +group = DefineGroup( + 'example_component_log', + src, + depend=['RT_USING_RUST'], + LIBS=LIBS, + LIBPATH=LIBPATH, + LINKFLAGS=LINKFLAGS +) + +Return('group') \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/component/component_registry/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/component/component_registry/Cargo.toml new file mode 100644 index 00000000..64ec88c2 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/component/component_registry/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "em_component_registry" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_component_registry" +crate-type = ["staticlib"] + +[dependencies] +rt-rust = { path = "../../../core" } +rt_macros = { path = "../../../rt_macros" } + +# Optional dependencies - only included when features are enabled +em_component_log = { path = "../log", optional = true } + +[features] +default = [] +enable-log = ["em_component_log", "em_component_log/enable-log"] \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/component/component_registry/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/component/component_registry/src/lib.rs new file mode 100644 index 00000000..0e97e5ac --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/component/component_registry/src/lib.rs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-25 foxglove Component registry for unified component registration + * 2025-10-29 foxglove Updated to demonstrate new modular macro interface + */ +#![no_std] + +extern crate alloc; + +// 演示新的模块化宏接口 +use rt_macros::rt_component_export; +use rt_rust::param::{Param, ParamItem}; +use rt_rust::println; + +// 重新导出组件功能,供其他模块使用 +#[cfg(feature = "enable-log")] +pub use em_component_log::*; +#[cfg(feature = "enable-log")] +use em_component_log::logging::Level; + +/// 统一的组件注册入口点 +#[cfg(feature = "enable-log")] +#[rt_component_export(name = "rust_component_registry")] +fn init_registry_component() { + println!("[logging component init] hello world"); + log!(Level::Info, "hello world"); + info!("hello world"); + warn!("hello world"); + error!("hello world"); + trace!("hello world"); + debug!("hello world"); +} + +/// 当没有启用任何组件特性时的空实现 +#[cfg(not(feature = "enable-log"))] +pub extern "C" fn component_init() { + // 空实现,确保库仍然可以被链接 +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/component/log/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/component/log/Cargo.toml new file mode 100644 index 00000000..901772b7 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/component/log/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "em_component_log" +version = "0.1.0" +edition = "2021" + +[lib] +name = "em_component_log" +crate-type = ["rlib", "staticlib"] + +[features] +default = [] +enable-log = [] + +[dependencies] +rt-rust = { path = "../../../core" } diff --git a/machines/qemu-virt-riscv64/rust/examples/component/log/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/component/log/src/lib.rs new file mode 100644 index 00000000..0e5a1582 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/component/log/src/lib.rs @@ -0,0 +1,28 @@ + +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-23 foxglove log component demo + */ +#![no_std] + +extern crate alloc; + +#[cfg(feature = "enable-log")] +pub mod logging; +#[cfg(feature = "enable-log")] +use rt_rust::println; +#[cfg(feature = "enable-log")] +use crate::logging::Level; +// 导入rust库的panic_handler +use rt_rust::param::{Param, ParamItem}; + +// 当 enable-log feature 未启用时,提供一个空的实现 +#[cfg(not(feature = "enable-log"))] +pub extern "C" fn component_init() { + // 空实现,确保库仍然可以被链接 +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/component/log/src/logging.rs b/machines/qemu-virt-riscv64/rust/examples/component/log/src/logging.rs new file mode 100644 index 00000000..2767a72a --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/component/log/src/logging.rs @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-20 foxglove micro rust log component + */ +use alloc::string::String; +use rt_rust::println; + +#[repr(usize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Level { + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} + +#[macro_export] +macro_rules! log { + ($level:expr, $($arg:tt)*) => ({ + $crate::logging::_log($level, format_args!($($arg)*)); + }); +} + +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => ({ + $crate::logging::_log($crate::logging::Level::Error, format_args!($($arg)*)); + }); +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => ({ + $crate::logging::_log($crate::logging::Level::Warn, format_args!($($arg)*)); + }); +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => ({ + $crate::logging::_log($crate::logging::Level::Info, format_args!($($arg)*)); + }); +} + +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => ({ + $crate::logging::_log($crate::logging::Level::Debug, format_args!($($arg)*)); + }); +} + +#[macro_export] +macro_rules! trace { + ($($arg:tt)*) => ({ + $crate::logging::_log($crate::logging::Level::Trace, format_args!($($arg)*)); + }); +} + +pub fn _log(level: Level, args: core::fmt::Arguments) { + use core::fmt::Write; + use rt_rust::time; + let mut s = String::new(); + write!(&mut s, "[{:?}][{:?}]", level, time::get_time()).unwrap(); + write!(&mut s, "{}", args).unwrap(); + match level { + Level::Error => { + println!("\x1b[1;31m{}\x1b[0m", s); + } + Level::Warn => { + println!("\x1b[1;33m{}\x1b[0m", s); + } + Level::Info => { + println!("\x1b[1;32m{}\x1b[0m", s); + } + Level::Debug => { + println!("\x1b[1;34m{}\x1b[0m", s); + } + Level::Trace => { + println!("\x1b[1;30m{}\x1b[0m", s); + } + } +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/modules/SConscript b/machines/qemu-virt-riscv64/rust/examples/modules/SConscript new file mode 100644 index 00000000..98e57d1f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/modules/SConscript @@ -0,0 +1,159 @@ +import os +import sys +import subprocess +import toml +from building import * + +cwd = GetCurrentDir() + +RUSTC_FLAGS = { + "linker": "-C linker=ld.lld", + "panic": "-C panic=abort", +} + +CARGO_CMD = { + "base": "cargo build", + "build_std": "-Z build-std=core,alloc,panic_abort", + "target_flag": "--target", + "target_arch": "%s", + "release_profile": "--release", + "debug_profile": "", # No additional flag for debug mode +} + +tools_dir = os.path.join(cwd, '..', '..', 'tools') +sys.path.insert(0, tools_dir) + +from build_support import detect_rust_target, ensure_rust_target_installed, clean_rust_build + +def _has(sym: str) -> bool: + """Helper function to check if a configuration symbol is enabled""" + try: + return bool(GetDepend([sym])) + except Exception: + return bool(GetDepend(sym)) + +def detect_target_for_dynamic_modules(): + """ + Detect the appropriate Rust target for dynamic modules. + For dynamic modules, we need Linux targets instead of bare-metal targets. + """ + if detect_rust_target is not None: + try: + import rtconfig + bare_metal_target = detect_rust_target(_has, rtconfig) + + if bare_metal_target: + if "riscv64" in bare_metal_target: + return "riscv64gc-unknown-linux-gnu" + elif "riscv32" in bare_metal_target: + return "riscv32gc-unknown-linux-gnu" + elif "aarch64" in bare_metal_target: + return "aarch64-unknown-linux-gnu" + elif "arm" in bare_metal_target or "thumb" in bare_metal_target: + return "armv7-unknown-linux-gnueabihf" + except Exception as e: + print(f"Error: Target detection failed: {e}") + raise RuntimeError(f"Failed to detect Rust target for dynamic modules: {e}") + + print("Error: Unable to detect appropriate Rust target for dynamic modules") + raise RuntimeError("Target detection failed - no valid target found") + +def build_rust_module(module_dir, build_root): + """Build a Rust dynamic module with automatic target detection""" + cargo_toml_path = os.path.join(module_dir, 'Cargo.toml') + if not os.path.exists(cargo_toml_path): + return [], [], "" + + with open(cargo_toml_path, 'r') as f: + cargo_config = toml.load(f) + + module_name = cargo_config['package']['name'] + + # Detect target automatically based on the current configuration + target = detect_target_for_dynamic_modules() + print(f"Building Rust module '{module_name}' for target: {target}") + + # Detect debug mode from rtconfig (same as main rust/SConscript) + debug = bool(_has('RUST_DEBUG_BUILD')) + build_mode = "debug" if debug else "release" + print(f"Building in {build_mode} mode") + + # Use global RUSTFLAGS configuration for dynamic modules + rustflags = " ".join(RUSTC_FLAGS.values()) + + # Verify that the target is installed + if ensure_rust_target_installed is not None: + if not ensure_rust_target_installed(target): + print(f"Error: Rust target '{target}' is not installed") + print(f"Please install it with: rustup target add {target}") + return [], [], "" + else: + print(f"Warning: Cannot verify if target '{target}' is installed") + + # Set up build environment + env = os.environ.copy() + env['RUSTFLAGS'] = rustflags + env['CARGO_TARGET_DIR'] = build_root + + # Build the module with configurable parameters using dictionary configuration + build_cmd = [ + CARGO_CMD["base"].split()[0], # 'cargo' + CARGO_CMD["base"].split()[1], # 'build' + CARGO_CMD["target_flag"], # '--target' + target, # actual target architecture + ] + + # Add profile flag based on debug mode + profile_flag = CARGO_CMD["debug_profile"] if debug else CARGO_CMD["release_profile"] + if profile_flag: + build_cmd.append(profile_flag) + + # Add build-std flag if specified + if CARGO_CMD["build_std"]: + build_std_parts = CARGO_CMD["build_std"].split() + build_cmd.extend(build_std_parts) + + try: + subprocess.run(build_cmd, cwd=module_dir, env=env, check=True, capture_output=True) + lib_dir = os.path.join(build_root, target, build_mode) + return [module_name], [lib_dir], "" + except subprocess.CalledProcessError: + return [], [], "" + +# Check dependencies +if not _has('RT_RUST_BUILD_MODULES'): + Return([]) + +build_root = os.path.join(Dir('#').abspath, "build", "rust_modules") + +# Handle clean operation +if GetOption('clean'): + if clean_rust_build is not None: + modules_build_dir = clean_rust_build(Dir('#').abspath, "rust_modules") + if os.path.exists(modules_build_dir): + print(f'Registering {modules_build_dir} for cleanup') + Clean('.', modules_build_dir) + else: + print('No rust_modules build artifacts to clean') + else: + print('Warning: clean_rust_build function not available') +else: + # Build all Rust modules in subdirectories + modules_built = [] + for item in os.listdir(cwd): + item_path = os.path.join(cwd, item) + if os.path.isdir(item_path) and os.path.exists(os.path.join(item_path, 'Cargo.toml')): + result = build_rust_module(item_path, build_root) + if result[0]: + modules_built.extend(result[0]) + + if modules_built: + print(f"Successfully built {len(modules_built)} Rust dynamic module(s): {', '.join(modules_built)}") + +group = DefineGroup( + 'rust_modules', + [], + depend=['RT_RUST_BUILD_MODULES'] +) + +Return('group') \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/.gitignore b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/.gitignore new file mode 100644 index 00000000..5f4fb259 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/.gitignore @@ -0,0 +1,37 @@ +# Rust build artifacts (now in build/rust) +/target/ +**/*.rs.bk +*.pdb + +# Cargo lock file (optional - uncomment if you want to exclude it) +# Cargo.lock + +# Build directories +/build/ +*.o +*.a +*.so +*.dylib +*.dll + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Backup files +*.bak +*.tmp +*.temp + +# Log files +*.log \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/Cargo.lock b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/Cargo.lock new file mode 100644 index 00000000..ea966447 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/Cargo.lock @@ -0,0 +1,115 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "examplelib" +version = "0.1.0" +dependencies = [ + "rt-rust", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rt-rust" +version = "0.1.0" +dependencies = [ + "rt_macros", +] + +[[package]] +name = "rt_macros" +version = "0.1.0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" diff --git a/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/Cargo.toml b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/Cargo.toml new file mode 100644 index 00000000..e44aae28 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] + +[package] +name = "examplelib" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +rt-rust = { path = "../../.." } \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/src/lib.rs b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/src/lib.rs new file mode 100644 index 00000000..b00efd79 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/examples/modules/example_lib/src/lib.rs @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-10 foxglove Basic library module template + */ +#![no_std] +// Bring rt-rust's println! macro into scope +use rt_rust::println; +use core::ffi::{c_char, CStr}; +#[unsafe(no_mangle)] +pub extern "C" fn rust_mylib_println(s: *const c_char) { + if s.is_null() { + println!(""); + } else { + let cs = unsafe {CStr::from_ptr(s)}; + match cs.to_str() { + Ok(msg) => println!("{}", msg), + Err(_) => println!("[invalid UTF-8]"), + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn rust_mylib_add(a: usize, b: usize) -> usize { + a + b +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/Cargo.lock b/machines/qemu-virt-riscv64/rust/rt_macros/Cargo.lock new file mode 100644 index 00000000..20859085 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/Cargo.lock @@ -0,0 +1,101 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rt_macros" +version = "0.1.0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/Cargo.toml b/machines/qemu-virt-riscv64/rust/rt_macros/Cargo.toml new file mode 100644 index 00000000..f58d8a50 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rt_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc_macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" +darling = "0.13.0" + +[dependencies.syn] +version = "1.0" +features = ["extra-traits", "full"] diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/src/lib.rs b/machines/qemu-virt-riscv64/rust/rt_macros/src/lib.rs new file mode 100644 index 00000000..396422c4 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/src/lib.rs @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-29 foxglove Modular macro library for RT-Thread Rust + */ + +//! RT-Thread Rust Macro Library +//! +//! This library provides Rust macros for RT-Thread system, including: +//! +//! - `rt_thread_main!` - Main thread entry macro +//! - `rt_component_export!` - Component export macro +//! - `rt_app_export!` - Application export macro +//! - `msh_cmd_export!` - Shell command export macro +//! +//! ## Usage Examples +//! +//! ### Main Thread Entry +//! ```rust +//! use rt_macros::rt_thread_main; +//! +//! #[rt_thread_main] +//! fn main() { +//! println!("Hello RT-Thread!"); +//! } +//! ``` +//! +//! ### Component Export +//! ```rust +//! use rt_macros::rt_component_export; +//! +//! #[rt_component_export] +//! fn my_component_init() { +//! // Component initialization code +//! } +//! ``` +//! +//! ### Application Export +//! ```rust +//! use rt_macros::rt_app_export; +//! +//! #[rt_app_export] +//! fn my_app_init() { +//! // Application initialization code +//! } +//! ``` +//! +//! ### Shell Command Export +//! ```rust +//! use rt_macros::msh_cmd_export; +//! +//! #[msh_cmd_export(name = "hello", desc = "Say hello")] +//! fn hello_cmd(args: vec::IntoIter) { +//! println!("Hello from shell command!"); +//! } +//! ``` + +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; + +// 导入模块化的宏定义 +mod macros; + +// 新的模块化宏定义 +/// RT-Thread 主函数入口宏 +#[proc_macro_attribute] +pub fn rt_thread_main(args: TokenStream, input: TokenStream) -> TokenStream { + macros::main::rt_thread_main(args, input) +} + +/// RT-Thread 组件导出宏 +#[proc_macro_attribute] +pub fn rt_component_export(args: TokenStream, input: TokenStream) -> TokenStream { + macros::component::rt_component_export(args, input) +} + +/// RT-Thread 应用导出宏 +#[proc_macro_attribute] +pub fn rt_app_export(args: TokenStream, input: TokenStream) -> TokenStream { + macros::app::rt_app_export(args, input) +} + +/// MSH 命令导出宏 +#[proc_macro_attribute] +pub fn msh_cmd_export(args: TokenStream, input: TokenStream) -> TokenStream { + macros::cmd::msh_cmd_export(args, input) +} diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/app.rs b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/app.rs new file mode 100644 index 00000000..7c37a74f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/app.rs @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-29 foxglove Application export macro module + */ + +use darling::FromMeta; +use proc_macro::TokenStream; +use proc_macro2::Span; +use syn::{parse_macro_input, spanned::Spanned, ReturnType, Visibility, Meta}; +use syn::parse::Parser; +use syn::NestedMeta as DarlingNestedMeta; + +#[derive(Debug, FromMeta)] +struct AppArgs { + #[darling(default)] + name: Option, +} + +/// RT-Thread application export macro +/// +/// Used to export a function as an RT-Thread application initialization entry +/// +/// Usage: +/// ```rust +/// #[rt_app_export] +/// fn my_app_init() { +/// // Application initialization logic +/// } +/// ``` +/// +/// Or specify a name: +/// ```rust +/// #[rt_app_export(name = "my_app")] +/// fn my_app_init() { +/// // Application initialization logic +/// } +/// ``` +pub fn rt_app_export(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as syn::ItemFn); + let parser = syn::punctuated::Punctuated::::parse_terminated; + let raw_meta = match parser.parse(args) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + let raw_arg: Vec = raw_meta + .into_iter() + .map(DarlingNestedMeta::Meta) + .collect(); + let parg = AppArgs::from_list(&raw_arg).map_err(|e| e.write_errors()); + let arg = match parg { + Ok(x) => x, + Err(e) => { + return e.into(); + } + }; + + if arg.name.is_none() { + return syn::parse::Error::new( + Span::call_site(), + "`#[rt_app_export]` macro must have attribute `name`", + ) + .to_compile_error() + .into(); + } + + let app_seg_name = format_ident!("__{}_app_seg", arg.name.as_ref().unwrap()); + let app_func_name = format_ident!("__{}_app_func", arg.name.as_ref().unwrap()); + let app_struct_name = format_ident!("__{}_app_seg_struct", arg.name.as_ref().unwrap()); + let mod_name = format_ident!("__app_func_{}_", arg.name.as_ref().unwrap()); + let call_func_name = f.sig.ident.clone(); + + // Check function signature + let valid_signature = f.sig.constness.is_none() + && f.sig.unsafety.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + _ => false, + }; + + if !valid_signature { + return syn::parse::Error::new( + f.span(), + "`#[rt_app_export]` function must have signature `fn()`", + ) + .to_compile_error() + .into(); + } + + let attrs = f.attrs.clone(); + + let origin = quote!( + #(#attrs)* + #f + ); + + let app_seg = quote!( + struct #app_struct_name (*const ()); + unsafe impl Sync for #app_struct_name{} + + #[no_mangle] + pub unsafe extern "C" fn #app_func_name() -> i32 { + #call_func_name(); + 0 + } + + #[link_section = ".rti_fn.6"] + #[no_mangle] + static #app_seg_name: #app_struct_name + = #app_struct_name (#app_func_name as *const ()); + ); + + quote!( + #origin + mod #mod_name { + use super::#call_func_name; + use core::marker::Sync; + + #app_seg + } + ) + .into() +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/cmd.rs b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/cmd.rs new file mode 100644 index 00000000..dadfa40e --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/cmd.rs @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-29 foxglove Command export macro module + */ + +use darling::FromMeta; +use proc_macro::TokenStream; +use proc_macro2::{Literal, Span}; +use syn::{parse_macro_input, spanned::Spanned, ReturnType, Visibility, Meta}; +use syn::parse::Parser; +use syn::NestedMeta as DarlingNestedMeta; + +#[derive(Debug, FromMeta)] +struct CmdArgs { + #[darling(default)] + name: Option, + #[darling(default)] + desc: Option, +} + +/// RT-Thread Shell command export macro +/// +/// Used to export a function as an RT-Thread Shell command +/// +/// Usage: +/// ```rust +/// #[msh_cmd_export(name = "my_cmd", desc = "My command description")] +/// fn my_command(args: vec::IntoIter) { +/// // Command logic +/// } +/// ``` +pub fn msh_cmd_export(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as syn::ItemFn); + let parser = syn::punctuated::Punctuated::::parse_terminated; + let raw_meta = match parser.parse(args) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + let raw_arg: Vec = raw_meta + .into_iter() + .map(DarlingNestedMeta::Meta) + .collect(); + let parg = CmdArgs::from_list(&raw_arg).map_err(|e| e.write_errors()); + let arg = match parg { + Ok(x) => x, + Err(e) => { + return e.into(); + } + }; + + if arg.name.is_none() { + return syn::parse::Error::new( + Span::call_site(), + "`#[msh_cmd_export]` macro must have attribute `name`", + ) + .to_compile_error() + .into(); + } + + let main_func_name = format_ident!("__{}_main_func", arg.name.as_ref().unwrap()); + let cmd_seg_name = format_ident!("__{}_cmd_seg", arg.name.as_ref().unwrap()); + let cmd_struct_name = format_ident!("__{}_cmd_seg_struct", arg.name.as_ref().unwrap()); + let cmd_namestr_name = format_ident!("__{}_cmd_namestr", arg.name.as_ref().unwrap()); + let cmd_descstr_name = format_ident!("__{}_cmd_descstr", arg.name.as_ref().unwrap()); + let mod_name = format_ident!("__cmd_func_{}_", arg.name.as_ref().unwrap()); + let call_func_name = f.sig.ident.clone(); + + // Check function signature + let valid_signature = f.sig.constness.is_none() + && f.sig.unsafety.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.len() == 1 + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + _ => false, + }; + + if !valid_signature { + return syn::parse::Error::new( + f.span(), + "`#[msh_cmd_export]` function must have signature `fn(args: vec::IntoIter)`", + ) + .to_compile_error() + .into(); + } + + let attrs = f.attrs.clone(); + + let origin = quote!( + #(#attrs)* + #f + ); + + let desc = arg.desc.map_or(String::from("No desc\0"), |mut x| { + x.push_str("\0"); + x + }); + let r_desc = Literal::byte_string(desc.as_bytes()); + + let mut cmd_name = arg.name.as_ref().unwrap().clone(); + cmd_name.push_str("\0"); + let r_cmd_name = Literal::byte_string(cmd_name.as_bytes()); + + let desc_len = desc.len(); + let cmd_name_len = cmd_name.len(); + + let core = quote!( + #[no_mangle] + pub unsafe extern "C" fn #main_func_name(argc: u32, argv: *const *const u8) { + use core::iter::Iterator; + use rt_rust::param::ParamItem; + let vec = { + (0..argc as isize) + .map(|i| { + let mut len = 0usize; + loop { + if *(*argv.offset(i)).offset(len as isize) != b'\0' { + len += 1; + } else { + break + } + } + ParamItem::new(core::slice::from_raw_parts::<'static, _>(*argv.offset(i), len)) + }) + .collect::>() + }; + #call_func_name(vec.into_iter()) + } + ); + + let cmd_seg = quote!( + #[link_section = ".rodata.name"] + #[no_mangle] + static #cmd_namestr_name: [u8; #cmd_name_len] = *#r_cmd_name; + #[link_section = ".rodata.name"] + #[no_mangle] + static #cmd_descstr_name: [u8; #desc_len] = *#r_desc; + + #[repr(C)] + struct #cmd_struct_name { + name: *const u8, + desc: *const u8, + opt: *const core::ffi::c_void, + func: extern "C" fn(argc: u32, argv: *const *const u8) -> i32, + } + unsafe impl Sync for #cmd_struct_name{} + + extern "C" fn __wrap_main(argc: u32, argv: *const *const u8) -> i32 { + unsafe { #main_func_name(argc, argv); } + 0 + } + + #[link_section = "FSymTab"] + #[no_mangle] + static #cmd_seg_name: #cmd_struct_name = #cmd_struct_name { + name: #cmd_namestr_name.as_ptr(), + desc: #cmd_descstr_name.as_ptr(), + opt: core::ptr::null(), + func: __wrap_main, + }; + ); + + quote!( + #origin + mod #mod_name { + use super::#call_func_name; + use core::marker::Sync; + extern crate alloc; + use alloc::vec::Vec; + use core::iter::IntoIterator; + + #core + #cmd_seg + } + ) + .into() +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/component.rs b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/component.rs new file mode 100644 index 00000000..20f8fa25 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/component.rs @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-29 foxglove Component export macro module + */ + +use darling::FromMeta; +use proc_macro::TokenStream; +use proc_macro2::Span; +use syn::{parse_macro_input, spanned::Spanned, ReturnType, Visibility, Meta}; +use syn::parse::Parser; +use syn::NestedMeta as DarlingNestedMeta; + +#[derive(Debug, FromMeta)] +struct ComponentArgs { + #[darling(default)] + name: Option, +} + +/// RT-Thread component export macro +/// +/// Used to export a function as an RT-Thread component initialization entry +/// +/// Usage: +/// ```rust +/// #[rt_component_export] +/// fn my_component_init() { +/// // Component initialization logic +/// } +/// ``` +/// +/// Or specify a name: +/// ```rust +/// #[rt_component_export(name = "my_component")] +/// fn my_component_init() { +/// // Component initialization logic +/// } +/// ``` +pub fn rt_component_export(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as syn::ItemFn); + let parser = syn::punctuated::Punctuated::::parse_terminated; + let raw_meta = match parser.parse(args) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + let raw_arg: Vec = raw_meta + .into_iter() + .map(DarlingNestedMeta::Meta) + .collect(); + let parg = ComponentArgs::from_list(&raw_arg).map_err(|e| e.write_errors()); + let arg = match parg { + Ok(x) => x, + Err(e) => { + return e.into(); + } + }; + + if arg.name.is_none() { + return syn::parse::Error::new( + Span::call_site(), + "`#[rt_component_export]` macro must have attribute `name`", + ) + .to_compile_error() + .into(); + } + + let component_seg_name = format_ident!("__{}_component_seg", arg.name.as_ref().unwrap()); + let component_func_name = format_ident!("__{}_component_func", arg.name.as_ref().unwrap()); + let component_struct_name = format_ident!("__{}_component_seg_struct", arg.name.as_ref().unwrap()); + let mod_name = format_ident!("__component_func_{}_", arg.name.as_ref().unwrap()); + let call_func_name = f.sig.ident.clone(); + + // Check function signature + let valid_signature = f.sig.constness.is_none() + && f.sig.unsafety.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + _ => false, + }; + + if !valid_signature { + return syn::parse::Error::new( + f.span(), + "`#[rt_component_export]` function must have signature `fn()`", + ) + .to_compile_error() + .into(); + } + + let attrs = f.attrs.clone(); + + let origin = quote!( + #(#attrs)* + #f + ); + + let component_seg = quote!( + struct #component_struct_name (*const ()); + unsafe impl Sync for #component_struct_name{} + + #[no_mangle] + pub unsafe extern "C" fn #component_func_name() -> i32 { + #call_func_name(); + 0 + } + + #[link_section = ".rti_fn.4"] + #[no_mangle] + static #component_seg_name: #component_struct_name + = #component_struct_name (#component_func_name as *const ()); + ); + + quote!( + #origin + mod #mod_name { + use super::#call_func_name; + use core::marker::Sync; + + #component_seg + } + ) + .into() +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/main.rs b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/main.rs new file mode 100644 index 00000000..d9bfa0bc --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/main.rs @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-29 foxglove Main thread entry macro module + */ + +use darling::FromMeta; +use proc_macro::TokenStream; +use proc_macro2::Span; +use syn::{parse_macro_input, spanned::Spanned, ReturnType, Visibility, Meta}; +use syn::parse::Parser; +use syn::NestedMeta as DarlingNestedMeta; + +#[derive(Debug, FromMeta)] +struct MainArgs { + #[darling(default)] + name: Option, +} + +/// RT-Thread main thread entry macro +/// +/// Used to mark the main entry function of a Rust program, exporting it as +/// a main thread entry recognizable by RT-Thread +/// +/// Usage: +/// ```rust +/// #[rt_thread_main] +/// fn main() { +/// // Main thread logic +/// } +/// ``` +/// +/// Or specify a name: +/// ```rust +/// #[rt_thread_main(name = "my_main")] +/// fn main() { +/// // Main thread logic +/// } +/// ``` +pub fn rt_thread_main(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as syn::ItemFn); + let parser = syn::punctuated::Punctuated::::parse_terminated; + let raw_meta = match parser.parse(args) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + let raw_arg: Vec = raw_meta + .into_iter() + .map(DarlingNestedMeta::Meta) + .collect(); + let parg = MainArgs::from_list(&raw_arg).map_err(|e| e.write_errors()); + let arg = match parg { + Ok(x) => x, + Err(e) => { + return e.into(); + } + }; + + if arg.name.is_none() { + return syn::parse::Error::new( + Span::call_site(), + "`#[rt_thread_main]` macro must have attribute `name`", + ) + .to_compile_error() + .into(); + } + + let main_func_name = format_ident!("__{}_main_func", arg.name.as_ref().unwrap()); + let mod_name = format_ident!("__init_func_{}_", arg.name.as_ref().unwrap()); + let call_func_name = f.sig.ident.clone(); + + // Check function signature + let valid_signature = f.sig.constness.is_none() + && f.sig.unsafety.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + _ => false, + }; + + if !valid_signature { + return syn::parse::Error::new( + f.span(), + "`#[rt_thread_main]` function must have signature `fn()`", + ) + .to_compile_error() + .into(); + } + + let attrs = f.attrs.clone(); + + let origin = quote!( + #(#attrs)* + #f + ); + + let core = quote!( + #[no_mangle] + pub unsafe extern "C" fn #main_func_name(argc: u32, argv: *const *const u8) { + use core::iter::Iterator; + use rt_rust::param::ParamItem; + let vec = { + (0..argc as isize) + .map(|i| { + let mut len = 0usize; + loop { + if *(*argv.offset(i)).offset(len as isize) != b'\0' { + len += 1; + } else { + break + } + } + ParamItem::new(core::slice::from_raw_parts::<'static, _>(*argv.offset(i), len)) + }) + .collect::>() + }; + #call_func_name(vec.into_iter()) + } + ); + + quote!( + #origin + mod #mod_name { + use super::#call_func_name; + extern crate alloc; + use alloc::vec::Vec; + use core::iter::IntoIterator; + + #core + } + ) + .into() +} \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/mod.rs b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/mod.rs new file mode 100644 index 00000000..7014317f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/rt_macros/src/macros/mod.rs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author notes + * 2025-10-29 foxglove RT-Thread Rust macro module + */ + +//! RT-Thread Rust Macro Module +//! +//! This module contains various macro definitions for RT-Thread system: +//! +//! - `rt_thread_main!` - Main thread entry macro +//! - `rt_component_export!` - Component export macro +//! - `rt_app_export!` - Application export macro +//! - `msh_cmd_export!` - Shell command export macro + +pub mod main; +pub mod component; +pub mod app; +pub mod cmd; diff --git a/machines/qemu-virt-riscv64/rust/tools/__init__.py b/machines/qemu-virt-riscv64/rust/tools/__init__.py new file mode 100644 index 00000000..cdce0039 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/tools/__init__.py @@ -0,0 +1 @@ +# User application build tools package \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/tools/build_component.py b/machines/qemu-virt-riscv64/rust/tools/build_component.py new file mode 100644 index 00000000..9170617e --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/tools/build_component.py @@ -0,0 +1,200 @@ +import os +import subprocess + + +# Configuration to feature mapping table for components +# This table defines which RT-Thread configurations should enable which component features +# All feature configurations are now defined in feature_config_component.py +CONFIG_COMPONENT_FEATURE_MAP = {} + + +class ComponentBuildError(Exception): + pass + + +def check_component_dependencies(component_dir, required_dependencies): + """ + Check if a component has the required dependencies + + Args: + component_dir: Component directory path + required_dependencies: List of dependency names to check + + Returns: + bool: True if all required dependencies are present + """ + if not component_dir or not required_dependencies: + return True + + cargo_toml_path = os.path.join(component_dir, 'Cargo.toml') + if not os.path.exists(cargo_toml_path): + return False + + try: + import toml + with open(cargo_toml_path, 'r') as f: + cargo_data = toml.load(f) + + dependencies = cargo_data.get('dependencies', {}) + + # Check if all required dependencies are present + for dep in required_dependencies: + if dep not in dependencies: + return False + + return True + + except Exception as e: + print(f"Warning: Failed to parse {cargo_toml_path}: {e}") + return False + + +def collect_component_features(has_func, component_dir=None): + """ + Collect component features based on RT-Thread configuration using extensible mapping table + + Args: + has_func: Function to check if a configuration is enabled + component_dir: Component directory to check dependencies (optional) + + Returns: + list: List of features to enable + """ + features = [] + + # Iterate through all configured mappings + for config_name, config_info in CONFIG_COMPONENT_FEATURE_MAP.items(): + # Check if this RT-Thread configuration is enabled + if has_func(config_name): + feature_name = config_info['feature'] + required_deps = config_info.get('dependencies', []) + + # If component_dir is provided, check dependencies + if component_dir: + if check_component_dependencies(component_dir, required_deps): + features.append(feature_name) + print(f"Enabling component feature '{feature_name}' for {config_name} in {os.path.basename(component_dir)}") + else: + # If no component_dir provided, enable for all (backward compatibility) + features.append(feature_name) + print(f"Enabling component feature '{feature_name}' for {config_name}") + + return features + +def cargo_build_component_staticlib(rust_dir, target, features, debug, rustflags=None, build_root=None): + """ + Build a Rust component as a static library using Cargo. + + Args: + rust_dir: Directory containing the Rust component + target: Rust target architecture + features: List of features to enable + debug: Whether this is a debug build + rustflags: Additional Rust compilation flags + build_root: Build root directory (if not provided, will raise error) + + Returns: + str: Path to the built library file, or None if build failed + """ + if not build_root: + raise ComponentBuildError("build_root parameter is required") + + build_root = os.path.abspath(build_root) + os.makedirs(build_root, exist_ok=True) + + env = os.environ.copy() + if rustflags: + prev = env.get("RUSTFLAGS", "").strip() + env["RUSTFLAGS"] = (prev + " " + rustflags).strip() if prev else rustflags + env["CARGO_TARGET_DIR"] = build_root + + cmd = [ + "cargo", "build", + "--target", target, + "--manifest-path", os.path.join(rust_dir, "Cargo.toml") + ] + + if not debug: + cmd.insert(2, "--release") + + if features: + cmd += ["--no-default-features", "--features", ",".join(features)] + + print("Building example component log (cargo)…") + res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True) + + if res.returncode != 0: + print("Warning: Example component build failed") + if res.stderr: + print(res.stderr) + return None + + mode = "debug" if debug else "release" + + # Try target-specific path first, then fallback to direct path + lib_path = os.path.join(build_root, target, mode, "libem_component_registry.a") + if os.path.exists(lib_path): + print("Example component log built successfully") + return lib_path + + print("Warning: Library not found at expected location") + print(f"Expected: {lib_path}") + return None + + +def build_example_component(cwd, has_func, rtconfig, build_root=None): + """ + Build the example component. + + Args: + cwd: Current working directory (component directory) + has_func: Function to check if a configuration is enabled + rtconfig: RT-Thread configuration module + build_root: Optional build root directory + + Returns: + tuple: (LIBS, LIBPATH, LINKFLAGS) for SCons + """ + LIBS = [] + LIBPATH = [] + LINKFLAGS = "" + + # Import build support functions + import sys + sys.path.append(os.path.join(cwd, '../rust/tools')) + from build_support import ( + detect_rust_target, + make_rustflags, + ) + + target = detect_rust_target(has_func, rtconfig) + + # Build mode and features + debug = bool(has_func('RUST_DEBUG_BUILD')) + + # Build the component registry + registry_dir = os.path.join(cwd, 'component_registry') + features = collect_component_features(has_func, registry_dir) + + rustflags = make_rustflags(rtconfig, target) + + rust_lib = cargo_build_component_staticlib( + rust_dir=registry_dir, + target=target, + features=features, + debug=debug, + rustflags=rustflags, + build_root=build_root + ) + + if rust_lib: + LIBS = ['em_component_registry'] + LIBPATH = [os.path.dirname(rust_lib)] + # Add LINKFLAGS to ensure component is linked into final binary + LINKFLAGS += " -Wl,--whole-archive -lem_component_registry -Wl,--no-whole-archive" + LINKFLAGS += " -Wl,--allow-multiple-definition" + print('Example component registry library linked successfully') + else: + print('Warning: Failed to build example component registry library') + + return LIBS, LIBPATH, LINKFLAGS \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/tools/build_support.py b/machines/qemu-virt-riscv64/rust/tools/build_support.py new file mode 100644 index 00000000..eb05fc1f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/tools/build_support.py @@ -0,0 +1,233 @@ +import os +import subprocess + + +def _parse_cflags(cflags: str): + info = { + "march": None, + "mabi": None, + "rv_bits": None, # 32 or 64 + "has_f": False, + "has_d": False, + } + + if not cflags: + return info + + parts = cflags.split() + for flag in parts: + if flag.startswith("-march="): + info["march"] = flag.split("=", 1)[1] + if "rv32" in info["march"]: + info["rv_bits"] = 32 + elif "rv64" in info["march"]: + info["rv_bits"] = 64 + # crude feature detection + m = info["march"] + if m: + info["has_f"] = ("f" in m) + info["has_d"] = ("d" in m) + elif flag.startswith("-mabi="): + info["mabi"] = flag.split("=", 1)[1] + if info["mabi"] in ("ilp32d", "ilp32f", "lp64d", "lp64f"): + # floating-point ABI implies FPU availability + info["has_f"] = True + info["has_d"] = info["mabi"].endswith("d") + + return info + + +def detect_rust_target(has, rtconfig): + """ + Decide the Rust target triple based on RT-Thread Kconfig and rtconfig.*. + `has` is a callable: has("SYMBOL") -> bool + """ + # ARM Cortex-M + if has("ARCH_ARM"): + # FPU hints from flags/macros + cflags = getattr(rtconfig, "CFLAGS", "") + hard_float = "-mfloat-abi=hard" in cflags or has("ARCH_ARM_FPU") or has("ARCH_FPU_VFP") + + if has("ARCH_ARM_CORTEX_M3"): + return "thumbv7m-none-eabi" + if has("ARCH_ARM_CORTEX_M4") or has("ARCH_ARM_CORTEX_M7"): + return "thumbv7em-none-eabihf" if hard_float else "thumbv7em-none-eabi" + if has("ARCH_ARM_CORTEX_M33"): + # v8m.main + return "thumbv8m.main-none-eabi" + if has("ARCH_ARM_CORTEX_A"): + return "armv7a-none-eabi" + + # AArch64 + if has("ARCH_AARCH64") or has("ARCH_ARMV8") or has("ARCH_ARM64"): + if has("ARCH_CPU_FLOAT_ABI_SOFT"): + return "aarch64-unknown-none-softfloat" + return "aarch64-unknown-none" + + # RISC-V + if has("ARCH_RISCV32") or has("ARCH_RISCV64"): + cflags = getattr(rtconfig, "CFLAGS", "") + info = _parse_cflags(cflags) + + # fallback to Kconfig hint if march not present + rv_bits = info["rv_bits"] or (32 if has("ARCH_RISCV32") else 64) + + # ABI must carry f/d to actually use hard-float calling convention + abi = info["mabi"] or "" + abi_has_fp = abi.endswith("f") or abi.endswith("d") + + if rv_bits == 32: + # Only pick *f* target when ABI uses hard-float; otherwise use soft-float even if core has F/D + return "riscv32imafc-unknown-none-elf" if abi_has_fp else "riscv32imac-unknown-none-elf" + else: + # rv64: prefer gc (includes fd) only when ABI uses hard-float + return "riscv64gc-unknown-none-elf" if abi_has_fp else "riscv64imac-unknown-none-elf" + + # Fallback by ARCH string or CFLAGS + arch = getattr(rtconfig, "ARCH", None) + if arch: + arch_l = arch.lower() + if "aarch64" in arch_l: + return "aarch64-unknown-none" + if "arm" == arch_l or "armv7" in arch_l: + return "armv7a-none-eabi" + if "riscv32" in arch_l: + return "riscv32imac-unknown-none-elf" + if "riscv64" in arch_l or "risc-v" in arch_l: + # Many BSPs use "risc-v" token; assume 64-bit for virt64 + return "riscv64imac-unknown-none-elf" + + # Parse CFLAGS for hints + cflags = getattr(rtconfig, "CFLAGS", "") + if "-mcpu=cortex-m3" in cflags: + return "thumbv7m-none-eabi" + if "-mcpu=cortex-m4" in cflags or "-mcpu=cortex-m7" in cflags: + if "-mfpu=" in cflags and "-mfloat-abi=hard" in cflags: + return "thumbv7em-none-eabihf" + return "thumbv7em-none-eabi" + if "-march=rv32" in cflags: + march_val = None + mabi_val = None + for flag in cflags.split(): + if flag.startswith("-march="): + march_val = flag[len("-march="):] + elif flag.startswith("-mabi="): + mabi_val = flag[len("-mabi="):] + has_f_or_d = False + if march_val and any(x in march_val for x in ("f", "d")): + has_f_or_d = True + if mabi_val and any(x in mabi_val for x in ("f", "d")): + has_f_or_d = True + return "riscv32imafc-unknown-none-elf" if has_f_or_d else "riscv32imac-unknown-none-elf" + if "-march=rv64" in cflags: + march_val = None + mabi_val = None + for flag in cflags.split(): + if flag.startswith("-march="): + march_val = flag[len("-march="):] + elif flag.startswith("-mabi="): + mabi_val = flag[len("-mabi="):] + has_f_or_d = False + if mabi_val and (("lp64d" in mabi_val) or ("lp64f" in mabi_val)): + has_f_or_d = True + if march_val and any(x in march_val for x in ("f", "d")): + has_f_or_d = True + if mabi_val and any(x in mabi_val for x in ("f", "d")): + has_f_or_d = True + if has_f_or_d: + return "riscv64gc-unknown-none-elf" + return "riscv64imac-unknown-none-elf" + + return None + + +def make_rustflags(rtconfig, target: str): + rustflags = [ + "-C", "opt-level=z", + "-C", "panic=abort", + "-C", "relocation-model=static", + ] + + if "riscv" in target: + rustflags += [ + "-C", "code-model=medium", + "-C", "link-dead-code", + ] + # propagate march/mabi for consistency (use link-arg for staticlib builds – harmless) + cflags = getattr(rtconfig, "CFLAGS", "") + for flag in cflags.split(): + if flag.startswith("-march=") or flag.startswith("-mabi="): + rustflags += ["-C", f"link-arg={flag}"] + + if "thumb" in target or "aarch64" in target: + rustflags += ["-C", "link-arg=-nostartfiles"] + + return " ".join(rustflags) + + +def collect_features(has): + feats = [] + if has("RT_USING_SMP"): + feats.append("smp") + return feats + + +def verify_rust_toolchain(): + try: + r1 = subprocess.run(["rustc", "--version"], capture_output=True, text=True) + r2 = subprocess.run(["cargo", "--version"], capture_output=True, text=True) + return r1.returncode == 0 and r2.returncode == 0 + except Exception: + return False + + +def ensure_rust_target_installed(target: str): + try: + result = subprocess.run(["rustup", "target", "list", "--installed"], capture_output=True, text=True) + if result.returncode == 0 and target in result.stdout: + return True + print(f"Rust target '{target}' is not installed.") + print(f"Please install it with: rustup target add {target}") + except Exception: + print("Warning: Failed to check rustup target list (rustup missing?)") + return False + + +def cargo_build_staticlib(rust_dir: str, target: str, features, debug: bool, rustflags: str = None): + build_root = os.path.join((os.path.abspath(os.path.join(rust_dir, os.pardir, os.pardir))), "build", "rust") + target_dir = os.path.join(build_root, "target") + os.makedirs(build_root, exist_ok=True) + + env = os.environ.copy() + if rustflags: + prev = env.get("RUSTFLAGS", "").strip() + env["RUSTFLAGS"] = (prev + " " + rustflags).strip() if prev else rustflags + env["CARGO_TARGET_DIR"] = target_dir + + cmd = ["cargo", "build", "--target", target, "--manifest-path", os.path.join(rust_dir, "Cargo.toml")] + if not debug: + cmd.insert(2, "--release") + if features: + cmd += ["--no-default-features", "--features", ",".join(features)] + + print("Building Rust component (cargo)…") + res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True) + if res.returncode != 0: + print("Warning: Rust build failed") + if res.stderr: + print(res.stderr) + return None + + mode = "debug" if debug else "release" + lib_path = os.path.join(target_dir, target, mode, "librt_rust.a") + if os.path.exists(lib_path): + print("Rust component built successfully") + return lib_path + print("Warning: Library not found at expected location") + return None + + +def clean_rust_build(bsp_root: str, artifact_type: str = "rust"): + """Return the build directory path for SCons Clean operation""" + build_dir = os.path.join(bsp_root, "build", artifact_type) + return build_dir \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/tools/build_usrapp.py b/machines/qemu-virt-riscv64/rust/tools/build_usrapp.py new file mode 100644 index 00000000..9d97a60f --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/tools/build_usrapp.py @@ -0,0 +1,392 @@ +import os +import subprocess +import toml +import shutil + + +# Configuration to feature mapping table +# This table defines which RT-Thread configurations should enable which Rust features +# All feature configurations are now defined in feature_config_examples.py +CONFIG_FEATURE_MAP = {} + +# Application directory to Kconfig mapping table +# This table defines which Kconfig options control which application directories +APP_CONFIG_MAP = { + 'fs': 'RT_RUST_EXAMPLE_FS', + 'loadlib': 'RT_RUST_EXAMPLE_LOADLIB', + 'mutex': 'RT_RUST_EXAMPLE_MUTEX', + 'param': 'RT_RUST_EXAMPLE_PARAM', + 'queue': 'RT_RUST_EXAMPLE_QUEUE', + 'semaphore': 'RT_RUST_EXAMPLE_SEMAPHORE', + 'thread': 'RT_RUST_EXAMPLE_THREAD' +} + + +def should_build_app(app_dir, has_func): + """ + Check if an application should be built based on Kconfig configuration + + Args: + app_dir: Application directory path + has_func: Function to check if a configuration is enabled + + Returns: + bool: True if the application should be built + """ + # Get the application name from the directory + app_name = os.path.basename(app_dir) + + # Check if there's a specific Kconfig option for this app + if app_name in APP_CONFIG_MAP: + config_option = APP_CONFIG_MAP[app_name] + return has_func(config_option) + + # If no specific config found, check if applications are enabled in general + return has_func('RT_RUST_BUILD_APPLICATIONS') + + +def check_app_dependencies(app_dir, required_dependencies): + """ + Check if an application has the required dependencies + + Args: + app_dir: Application directory path + required_dependencies: List of dependency names to check + + Returns: + bool: True if all required dependencies are present + """ + if not app_dir or not required_dependencies: + return True + + cargo_toml_path = os.path.join(app_dir, 'Cargo.toml') + if not os.path.exists(cargo_toml_path): + return False + + try: + with open(cargo_toml_path, 'r') as f: + cargo_data = toml.load(f) + + dependencies = cargo_data.get('dependencies', {}) + + # Check if all required dependencies are present + for dep in required_dependencies: + if dep not in dependencies: + return False + + return True + + except Exception as e: + print(f"Warning: Failed to parse {cargo_toml_path}: {e}") + return False + + +def collect_features(has_func, app_dir=None): + """ + Collect Rust features based on RT-Thread configuration using extensible mapping table + + Args: + has_func: Function to check if a configuration is enabled + app_dir: Application directory to check dependencies (optional) + + Returns: + list: List of features to enable + """ + features = [] + + # Iterate through all configured mappings + for config_name, config_info in CONFIG_FEATURE_MAP.items(): + # Check if this RT-Thread configuration is enabled + if has_func(config_name): + feature_name = config_info['feature'] + required_deps = config_info.get('dependencies', []) + + # If app_dir is provided, check dependencies + if app_dir: + if check_app_dependencies(app_dir, required_deps): + features.append(feature_name) + print(f"Enabling feature '{feature_name}' for {config_name} in {os.path.basename(app_dir)}") + else: + # If no app_dir provided, enable for all (backward compatibility) + features.append(feature_name) + print(f"Enabling feature '{feature_name}' for {config_name}") + + return features + + + + + +class UserAppBuildError(Exception): + """User application build error exception""" + pass + + +def parse_cargo_toml(cargo_toml_path): + """ + Parse Cargo.toml file to extract library name and library type + + Args: + cargo_toml_path: Path to Cargo.toml file + + Returns: + tuple: (lib_name, is_staticlib) + """ + try: + with open(cargo_toml_path, 'r') as f: + cargo_data = toml.load(f) + + package_name = cargo_data.get('package', {}).get('name') + if not package_name: + raise UserAppBuildError(f"No package name found in {cargo_toml_path}") + + lib_config = cargo_data.get('lib', {}) + crate_type = lib_config.get('crate-type', []) + is_staticlib = 'staticlib' in crate_type + + # Use lib name if specified, otherwise use package name + lib_name = lib_config.get('name', package_name) + + return lib_name, is_staticlib + + except Exception as e: + raise UserAppBuildError(f"Failed to parse {cargo_toml_path}: {e}") + + +def discover_user_apps(base_dir): + """ + Discover all user application directories + + Args: + base_dir: Base directory path + + Returns: + list: List of directories containing Cargo.toml + """ + user_apps = [] + + for root, dirs, files in os.walk(base_dir): + if 'Cargo.toml' in files: + if 'target' in root or 'build' in root: + continue + user_apps.append(root) + + return user_apps + + +def build_user_app(app_dir, target, debug, rustflags, build_root, features=None): + """ + Build a single user application + + Args: + app_dir: Application directory + target: Rust target architecture + debug: Whether this is a debug build + rustflags: Rust compilation flags + build_root: Build root directory + features: List of features to enable + + Returns: + tuple: (success, lib_name, lib_path) + """ + try: + cargo_toml_path = os.path.join(app_dir, 'Cargo.toml') + lib_name, is_staticlib = parse_cargo_toml(cargo_toml_path) + + if not is_staticlib: + return False, None, None + + env = os.environ.copy() + env['RUSTFLAGS'] = rustflags + env['CARGO_TARGET_DIR'] = build_root + + cmd = ['cargo', 'build', '--target', target] + if not debug: + cmd.append('--release') + + # Add features if specified + if features: + cmd.extend(['--features', ','.join(features)]) + + print(f"Building example user app {lib_name} (cargo)…") + result = subprocess.run(cmd, cwd=app_dir, env=env, + capture_output=True, text=True) + + if result.returncode != 0: + print(f"Failed to build user app in {app_dir}") + print(f"Command: {' '.join(cmd)}") + print(f"Return code: {result.returncode}") + print(f"STDOUT: {result.stdout}") + print(f"STDERR: {result.stderr}") + return False, None, None + + lib_file = find_library_file(build_root, target, lib_name, debug) + if lib_file: + # Return the library name for linking + return True, lib_name, lib_file + else: + print(f"Library file not found for lib {lib_name}") + return False, None, None + + except Exception as e: + print(f"Exception occurred while building user app in {app_dir}: {e}") + return False, None, None + + +def find_library_file(build_root, target, lib_name, debug): + """ + Find the generated library file + + Args: + build_root: Build root directory + target: Rust target architecture + lib_name: Library name + debug: Whether this is a debug build + + Returns: + str: Library file path, or None if not found + """ + profile = "debug" if debug else "release" + + possible_names = [ + f"lib{lib_name}.a", + f"lib{lib_name.replace('-', '_')}.a" + ] + + search_paths = [ + os.path.join(build_root, target, profile), + os.path.join(build_root, target, profile, "deps") + ] + + for search_path in search_paths: + if not os.path.exists(search_path): + continue + + for name in possible_names: + lib_path = os.path.join(search_path, name) + if os.path.exists(lib_path): + return lib_path + + return None + + +def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func): + """ + Build all user applications + + Args: + base_dir: User applications base directory + target: Rust target architecture + debug: Whether this is a debug build + rustflags: Rust compilation flags + build_root: Build root directory + has_func: Function to check if a configuration is enabled + + Returns: + tuple: (LIBS, LIBPATH, success_count, total_count) + """ + LIBS = [] + LIBPATH = [] + success_count = 0 + + user_apps = discover_user_apps(base_dir) + total_count = len(user_apps) + + for app_dir in user_apps: + # Check if this application should be built based on Kconfig + if not should_build_app(app_dir, has_func): + app_name = os.path.basename(app_dir) + print(f"Skipping {app_name} (disabled in Kconfig)") + continue + + # Collect features for this specific app + features = collect_features(has_func, app_dir) + success, lib_name, lib_path = build_user_app(app_dir, target, debug, rustflags, build_root, features) + + if success and lib_path: + app_name = os.path.basename(app_dir) + print(f"Example user app {app_name} built successfully") + LIBS.append(lib_name) + lib_dir = os.path.dirname(lib_path) + if lib_dir not in LIBPATH: + LIBPATH.append(lib_dir) + success_count += 1 + + return LIBS, LIBPATH, success_count, total_count + + +def generate_linkflags(LIBS, LIBPATH): + """ + Generate link flags + + Args: + LIBS: List of library names + LIBPATH: List of library paths + + Returns: + str: Link flags string + """ + if not LIBS or not LIBPATH: + return "" + + linkflags = f" -L{LIBPATH[0]} -Wl,--whole-archive" + for lib in LIBS: + linkflags += f" -l{lib}" + linkflags += " -Wl,--no-whole-archive -Wl,--allow-multiple-definition" + + return linkflags + + +def clean_user_apps_build(build_root): + """ + Clean user applications build artifacts + + Args: + build_root: Build root directory + """ + if os.path.exists(build_root): + shutil.rmtree(build_root) + + +def build_example_usrapp(cwd, has_func, rtconfig, build_root=None): + """ + Build the example user applications. + + Args: + cwd: Current working directory (usrapp directory) + has_func: Function to check if a configuration is enabled + rtconfig: RT-Thread configuration module + build_root: Optional build root directory + + Returns: + tuple: (LIBS, LIBPATH, LINKFLAGS) for SCons + """ + LIBS = [] + LIBPATH = [] + LINKFLAGS = "" + + try: + # Import build support functions + import sys + sys.path.append(os.path.join(cwd, '../rust/tools')) + import build_support as rust_build_support + + target = rust_build_support.detect_rust_target(has_func, rtconfig) + debug = bool(has_func('RUST_DEBUG_BUILD')) + rustflags = rust_build_support.make_rustflags(rtconfig, target) + LIBS, LIBPATH, success_count, total_count = build_all_user_apps( + cwd, target, debug, rustflags, build_root, has_func + ) + + if success_count == 0 and total_count > 0: + print(f'Warning: Failed to build all {total_count} user applications') + elif success_count > 0: + LINKFLAGS = generate_linkflags(LIBS, LIBPATH) + print(f'Example user apps linked successfully') + + except UserAppBuildError as e: + print(f'Error: {e}') + except Exception as e: + print(f'Unexpected error during user apps build: {e}') + + return LIBS, LIBPATH, LINKFLAGS \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/tools/feature_config_component.py b/machines/qemu-virt-riscv64/rust/tools/feature_config_component.py new file mode 100644 index 00000000..221d529a --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/tools/feature_config_component.py @@ -0,0 +1,30 @@ +import sys +import os +from building import * +cwd = GetCurrentDir() +sys.path.append(os.path.join(cwd, 'tools')) +from build_component import CONFIG_COMPONENT_FEATURE_MAP + +def setup_all_component_features(): + """ + Setup all component feature mappings. + This function configures which RT-Thread configurations should enable which component features. + """ + + CONFIG_COMPONENT_FEATURE_MAP.update({ + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, + }) + + print("All component feature mappings have been configured!") + + +if __name__ == "__main__": + setup_all_component_features() + print(f"Total component configurations: {len(CONFIG_COMPONENT_FEATURE_MAP)}") + print("Available component configurations:") + for config, info in CONFIG_COMPONENT_FEATURE_MAP.items(): + print(f" {config}: {info['feature']} - {info['description']}") \ No newline at end of file diff --git a/machines/qemu-virt-riscv64/rust/tools/feature_config_examples.py b/machines/qemu-virt-riscv64/rust/tools/feature_config_examples.py new file mode 100644 index 00000000..1e4befa6 --- /dev/null +++ b/machines/qemu-virt-riscv64/rust/tools/feature_config_examples.py @@ -0,0 +1,26 @@ +import sys +import os +from building import * +cwd = GetCurrentDir() +sys.path.append(os.path.join(cwd, 'tools')) +from build_usrapp import CONFIG_FEATURE_MAP + +def setup_all_example_features(): + + CONFIG_FEATURE_MAP.update({ + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, + }) + + print("All example feature mappings have been configured!") + + +if __name__ == "__main__": + setup_all_example_features() + print(f"Total configurations: {len(CONFIG_FEATURE_MAP)}") + print("Available configurations:") + for config, info in CONFIG_FEATURE_MAP.items(): + print(f" {config}: {info['feature']} - {info['description']}") \ No newline at end of file