diff options
Diffstat (limited to 'components/rust/core/src')
27 files changed, 2233 insertions, 0 deletions
diff --git a/components/rust/core/src/allocator.rs b/components/rust/core/src/allocator.rs new file mode 100644 index 0000000000..731a072710 --- /dev/null +++ b/components/rust/core/src/allocator.rs @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2006-2025, 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; + +pub struct RttAlloc; +unsafe impl GlobalAlloc for RttAlloc { + 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(), + } + } + } + + 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); + } + } + + 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 { + 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); + + mem_free_aligned(ptr as *mut c_void); + + new_ptr + } else { + 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/components/rust/core/src/api/base.rs b/components/rust/core/src/api/base.rs new file mode 100644 index 0000000000..fabb00d5ba --- /dev/null +++ b/components/rust/core/src/api/base.rs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2025, 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<i32> 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<i64> 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 + } +} diff --git a/components/rust/core/src/api/interrupt.rs b/components/rust/core/src/api/interrupt.rs new file mode 100644 index 0000000000..9a62442cb9 --- /dev/null +++ b/components/rust/core/src/api/interrupt.rs @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2025, 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, T>(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/components/rust/core/src/api/libloading.rs b/components/rust/core/src/api/libloading.rs new file mode 100644 index 0000000000..438f1fb40b --- /dev/null +++ b/components/rust/core/src/api/libloading.rs @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2006-2025, 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<T>` 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<Self, DlError> { + let cstr = match CString::new(path) { + Ok(s) => s, + Err(_) => { + 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<Self, DlError> { + 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, DlError> { + 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<T>(&self, symbol: *const c_char) -> Result<Symbol<'_, T>, 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<T>(&self, symbol: &[u8]) -> Result<Symbol<'_, T>, DlError> { + let (ptr, _guard_buf): (*const c_char, Option<Vec<u8>>) = 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)) + }; + 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<T>, +} + +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<U>(&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) + } + + 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`. */ + /* Safety: 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) } + } +} + +#[inline] +pub fn last_error() -> *const c_char { + libc::last_error_ptr() +} diff --git a/components/rust/core/src/api/mem.rs b/components/rust/core/src/api/mem.rs new file mode 100644 index 0000000000..8b998f77c3 --- /dev/null +++ b/components/rust/core/src/api/mem.rs @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2006-2025, 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; + +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) + } +} + +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) + } +} + +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) + } +} + +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) + } +} + +pub fn mem_free(ptr: *mut c_void) { + if !ptr.is_null() { + unsafe { rt_free(ptr) }; + } +} + +pub fn mem_free_aligned(ptr: *mut c_void) { + if !ptr.is_null() { + unsafe { rt_free_align(ptr) }; + } +} diff --git a/components/rust/core/src/api/mod.rs b/components/rust/core/src/api/mod.rs new file mode 100644 index 0000000000..8fc2ba2301 --- /dev/null +++ b/components/rust/core/src/api/mod.rs @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2025, 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/components/rust/core/src/api/mutex.rs b/components/rust/core/src/api/mutex.rs new file mode 100644 index 0000000000..176e02e2e7 --- /dev/null +++ b/components/rust/core/src/api/mutex.rs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006-2025, 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<APIRawMutex> { + 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); + } +} diff --git a/components/rust/core/src/api/queue.rs b/components/rust/core/src/api/queue.rs new file mode 100644 index 0000000000..96665e47f2 --- /dev/null +++ b/components/rust/core/src/api/queue.rs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2025, 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<APIRawQueue> { + 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); + } +} diff --git a/components/rust/core/src/api/sem.rs b/components/rust/core/src/api/sem.rs new file mode 100644 index 0000000000..43c21978d2 --- /dev/null +++ b/components/rust/core/src/api/sem.rs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2006-2025, 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<APIRawSem> { + 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); + } +} diff --git a/components/rust/core/src/api/thread.rs b/components/rust/core/src/api/thread.rs new file mode 100644 index 0000000000..d8b1ed295b --- /dev/null +++ b/components/rust/core/src/api/thread.rs @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2006-2025, 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 RT-Thread C code */ +pub type APIRawThread = rt_thread_t; +/* Thread entry function, defined by RT-Thread C code */ +pub type ThreadEntry = extern "C" fn(parameter: *mut c_void); + +#[inline] +pub fn thread_create( + name: &str, + entry: ThreadEntry, + param: *mut c_void, + stack_size: u32, + priority: u8, + tick: u32, +) -> Option<APIRawThread> { + 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) + } +} + +#[inline] +pub fn thread_delete(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_delete(th).into() } +} + +#[inline] +pub fn thread_self() -> Option<APIRawThread> { + let ret; + unsafe { + ret = rt_thread_self(); + } + if ret == ptr::null_mut() { + None + } else { + Some(ret) + } +} + +#[inline] +pub fn thread_startup(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_startup(th).into() } +} + +#[inline] +pub fn thread_delay(ticks: usize) -> RttCResult { + unsafe { rt_thread_delay(ticks as _).into() } +} + +#[inline] +pub fn thread_m_delay(ms: i32) -> RttCResult { + unsafe { rt_thread_mdelay(ms as _).into() } +} + +#[inline] +pub fn thread_yield() { + unsafe { + rt_thread_yield(); + } +} + +#[inline] +pub fn thread_suspend(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_suspend(th).into() } +} + +#[inline] +pub fn thread_resume(th: APIRawThread) -> RttCResult { + unsafe { rt_thread_resume(th).into() } +} diff --git a/components/rust/core/src/bindings/libc.rs b/components/rust/core/src/bindings/libc.rs new file mode 100644 index 0000000000..51efc78b35 --- /dev/null +++ b/components/rust/core/src/bindings/libc.rs @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2006-2025, 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}; + +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 */ +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; +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() } +} diff --git a/components/rust/core/src/bindings/librt.rs b/components/rust/core/src/bindings/librt.rs new file mode 100644 index 0000000000..3f16c014fd --- /dev/null +++ b/components/rust/core/src/bindings/librt.rs @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2006-2025, 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; +} diff --git a/components/rust/core/src/bindings/mod.rs b/components/rust/core/src/bindings/mod.rs new file mode 100644 index 0000000000..ae2610e9c8 --- /dev/null +++ b/components/rust/core/src/bindings/mod.rs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2025, 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/components/rust/core/src/fs.rs b/components/rust/core/src/fs.rs new file mode 100644 index 0000000000..6ad02037ab --- /dev/null +++ b/components/rust/core/src/fs.rs @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2006-2025, 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<File> { + 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<String> { + 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<usize> { + 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<usize> { + 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<i64> { + 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/components/rust/core/src/init.rs b/components/rust/core/src/init.rs new file mode 100644 index 0000000000..3f8e326e82 --- /dev/null +++ b/components/rust/core/src/init.rs @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2006-2025, 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 initialization of the Rust component within RT-Thread. + */ + +use crate::println; + +#[no_mangle] +pub extern "C" fn rust_init() -> i32 { + println!("[rust_init]: Rust component initialized!"); + 0 +} diff --git a/components/rust/core/src/lib.rs b/components/rust/core/src/lib.rs new file mode 100644 index 0000000000..2787b7240a --- /dev/null +++ b/components/rust/core/src/lib.rs @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2006-2025, 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 core library + +Provides basic system support functionality including file system, network, +and device interfaces. Designed for embedded devices running 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; + +#[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<T> = Result<T, RTTError>; diff --git a/components/rust/core/src/libloader.rs b/components/rust/core/src/libloader.rs new file mode 100644 index 0000000000..5fb66d98a9 --- /dev/null +++ b/components/rust/core/src/libloader.rs @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2006-2025, 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) + */ + +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, DlError> { + 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, DlError> { + 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<Symbol<'lib, T>, DlError> { + lib.get_ptr(symbol) +} + +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<R, F>(path: *const c_char, flags: c_int, f: F) -> Result<R, DlError> +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<fn($($t,)*) -> $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<fn() -> $ret> = lib.get($fn_name.as_bytes()).unwrap(); + func() + } + }; +} diff --git a/components/rust/core/src/mutex.rs b/components/rust/core/src/mutex.rs new file mode 100644 index 0000000000..651fa7ef78 --- /dev/null +++ b/components/rust/core/src/mutex.rs @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2006-2025, 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<T: Send, M: RawMutexOps + Sized> Send for Mutex<T, M> {} +unsafe impl<T: Send, M: RawMutexOps + Sized> Sync for Mutex<T, M> {} + +pub struct Mutex<T: ?Sized, M = SleepMutex> +where + M: RawMutexOps + Sized, +{ + mutex: M, + data: UnsafeCell<T>, +} + +/* Impl for Default Mutex<M = SleepMutex, NAME = UnnamedObj> */ +impl<T> Mutex<T> { + pub fn new(t: T) -> Result<Self, RTTError> { + Ok(Mutex { + mutex: SleepMutex::create("Unnamed")?, + data: UnsafeCell::new(t), + }) + } + + pub fn new_with_name(t: T, name: &str) -> Result<Self, RTTError> { + Ok(Mutex { + mutex: SleepMutex::create(name)?, + data: UnsafeCell::new(t), + }) + } + + pub fn try_lock(&self, max_wait: isize) -> Result<MutexGuard<'_, SleepMutex, T>, RTTError> { + self.mutex.take(max_wait)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } + + pub fn lock(&self) -> Result<MutexGuard<'_, SleepMutex, T>, RTTError> { + self.mutex.take(RT_WAITING_FOREVER)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } +} + +/* Impl for all Mutex */ +impl<T, M: RawMutexOps> Mutex<T, M> { + pub fn spec_new(t: T) -> Result<Self, RTTError> { + Ok(Mutex { + mutex: M::create("Unnamed")?, + data: UnsafeCell::new(t), + }) + } + + pub fn spec_new_with_name(t: T, name: &str) -> Result<Self, RTTError> { + Ok(Mutex { + mutex: M::create(name)?, + data: UnsafeCell::new(t), + }) + } + + pub fn spec_try_lock(&self, max_wait: isize) -> Result<MutexGuard<'_, M, T>, RTTError> { + self.mutex.take(max_wait)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } + + pub fn spec_lock(&self) -> Result<MutexGuard<'_, M, T>, RTTError> { + self.mutex.take(RT_WAITING_FOREVER)?; + Ok(MutexGuard { + __mutex: &self.mutex, + __data: &self.data, + }) + } +} + +impl<T: ?Sized> fmt::Debug for Mutex<T> { + 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<T>, +} + +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<InterruptFlag>, AtomicBool); + +pub trait RawMutexOps: Sized { + fn create(name: &str) -> Result<Self, RTTError>; + fn take(&self, max_wait: isize) -> Result<(), RTTError>; + fn release(&self); + fn drop(&mut self); +} + +impl RawMutexOps for AtomicMutex { + fn create(_name: &str) -> Result<Self, RTTError> { + 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<Self, RTTError> { + 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/components/rust/core/src/out.rs b/components/rust/core/src/out.rs new file mode 100644 index 0000000000..da191ade36 --- /dev/null +++ b/components/rust/core/src/out.rs @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2006-2025, 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)*))); + }); +} diff --git a/components/rust/core/src/panic.rs b/components/rust/core/src/panic.rs new file mode 100644 index 0000000000..371f86fdb6 --- /dev/null +++ b/components/rust/core/src/panic.rs @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2006-2025, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Panic implementation + */ +use crate::print; + +pub fn panic_on_atomic_context(s: &str) { + use crate::api::is_irq_context; + if is_irq_context() { + panic!("In irq context {}", s); + } +} + +#[panic_handler] +#[inline(never)] +fn panic(info: &core::panic::PanicInfo) -> ! { + print!("{:}", info); + __rust_panic() +} + +#[linkage = "weak"] +#[unsafe(no_mangle)] +fn __rust_panic() -> ! { + /* Default weak panic handler: loops forever to halt execution. */ + print!("Entered weak panic handler: system will halt."); + loop {} +} diff --git a/components/rust/core/src/param.rs b/components/rust/core/src/param.rs new file mode 100644 index 0000000000..3582a6430c --- /dev/null +++ b/components/rust/core/src/param.rs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2006-2025, 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 = <Vec<ParamItem> as IntoIterator>::IntoIter;
\ No newline at end of file diff --git a/components/rust/core/src/prelude/mod.rs b/components/rust/core/src/prelude/mod.rs new file mode 100644 index 0000000000..494df6b82b --- /dev/null +++ b/components/rust/core/src/prelude/mod.rs @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2006-2025, 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/components/rust/core/src/prelude/no_std.rs b/components/rust/core/src/prelude/no_std.rs new file mode 100644 index 0000000000..6f689cefd9 --- /dev/null +++ b/components/rust/core/src/prelude/no_std.rs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2025, 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/components/rust/core/src/queue.rs b/components/rust/core/src/queue.rs new file mode 100644 index 0000000000..82ae43cc54 --- /dev/null +++ b/components/rust/core/src/queue.rs @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2006-2025, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-10-10 foxglove RT-Thread Queue implementation + */ + +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<T> Send for Queue<T> where T: Send {} +unsafe impl<T> Sync for Queue<T> where T: Send {} + +#[derive(Debug)] +pub struct Queue<T> { + queue: APIRawQueue, + item_type: PhantomData<UnsafeCell<T>>, +} + +impl<T> Queue<T> { + pub fn new(max_size: usize) -> Result<Queue<T>, RTTError> { + queue_create("Unnamed", max_size as _, size_of::<T>() as _) + .ok_or(RTTError::OutOfMemory) + .map(|m| Queue { + queue: m, + item_type: PhantomData, + }) + } + + pub fn new_with_name(name: &str, max_size: usize) -> Result<Queue<T>, RTTError> { + queue_create(name, max_size as _, size_of::<T>() 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::<T>() as _, + max_wait, + ); + return if !is_eok(ret) { + unsafe { Err((RTTError::QueueSendTimeout, inner.assume_init())) } + } else { + Ok(()) + }; + } + + pub fn try_recv(&self) -> Result<T, RTTError> { + self._receive(0) + } + + pub fn recv(&self, max_wait: i32) -> Result<T, RTTError> { + panic_on_atomic_context("recv"); + self._receive(max_wait) + } + + pub fn recv_wait_forever(&self) -> Result<T, RTTError> { + panic_on_atomic_context("recv_wait_forever"); + self._receive(RT_WAITING_FOREVER as _) + } + + fn _receive(&self, max_wait: i32) -> Result<T, RTTError> { + let mut inner = MaybeUninit::<T>::uninit(); + let ret = queue_receive_wait( + self.queue, + inner.as_mut_ptr() as *mut c_void, + size_of::<T>() as _, + max_wait, + ); + return if is_eok(ret) { + Ok(unsafe { inner.assume_init() }) + } else { + Err(RTTError::QueueReceiveTimeout) + }; + } +} + +impl<T> Drop for Queue<T> { + fn drop(&mut self) { + queue_delete(self.queue); + } +}
\ No newline at end of file diff --git a/components/rust/core/src/sem.rs b/components/rust/core/src/sem.rs new file mode 100644 index 0000000000..502bf8560c --- /dev/null +++ b/components/rust/core/src/sem.rs @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2006-2025, 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<usize>>, + sem: APIRawSem +} + +impl Semaphore { + pub fn new() -> Result<Self, RTTError> { + Self::new_with_name("unknown") + } + + pub fn new_with_name(name: &str) -> Result<Self, RTTError> { + 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/components/rust/core/src/thread.rs b/components/rust/core/src/thread.rs new file mode 100644 index 0000000000..35e03f3c3a --- /dev/null +++ b/components/rust/core/src/thread.rs @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2006-2025, 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<dyn FnOnce()>, + ) -> Result<Self, RTTError> { + 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<dyn FnOnce()>); + 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<F>( + name: String, + stack_size: u32, + priority: u8, + ticks: u32, + func: F, + ) -> RTResult<Thread> + 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<F>(&self, func: F) -> RTResult<Thread> + 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/components/rust/core/src/time.rs b/components/rust/core/src/time.rs new file mode 100644 index 0000000000..60dcf47d1d --- /dev/null +++ b/components/rust/core/src/time.rs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2006-2025, 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 |
