diff --git a/library/std/src/sys/sync/condvar/mod.rs b/library/std/src/sys/sync/condvar/mod.rs index 615781ea9b7dc..350af390a207e 100644 --- a/library/std/src/sys/sync/condvar/mod.rs +++ b/library/std/src/sys/sync/condvar/mod.rs @@ -11,6 +11,7 @@ cfg_select! { all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", all(target_os = "wasi", target_env = "p3"), + target_os = "vexos", ) => { mod futex; pub use futex::Condvar; diff --git a/library/std/src/sys/sync/futex/mod.rs b/library/std/src/sys/sync/futex/mod.rs index 0edb46cc10f86..131bc0949c97b 100644 --- a/library/std/src/sys/sync/futex/mod.rs +++ b/library/std/src/sys/sync/futex/mod.rs @@ -35,5 +35,9 @@ cfg_select! { target_os = "motor" => { pub use moto_rt::futex::*; } + target_os = "vexos" => { + mod vexos; + pub use vexos::*; + } _ => {} } diff --git a/library/std/src/sys/sync/futex/vexos.rs b/library/std/src/sys/sync/futex/vexos.rs new file mode 100644 index 0000000000000..bb224fe92476f --- /dev/null +++ b/library/std/src/sys/sync/futex/vexos.rs @@ -0,0 +1,55 @@ +use core::sync::atomic::Ordering; + +use crate::sync::atomic::Atomic; +use crate::time::{Duration, Instant}; + +/// An atomic for use as a futex that is at least 32-bits but may be larger +pub type Futex = Atomic; +/// Must be the underlying type of Futex +pub type Primitive = u32; + +/// An atomic for use as a futex that is at least 8-bits but may be larger. +pub type SmallFutex = Atomic; +/// Must be the underlying type of SmallFutex +pub type SmallPrimitive = u32; + +/// Wait for a futex_wake operation to wake us. +/// +/// Returns directly if the futex doesn't hold the expected value. +/// +/// Returns false on timeout, and true in all other cases. +pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { + if let Some(timeout) = timeout { + let begin = Instant::now(); + + while futex.load(Ordering::Acquire) == expected { + if begin.elapsed() >= timeout { + return false; + } + + // Wait for an ISR or Simple Task to wake. + crate::thread::yield_now(); + } + } else { + while futex.load(Ordering::Acquire) == expected { + crate::thread::yield_now(); + } + } + + true +} + +/// Wakes up one thread that's blocked on `futex_wait` on this futex. +/// +/// Returns true if this actually woke up such a thread, +/// or false if no thread was waiting on this futex. +pub fn futex_wake(_futex: &Atomic) -> bool { + // This matches the behavior of FreeBSD/DragonFlyBSD which also always return false here. + false +} + +/// Wakes up all threads that are waiting on `futex_wait` on this futex. +pub fn futex_wake_all(_futex: &Atomic) { + // The futex_wait will wake itself up whenever the futex is modified, so this can + // stay a no-op. +} diff --git a/library/std/src/sys/sync/mutex/mod.rs b/library/std/src/sys/sync/mutex/mod.rs index 895ab9c895697..363e9da4e5902 100644 --- a/library/std/src/sys/sync/mutex/mod.rs +++ b/library/std/src/sys/sync/mutex/mod.rs @@ -10,6 +10,7 @@ cfg_select! { all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", all(target_os = "wasi", target_env = "p3"), + target_os = "vexos", ) => { mod futex; pub use futex::Mutex; diff --git a/library/std/src/sys/sync/once/mod.rs b/library/std/src/sys/sync/once/mod.rs index eee7edf8575fc..783640ec2fe30 100644 --- a/library/std/src/sys/sync/once/mod.rs +++ b/library/std/src/sys/sync/once/mod.rs @@ -20,6 +20,7 @@ cfg_select! { target_os = "fuchsia", target_os = "hermit", all(target_os = "wasi", target_env = "p3"), + target_os = "vexos", ) => { mod futex; pub use futex::{Once, OnceState}; diff --git a/library/std/src/sys/sync/rwlock/mod.rs b/library/std/src/sys/sync/rwlock/mod.rs index 9991f290e46d1..d886d24db8cac 100644 --- a/library/std/src/sys/sync/rwlock/mod.rs +++ b/library/std/src/sys/sync/rwlock/mod.rs @@ -11,6 +11,7 @@ cfg_select! { target_os = "hermit", target_os = "motor", all(target_os = "wasi", target_env = "p3"), + target_os = "vexos", ) => { mod futex; pub use futex::RwLock; diff --git a/library/std/src/sys/sync/thread_parking/mod.rs b/library/std/src/sys/sync/thread_parking/mod.rs index f1385ef7bdde6..57845eede299d 100644 --- a/library/std/src/sys/sync/thread_parking/mod.rs +++ b/library/std/src/sys/sync/thread_parking/mod.rs @@ -11,6 +11,7 @@ cfg_select! { target_os = "motor", target_os = "hermit", all(target_os = "wasi", target_env = "p3"), + target_os = "vexos", ) => { mod futex; pub use futex::Parker; diff --git a/library/std/src/sys/thread_local/no_threads.rs b/library/std/src/sys/thread_local/no_threads.rs index 9f4e7710dffb7..27e963366f50f 100644 --- a/library/std/src/sys/thread_local/no_threads.rs +++ b/library/std/src/sys/thread_local/no_threads.rs @@ -5,7 +5,13 @@ use crate::cell::{Cell, UnsafeCell}; use crate::mem::MaybeUninit; use crate::ptr; -#[cfg(target_has_threads)] +#[cfg(all( + target_has_threads, + // VEXos is "target_has_threads" because it allows user interrupt handlers which preempt the + // main thread, but they are forbidden from accessing TLS. Since there is only one context + // that's allowed to access thread locals, it's sound to use the no_threads implementation. + not(target_os = "vexos"), +))] compile_error!("Using no_threads implementation on a target with threads"); #[doc(hidden)] diff --git a/src/doc/rustc/src/platform-support/thumbv7a-vex-v5.md b/src/doc/rustc/src/platform-support/thumbv7a-vex-v5.md index d11601cfad18b..290d02d744968 100644 --- a/src/doc/rustc/src/platform-support/thumbv7a-vex-v5.md +++ b/src/doc/rustc/src/platform-support/thumbv7a-vex-v5.md @@ -23,31 +23,6 @@ This target is cross-compiled. Dynamic linking is unsupported. `#![no_std]` crates can be built using `build-std` to build `core` and `panic_abort` and optionally `alloc`. Unwinding panics are not yet supported on this target. -`std` has only partial support due to platform limitations. Notably: - -- `std::process` and `std::net` are unimplemented. `std::thread` only supports sleeping and yielding, as this is a single-threaded environment. -- `std::time` has full support for `Instant`, but no support for `SystemTime`. -- `std::io` has full support for `stdin`/`stdout`/`stderr`. `stdout` and `stderr` both write to USB channel 1 on this platform and are not differentiated. -- `std::fs` has limited support for reading or writing to files. The following features are unsupported: - - All directory operations (including `mkdir` and `readdir`), although reading directories is possible through [third-party crates](https://docs.rs/vex-sdk/latest/vex_sdk/file/fn.vexFileDirectoryGet.html) - - Deleting files and directories - - File metadata other than file size and type (that is, file vs. directory) - - Opening files with an uncommon combination of open options, such as read + write at the same time. - The supported modes for opening files are in read-only mode, append mode, or write mode (with or without truncation). -- A global allocator implemented on top of `dlmalloc` is provided. -- Modules that do not need to interact with the OS beyond allocation, such as `std::collections`, `std::hash`, `std::future`, `std::sync`, etc., are fully supported. -- Random number generation and hashing is insecure, as there is no reliable source of entropy on this platform. - -When compiling for this target, the "C" calling convention maps to AAPCS with VFP registers (hard float ABI) and the "system" calling convention maps to AAPCS without VFP registers (softfp ABI). - -This target generates binaries in the ELF format that may be uploaded to the brain with external tools. - -### Platform SDKs - -To use most platform-specific APIs, users must configure a supporting runtime SDK for `libstd` to link against. Official *VEXcode* SDKs from VEX can be downloaded and linked via the [`vex-sdk-vexcode`](https://crates.io/crates/vex-sdk-vexcode) crate, but they have a restrictive redistribution policy that might not be suitable for all projects. The suggested SDK for open-source projects is the community-supported [`vex-sdk-jumptable`](https://crates.io/crates/vex-sdk-jumptable) crate. SDK implementations are generally thin wrappers over system calls, so projects should not expect to see significant differences in behavior depending on which SDK they use. - -Libraries may access symbols from the active VEX SDK without depending on a specific implementation by using the [`vex-sdk`](https://crates.io/crates/vex-sdk) crate. - ## Building the target You can build Rust with support for this target by adding it to the `target` list in `bootstrap.toml`, and then running `./x build --target thumbv7a-vex-v5 compiler`. @@ -100,6 +75,71 @@ fn main() { } ``` +## Environment + +The `main` function is entered on a Zynq 7000's CPU1 in the *System* processor mode in *Secure* state. Programs or runtimes should periodically call `std::thread::yield_now` to flush buffers and fetch the latest peripheral state. + +Developers writing programs for this target should use a high-level runtime such as [vexide](https://vexide.dev) or a lower-level system access crate such as [`vex-sdk`](https://crates.io/crates/vex-sdk) to access peripherals such as motors and sensors. + +When compiling for this target, the "C" calling convention maps to AAPCS with VFP registers (hard float ABI) and the "system" calling convention maps to AAPCS without VFP registers (softfp ABI). + +This target generates binaries in the ELF format that may be uploaded to the brain with external tools. + +See Also: [VEX V5 Environment](https://internals.vexide.dev/technical/environment). + +### Platform SDKs + +To use most platform-specific APIs, users must configure a supporting runtime SDK for `libstd` to link against. Official *VEXcode* SDKs from VEX can be downloaded and linked via the [`vex-sdk-vexcode`](https://crates.io/crates/vex-sdk-vexcode) crate, but they have a restrictive redistribution policy that might not be suitable for all projects. The suggested SDK for open-source projects is the community-supported [`vex-sdk-jumptable`](https://crates.io/crates/vex-sdk-jumptable) crate. SDK implementations are generally thin wrappers over system calls, so projects should not expect to see significant differences in behavior depending on which SDK they use. + +Libraries may access symbols from the active VEX SDK without depending on a specific implementation by using the [`vex-sdk`](https://crates.io/crates/vex-sdk) crate. + +### Standard Library Support + +`std` has only partial support due to platform limitations. Notably: + +- `std::process` and `std::net` are unimplemented. `std::thread` only supports sleeping, yielding, and parking. +- `std::time` has full support for `Instant`, but no support for `SystemTime`. +- `std::io` has full support for `stdin`/`stdout`/`stderr`. `stdout` and `stderr` both write to USB channel 1 on this platform and are not differentiated. +- `std::fs` has limited support for reading or writing to files. The following features are unsupported: + - All directory operations (including `mkdir` and `readdir`), although reading directories is possible through [third-party crates](https://docs.rs/vex-sdk/latest/vex_sdk/file/fn.vexFileDirectoryGet.html) + - Deleting files and directories + - File metadata other than file size and type (that is, file vs. directory) + - Opening files with an uncommon combination of open options, such as read + write at the same time. + The supported modes for opening files are in read-only mode, append mode, or write mode (with or without truncation). +- A global allocator implemented on top of `dlmalloc` is provided. +- `std::sync`'s synchronization primitives are implemented over simple spinlocks on `std::thread::yield_now`. +- Modules that do not need to interact with the OS beyond allocation, such as `std::collections`, `std::hash`, `std::future`, etc., are fully supported. +- Random number generation and hashing is insecure, as there is no reliable source of entropy on this platform. + +### Thread Safety and Synchronization + +*This section only applies to programs that use the `std` crate.* + +When executing on this target, the `std` crate only supports one Rust execution context plus any user-installed Armv7-A exception handlers (configured using the `VBAR` register), provided they properly synchronize access to shared resources. `std` on this target is not aware of any execution inside a task scheduler, so `thread_local` globals will always have the same identity. + +#### VEXos Task Scheduler + +This target begins execution outside of the context of the builtin [VEXos task scheduler](https://internals.vexide.dev/sdk/tasks). Spawning VEXos Full Tasks through VEX's undocumented scheduler API (e.g. using the `vexTaskAdd` function) or using any other stack-switching scheduler (including FreeRTOS) is strictly forbidden on this target and will result in undefined behavior. Rust programs may assume that `vexTasksRun` and `std::thread::yield_now` will never switch stacks. + +Code running inside a VEXos Simple Task context (for example, inside a user touchscreen callback) is forbidden from ticking the task scheduler reentrantly. Thus, it is invalid for task code to: + +- call `vexTasksRun`, `thread::yield_now`, `thread::sleep`, `thread::park`, or derivatives, +- use blocking methods in `std::sync` including `LazyLock::deref` and `OnceLock::get_or_init`, +- or access standard I/O streams via `std::io`. + +#### Exception Handlers + +`std::sync` may be used to synchronize resources shared between ISRs and the main thread. However, ISRs should not use blocking functions such as `Mutex::lock`, `Condvar::wait`, `LazyLock::deref`, or `OnceLock::get_or_init` to prevent deadlocks and stack overflows (from ticking the scheduler on the small IRQ stack). + +ARM exception handlers are forbidden from: + +- accessing the default allocator or `std::fs`, +- accessing `thread_local!` values whatsoever, +- panicking, which indirectly accesses thread locals, +- or accessing standard I/O streams via `std::io` + +since these features are not synchronized. + ## Testing Binaries built for this target can be run in an emulator (such as [vex-v5-qemu](https://github.com/vexide/vex-v5-qemu)), or uploaded to a physical device over a USB serial connection.