diff options
| author | Tropical <42101043+Tropix126@users.noreply.github.com> | 2025-09-24 12:10:15 -0500 |
|---|---|---|
| committer | Tropical <42101043+Tropix126@users.noreply.github.com> | 2025-09-24 12:10:15 -0500 |
| commit | b2634e31c435aeff149c5660f9329a0578ad1e72 (patch) | |
| tree | b94e9505c4100a618cc00cda79b721c29f8ec4a1 /library/std | |
| parent | dd7fda570040e8a736f7d8bc28ddd1b444aabc82 (diff) | |
| download | rust-b2634e31c435aeff149c5660f9329a0578ad1e72.tar.gz rust-b2634e31c435aeff149c5660f9329a0578ad1e72.zip | |
std: add support for armv7a-vex-v5 target
Co-authored-by: Lewis McClelland <lewis@lewismcclelland.me>
Diffstat (limited to 'library/std')
| -rw-r--r-- | library/std/Cargo.toml | 7 | ||||
| -rw-r--r-- | library/std/build.rs | 1 | ||||
| -rw-r--r-- | library/std/src/env.rs | 2 | ||||
| -rw-r--r-- | library/std/src/sys/alloc/mod.rs | 3 | ||||
| -rw-r--r-- | library/std/src/sys/alloc/vexos.rs | 96 | ||||
| -rw-r--r-- | library/std/src/sys/env_consts.rs | 11 | ||||
| -rw-r--r-- | library/std/src/sys/fs/mod.rs | 4 | ||||
| -rw-r--r-- | library/std/src/sys/fs/vexos.rs | 615 | ||||
| -rw-r--r-- | library/std/src/sys/pal/mod.rs | 4 | ||||
| -rw-r--r-- | library/std/src/sys/pal/vexos/mod.rs | 80 | ||||
| -rw-r--r-- | library/std/src/sys/pal/vexos/time.rs | 28 | ||||
| -rw-r--r-- | library/std/src/sys/random/mod.rs | 2 | ||||
| -rw-r--r-- | library/std/src/sys/stdio/mod.rs | 4 | ||||
| -rw-r--r-- | library/std/src/sys/stdio/vexos.rs | 100 | ||||
| -rw-r--r-- | library/std/src/sys/thread/mod.rs | 7 | ||||
| -rw-r--r-- | library/std/src/sys/thread/vexos.rs | 17 | ||||
| -rw-r--r-- | library/std/src/sys/thread_local/mod.rs | 2 |
17 files changed, 982 insertions, 1 deletions
diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index d28a7f0b460..b4d349d66de 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -62,7 +62,7 @@ path = "../windows_targets" rand = { version = "0.9.0", default-features = false, features = ["alloc"] } rand_xorshift = "0.4.0" -[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] +[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] dlmalloc = { version = "0.2.10", features = ['rustc-dep-of-std'] } [target.x86_64-fortanix-unknown-sgx.dependencies] @@ -89,6 +89,11 @@ wasip2 = { version = '0.14.4', features = [ r-efi = { version = "5.2.0", features = ['rustc-dep-of-std'] } r-efi-alloc = { version = "2.0.0", features = ['rustc-dep-of-std'] } +[target.'cfg(target_os = "vexos")'.dependencies] +vex-sdk = { version = "0.27.0", features = [ + 'rustc-dep-of-std', +], default-features = false } + [features] backtrace = [ 'addr2line/rustc-dep-of-std', diff --git a/library/std/build.rs b/library/std/build.rs index ef695601a44..8a5a785060c 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -52,6 +52,7 @@ fn main() { || target_os == "rtems" || target_os == "nuttx" || target_os == "cygwin" + || target_os == "vexos" // See src/bootstrap/src/core/build_steps/synthetic_targets.rs || env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok() diff --git a/library/std/src/env.rs b/library/std/src/env.rs index e457cd61c75..6d716bd8544 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -1098,6 +1098,7 @@ pub mod consts { /// * `"redox"` /// * `"solaris"` /// * `"solid_asp3` + /// * `"vexos"` /// * `"vita"` /// * `"vxworks"` /// * `"xous"` @@ -1148,6 +1149,7 @@ pub mod consts { /// /// <details><summary>Full list of possible values</summary> /// + /// * `"bin"` /// * `"exe"` /// * `"efi"` /// * `"js"` diff --git a/library/std/src/sys/alloc/mod.rs b/library/std/src/sys/alloc/mod.rs index 6d4b09494a3..2045b2fecc6 100644 --- a/library/std/src/sys/alloc/mod.rs +++ b/library/std/src/sys/alloc/mod.rs @@ -92,6 +92,9 @@ cfg_select! { target_os = "uefi" => { mod uefi; } + target_os = "vexos" => { + mod vexos; + } target_family = "wasm" => { mod wasm; } diff --git a/library/std/src/sys/alloc/vexos.rs b/library/std/src/sys/alloc/vexos.rs new file mode 100644 index 00000000000..c1fb6896a89 --- /dev/null +++ b/library/std/src/sys/alloc/vexos.rs @@ -0,0 +1,96 @@ +// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint +#![allow(static_mut_refs)] + +use crate::alloc::{GlobalAlloc, Layout, System}; +use crate::ptr; +use crate::sync::atomic::{AtomicBool, Ordering}; + +// Symbols for heap section boundaries defined in the target's linkerscript +unsafe extern "C" { + static mut __heap_start: u8; + static mut __heap_end: u8; +} + +static mut DLMALLOC: dlmalloc::Dlmalloc<Vexos> = dlmalloc::Dlmalloc::new_with_allocator(Vexos); + +struct Vexos; + +unsafe impl dlmalloc::Allocator for Vexos { + /// Allocs system resources + fn alloc(&self, _size: usize) -> (*mut u8, usize, u32) { + static INIT: AtomicBool = AtomicBool::new(false); + + if !INIT.swap(true, Ordering::Relaxed) { + // This target has no growable heap, as user memory has a fixed + // size/location and VEXos does not manage allocation for us. + unsafe { + ( + (&raw mut __heap_start).cast::<u8>(), + (&raw const __heap_end).offset_from_unsigned(&raw const __heap_start), + 0, + ) + } + } else { + (ptr::null_mut(), 0, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + return false; + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + false + } + + fn page_size(&self) -> usize { + 0x1000 + } +} + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. + // Calling malloc() is safe because preconditions on this function match the trait method preconditions. + unsafe { DLMALLOC.malloc(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. + // Calling calloc() is safe because preconditions on this function match the trait method preconditions. + unsafe { DLMALLOC.calloc(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. + // Calling free() is safe because preconditions on this function match the trait method preconditions. + unsafe { DLMALLOC.free(ptr, layout.size(), layout.align()) } + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + // SAFETY: DLMALLOC access is guaranteed to be safe because we are a single-threaded target, which + // guarantees unique and non-reentrant access to the allocator. As such, no allocator lock is used. + // Calling realloc() is safe because preconditions on this function match the trait method preconditions. + unsafe { DLMALLOC.realloc(ptr, layout.size(), layout.align(), new_size) } + } +} diff --git a/library/std/src/sys/env_consts.rs b/library/std/src/sys/env_consts.rs index 711ba0a5f8a..573f540483b 100644 --- a/library/std/src/sys/env_consts.rs +++ b/library/std/src/sys/env_consts.rs @@ -323,6 +323,17 @@ pub mod os { pub const EXE_EXTENSION: &str = "efi"; } +#[cfg(target_os = "vexos")] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "vexos"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ".bin"; + pub const EXE_EXTENSION: &str = "bin"; +} + #[cfg(target_os = "visionos")] pub mod os { pub const FAMILY: &str = "unix"; diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0276bf6e64c..64f5a6b36d3 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -35,6 +35,10 @@ cfg_select! { mod uefi; use uefi as imp; } + target_os = "vexos" => { + mod vexos; + use vexos as imp; + } target_os = "wasi" => { mod wasi; use wasi as imp; diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs new file mode 100644 index 00000000000..f642e7cb074 --- /dev/null +++ b/library/std/src/sys/fs/vexos.rs @@ -0,0 +1,615 @@ +use crate::ffi::{OsString, c_char}; +use crate::fmt; +use crate::fs::TryLockError; +use crate::hash::Hash; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::path::{Path, PathBuf}; +use crate::sys::common::small_c_string::run_path_with_cstr; +use crate::sys::time::SystemTime; +use crate::sys::{unsupported, unsupported_err}; + +#[expect(dead_code)] +#[path = "unsupported.rs"] +mod unsupported_fs; +pub use unsupported_fs::{ + DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, rmdir, symlink, + unlink, +}; + +/// VEXos file descriptor. +/// +/// This stores an opaque pointer to a [FatFs file object structure] managed by VEXos +/// representing an open file on disk. +/// +/// [FatFs file object structure]: https://github.com/Xilinx/embeddedsw/blob/master/lib/sw_services/xilffs/src/include/ff.h?rgh-link-date=2025-09-23T20%3A03%3A43Z#L215 +/// +/// # Safety +/// +/// Since this platform uses a pointer to to an internal filesystem structure with a lifetime +/// associated with it (rather than a UNIX-style file descriptor table), care must be taken to +/// ensure that the pointer held by `FileDesc` is valid for as long as it exists. +#[derive(Debug)] +struct FileDesc(*mut vex_sdk::FIL); + +// SAFETY: VEXos's FDs can be used on a thread other than the one they were created on. +unsafe impl Send for FileDesc {} +// SAFETY: We assume an environment without threads (i.e. no RTOS). +// (If there were threads, it is possible that a mutex would be required.) +unsafe impl Sync for FileDesc {} + +pub struct File { + fd: FileDesc, +} + +#[derive(Clone)] +pub enum FileAttr { + Dir, + File { size: u64 }, +} + +pub struct ReadDir(!); + +pub struct DirEntry { + path: PathBuf, +} + +#[derive(Clone, Debug)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions {} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct FileType { + is_dir: bool, +} + +impl FileAttr { + pub fn size(&self) -> u64 { + match self { + Self::File { size } => *size, + Self::Dir => 0, + } + } + + pub fn perm(&self) -> FilePermissions { + FilePermissions {} + } + + pub fn file_type(&self) -> FileType { + FileType { is_dir: matches!(self, FileAttr::Dir) } + } + + pub fn modified(&self) -> io::Result<SystemTime> { + unsupported() + } + + pub fn accessed(&self) -> io::Result<SystemTime> { + unsupported() + } + + pub fn created(&self) -> io::Result<SystemTime> { + unsupported() + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + false + } + + pub fn set_readonly(&mut self, _readonly: bool) { + panic!("Perimissions do not exist") + } +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.is_dir + } + + pub fn is_file(&self) -> bool { + !self.is_dir + } + + pub fn is_symlink(&self) -> bool { + // No symlinks in VEXos - entries are either files or directories. + false + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + +impl Iterator for ReadDir { + type Item = io::Result<DirEntry>; + + fn next(&mut self) -> Option<io::Result<DirEntry>> { + self.0 + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + pub fn file_name(&self) -> OsString { + self.path.file_name().unwrap_or_default().into() + } + + pub fn metadata(&self) -> io::Result<FileAttr> { + stat(&self.path) + } + + pub fn file_type(&self) -> io::Result<FileType> { + Ok(self.metadata()?.file_type()) + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + } + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + pub fn write(&mut self, write: bool) { + self.write = write; + } + pub fn append(&mut self, append: bool) { + self.append = append; + } + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + pub fn create(&mut self, create: bool) { + self.create = create; + } + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } +} + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { + run_path_with_cstr(path, &|path| { + // Enforce the invariants of `create_new`/`create`. + // + // Since VEXos doesn't have anything akin to POSIX's `oflags`, we need to enforce + // the requirements that `create_new` can't have an existing file and `!create` + // doesn't create a file ourselves. + if !opts.read && (opts.write || opts.append) && (opts.create_new || !opts.create) { + let status = unsafe { vex_sdk::vexFileStatus(path.as_ptr()) }; + + if opts.create_new && status != 0 { + return Err(io::const_error!(io::ErrorKind::AlreadyExists, "file exists",)); + } else if !opts.create && status == 0 { + return Err(io::const_error!( + io::ErrorKind::NotFound, + "no such file or directory", + )); + } + } + + let file = match opts { + // read + write - unsupported + OpenOptions { read: true, write: true, .. } => { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "opening files with read and write access is unsupported on this target", + )); + } + + // read + OpenOptions { + read: true, + write: false, + append: _, + truncate: false, + create: false, + create_new: false, + } => unsafe { vex_sdk::vexFileOpen(path.as_ptr(), c"".as_ptr()) }, + + // append + OpenOptions { + read: false, + write: _, + append: true, + truncate: false, + create: _, + create_new: _, + } => unsafe { vex_sdk::vexFileOpenWrite(path.as_ptr()) }, + + // write + OpenOptions { + read: false, + write: true, + append: false, + truncate, + create: _, + create_new: _, + } => unsafe { + if *truncate { + vex_sdk::vexFileOpenCreate(path.as_ptr()) + } else { + // Open in append, but jump to the start of the file. + let fd = vex_sdk::vexFileOpenWrite(path.as_ptr()); + vex_sdk::vexFileSeek(fd, 0, 0); + fd + } + }, + + _ => { + return Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid argument")); + } + }; + + if file.is_null() { + Err(io::const_error!(io::ErrorKind::NotFound, "could not open file")) + } else { + Ok(Self { fd: FileDesc(file) }) + } + }) + } + + pub fn file_attr(&self) -> io::Result<FileAttr> { + // `vexFileSize` returns -1 upon error, so u64::try_from will fail on error. + if let Ok(size) = u64::try_from(unsafe { + // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime. + vex_sdk::vexFileSize(self.fd.0) + }) { + Ok(FileAttr::File { size }) + } else { + Err(io::const_error!(io::ErrorKind::InvalidData, "failed to get file size")) + } + } + + pub fn fsync(&self) -> io::Result<()> { + self.flush() + } + + pub fn datasync(&self) -> io::Result<()> { + self.flush() + } + + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) + } + + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + + pub fn truncate(&self, _size: u64) -> io::Result<()> { + unsupported() + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + let len = buf.len() as u32; + let buf_ptr = buf.as_mut_ptr(); + let read = unsafe { + // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime. + vex_sdk::vexFileRead(buf_ptr.cast::<c_char>(), 1, len, self.fd.0) + }; + + if read < 0 { + Err(io::const_error!(io::ErrorKind::Other, "could not read from file")) + } else { + Ok(read as usize) + } + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + crate::io::default_read_vectored(|b| self.read(b), bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|b| self.read(b), cursor) + } + + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + let len = buf.len() as u32; + let buf_ptr = buf.as_ptr(); + let written = unsafe { + // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime. + vex_sdk::vexFileWrite(buf_ptr.cast_mut().cast::<c_char>(), 1, len, self.fd.0) + }; + + if written < 0 { + Err(io::const_error!(io::ErrorKind::Other, "could not write to file")) + } else { + Ok(written as usize) + } + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn flush(&self) -> io::Result<()> { + unsafe { + // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime. + vex_sdk::vexFileSync(self.fd.0); + } + Ok(()) + } + + pub fn tell(&self) -> io::Result<u64> { + // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime. + let position = unsafe { vex_sdk::vexFileTell(self.fd.0) }; + + position.try_into().map_err(|_| { + io::const_error!(io::ErrorKind::InvalidData, "failed to get current location in file") + }) + } + + pub fn size(&self) -> Option<io::Result<u64>> { + None + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { + const SEEK_SET: i32 = 0; + const SEEK_CUR: i32 = 1; + const SEEK_END: i32 = 2; + + fn try_convert_offset<T: TryInto<u32>>(offset: T) -> io::Result<u32> { + offset.try_into().map_err(|_| { + io::const_error!( + io::ErrorKind::InvalidInput, + "cannot seek to an offset too large to fit in a 32 bit integer", + ) + }) + } + + // SAFETY: `self.fd` contains a valid pointer to `FIL` for this struct's lifetime. + match pos { + SeekFrom::Start(offset) => unsafe { + map_fresult(vex_sdk::vexFileSeek(self.fd.0, try_convert_offset(offset)?, SEEK_SET))? + }, + SeekFrom::End(offset) => unsafe { + if offset >= 0 { + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset(offset)?, + SEEK_END, + ))? + } else { + // `vexFileSeek` does not support seeking with negative offset, meaning + // we have to calculate the offset from the end of the file ourselves. + + // Seek to the end of the file to get the end position in the open buffer. + map_fresult(vex_sdk::vexFileSeek(self.fd.0, 0, SEEK_END))?; + let end_position = self.tell()?; + + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + // NOTE: Files internally use a 32-bit representation for stream + // position, so `end_position as i64` should never overflow. + try_convert_offset(end_position as i64 + offset)?, + SEEK_SET, + ))? + } + }, + SeekFrom::Current(offset) => unsafe { + if offset >= 0 { + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset(offset)?, + SEEK_CUR, + ))? + } else { + // `vexFileSeek` does not support seeking with negative offset, meaning + // we have to calculate the offset from the stream position ourselves. + map_fresult(vex_sdk::vexFileSeek( + self.fd.0, + try_convert_offset((self.tell()? as i64) + offset)?, + SEEK_SET, + ))? + } + }, + } + + Ok(self.tell()?) + } + + pub fn duplicate(&self) -> io::Result<File> { + unsupported() + } + + pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { + unsupported() + } + + pub fn set_times(&self, _times: FileTimes) -> io::Result<()> { + unsupported() + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("File").field("fd", &self.fd.0).finish() + } +} +impl Drop for File { + fn drop(&mut self) { + unsafe { vex_sdk::vexFileClose(self.fd.0) }; + } +} + +pub fn readdir(_p: &Path) -> io::Result<ReadDir> { + // While there *is* a userspace function for reading file directories, + // the necessary implementation cannot currently be done cleanly, as + // VEXos does not expose directory length to user programs. + // + // This means that we would need to create a large fixed-length buffer + // and hope that the folder's contents didn't exceed that buffer's length, + // which obviously isn't behavior we want to rely on in the standard library. + unsupported() +} + +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + unsupported() +} + +pub fn exists(path: &Path) -> io::Result<bool> { + run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0)) +} + +pub fn stat(p: &Path) -> io::Result<FileAttr> { + // `vexFileStatus` returns 3 if the given path is a directory, 1 if the path is a + // file, or 0 if no such path exists. + const FILE_STATUS_DIR: u32 = 3; + + run_path_with_cstr(p, &|c_path| { + let file_type = unsafe { vex_sdk::vexFileStatus(c_path.as_ptr()) }; + + // We can't get the size if its a directory because we cant open it as a file + if file_type == FILE_STATUS_DIR { + Ok(FileAttr::Dir) + } else { + let mut opts = OpenOptions::new(); + opts.read(true); + let file = File::open(p, &opts)?; + file.file_attr() + } + }) +} + +pub fn lstat(p: &Path) -> io::Result<FileAttr> { + // Symlinks aren't supported in this filesystem + stat(p) +} + +// Cannot use `copy` from `common` here, since `File::set_permissions` is unsupported on this target. +pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { + use crate::fs::File; + + // NOTE: If `from` is a directory, this call should fail due to vexFileOpen* returning null. + let mut reader = File::open(from)?; + let mut writer = File::create(to)?; + + io::copy(&mut reader, &mut writer) +} + +fn map_fresult(fresult: vex_sdk::FRESULT) -> io::Result<()> { + // VEX uses a derivative of FatFs (Xilinx's xilffs library) for filesystem operations. + match fresult { + vex_sdk::FRESULT::FR_OK => Ok(()), + vex_sdk::FRESULT::FR_DISK_ERR => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "internal function reported an unrecoverable hard error", + )), + vex_sdk::FRESULT::FR_INT_ERR => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "internal error in filesystem runtime", + )), + vex_sdk::FRESULT::FR_NOT_READY => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "the storage device could not be prepared to work", + )), + vex_sdk::FRESULT::FR_NO_FILE => Err(io::const_error!( + io::ErrorKind::NotFound, + "could not find the file in the directory" + )), + vex_sdk::FRESULT::FR_NO_PATH => Err(io::const_error!( + io::ErrorKind::NotFound, + "a directory in the path name could not be found", + )), + vex_sdk::FRESULT::FR_INVALID_NAME => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "the given string is invalid as a path name", + )), + vex_sdk::FRESULT::FR_DENIED => Err(io::const_error!( + io::ErrorKind::PermissionDenied, + "the required access for this operation was denied", + )), + vex_sdk::FRESULT::FR_EXIST => Err(io::const_error!( + io::ErrorKind::AlreadyExists, + "an object with the same name already exists in the directory", + )), + vex_sdk::FRESULT::FR_INVALID_OBJECT => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "invalid or null file/directory object", + )), + vex_sdk::FRESULT::FR_WRITE_PROTECTED => Err(io::const_error!( + io::ErrorKind::PermissionDenied, + "a write operation was performed on write-protected media", + )), + vex_sdk::FRESULT::FR_INVALID_DRIVE => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "an invalid drive number was specified in the path name", + )), + vex_sdk::FRESULT::FR_NOT_ENABLED => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "work area for the logical drive has not been registered", + )), + vex_sdk::FRESULT::FR_NO_FILESYSTEM => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "valid FAT volume could not be found on the drive", + )), + vex_sdk::FRESULT::FR_MKFS_ABORTED => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "failed to create filesystem volume" + )), + vex_sdk::FRESULT::FR_TIMEOUT => Err(io::const_error!( + io::ErrorKind::TimedOut, + "the function was canceled due to a timeout of thread-safe control", + )), + vex_sdk::FRESULT::FR_LOCKED => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "the operation to the object was rejected by file sharing control", + )), + vex_sdk::FRESULT::FR_NOT_ENOUGH_CORE => { + Err(io::const_error!(io::ErrorKind::OutOfMemory, "not enough memory for the operation")) + } + vex_sdk::FRESULT::FR_TOO_MANY_OPEN_FILES => Err(io::const_error!( + io::ErrorKind::Uncategorized, + "maximum number of open files has been reached", + )), + vex_sdk::FRESULT::FR_INVALID_PARAMETER => { + Err(io::const_error!(io::ErrorKind::InvalidInput, "a given parameter was invalid")) + } + _ => unreachable!(), // C-style enum + } +} diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index 513121c6d30..dd5e83ee570 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -45,6 +45,10 @@ cfg_select! { mod trusty; pub use self::trusty::*; } + target_os = "vexos" => { + mod vexos; + pub use self::vexos::*; + } all(target_os = "wasi", target_env = "p2") => { mod wasip2; pub use self::wasip2::*; diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs new file mode 100644 index 00000000000..61a34b0f68a --- /dev/null +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -0,0 +1,80 @@ +#[path = "../unsupported/os.rs"] +pub mod os; +#[path = "../unsupported/pipe.rs"] +pub mod pipe; +pub mod time; + +#[expect(dead_code)] +#[path = "../unsupported/common.rs"] +mod unsupported_common; + +pub use unsupported_common::{ + decode_error_kind, init, is_interrupted, unsupported, unsupported_err, +}; + +use crate::arch::global_asm; +use crate::ptr; +use crate::sys::stdio; +use crate::time::{Duration, Instant}; + +global_asm!( + r#" + .section .boot, "ax" + .global _boot + + _boot: + ldr sp, =__stack_top @ Set up the user stack. + b _start @ Jump to the Rust entrypoint. + "# +); + +#[cfg(not(test))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _start() -> ! { + unsafe extern "C" { + static mut __bss_start: u8; + static mut __bss_end: u8; + + fn main() -> i32; + } + + // Clear the .bss (uninitialized statics) section by filling it with zeroes. + // This is required, since the compiler assumes it will be zeroed on first access. + ptr::write_bytes( + &raw mut __bss_start, + 0, + (&raw mut __bss_end).offset_from_unsigned(&raw mut __bss_start), + ); + + main(); + + cleanup(); + abort_internal() +} + +// SAFETY: must be called only once during runtime cleanup. +// NOTE: this is not guaranteed to run, for example when the program aborts. +pub unsafe fn cleanup() { + let exit_time = Instant::now(); + const FLUSH_TIMEOUT: Duration = Duration::from_millis(15); + + // Force the serial buffer to flush + while exit_time.elapsed() < FLUSH_TIMEOUT { + vex_sdk::vexTasksRun(); + + // If the buffer has been fully flushed, exit the loop + if vex_sdk::vexSerialWriteFree(stdio::STDIO_CHANNEL) == (stdio::STDOUT_BUF_SIZE as i32) { + break; + } + } +} + +pub fn abort_internal() -> ! { + unsafe { + vex_sdk::vexSystemExitRequest(); + + loop { + vex_sdk::vexTasksRun(); + } + } +} diff --git a/library/std/src/sys/pal/vexos/time.rs b/library/std/src/sys/pal/vexos/time.rs new file mode 100644 index 00000000000..f95d96cd27a --- /dev/null +++ b/library/std/src/sys/pal/vexos/time.rs @@ -0,0 +1,28 @@ +use crate::time::Duration; + +#[expect(dead_code)] +#[path = "../unsupported/time.rs"] +mod unsupported_time; +pub use unsupported_time::{SystemTime, UNIX_EPOCH}; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +impl Instant { + pub fn now() -> Instant { + let micros = unsafe { vex_sdk::vexSystemHighResTimeGet() }; + Self(Duration::from_micros(micros)) + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> { + Some(Instant(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> { + Some(Instant(self.0.checked_sub(*other)?)) + } +} diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index 1e0eec07b50..3c5a4c82a9f 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -101,6 +101,7 @@ cfg_select! { any( all(target_family = "wasm", target_os = "unknown"), target_os = "xous", + target_os = "vexos", ) => { // FIXME: finally remove std support for wasm32-unknown-unknown // FIXME: add random data generation to xous @@ -116,6 +117,7 @@ cfg_select! { all(target_family = "wasm", target_os = "unknown"), all(target_os = "wasi", target_env = "p2"), target_os = "xous", + target_os = "vexos", )))] pub fn hashmap_random_keys() -> (u64, u64) { let mut buf = [0; 16]; diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs index 7436e4d9de4..404ac877926 100644 --- a/library/std/src/sys/stdio/mod.rs +++ b/library/std/src/sys/stdio/mod.rs @@ -29,6 +29,10 @@ cfg_select! { mod uefi; pub use uefi::*; } + target_os = "vexos" => { + mod vexos; + pub use vexos::*; + } all(target_os = "wasi", target_env = "p1") => { mod wasip1; pub use wasip1::*; diff --git a/library/std/src/sys/stdio/vexos.rs b/library/std/src/sys/stdio/vexos.rs new file mode 100644 index 00000000000..1f2251c6421 --- /dev/null +++ b/library/std/src/sys/stdio/vexos.rs @@ -0,0 +1,100 @@ +use crate::io; + +pub struct Stdin; +pub struct Stdout; +pub type Stderr = Stdout; + +pub const STDIO_CHANNEL: u32 = 1; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> { + let mut count = 0; + + for out_byte in buf.iter_mut() { + let byte = unsafe { vex_sdk::vexSerialReadChar(STDIO_CHANNEL) }; + if byte < 0 { + break; + } + + *out_byte = byte as u8; + count += 1; + } + + Ok(count) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let mut written = 0; + + // HACK: VEXos holds an internal ringbuffer for serial writes that is flushed to USB1 + // roughly every millisecond by `vexTasksRun`. For writes larger than 2048 bytes, we + // must block until that buffer is flushed to USB1 before writing the rest of `buf`. + // + // This is fairly nonstandard for a `write` implementation, but it avoids a guaranteed + // recursive panic when using macros such as `print!` to write large amounts of data + // (buf.len() > 2048) to stdout at once. + for chunk in buf.chunks(STDOUT_BUF_SIZE) { + if unsafe { vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize } < chunk.len() { + self.flush().unwrap(); + } + + let count: usize = unsafe { + vex_sdk::vexSerialWriteBuffer(STDIO_CHANNEL, chunk.as_ptr(), chunk.len() as u32) + } + .try_into() + .map_err(|_| { + io::const_error!(io::ErrorKind::Uncategorized, "internal write error occurred") + })?; + + written += count; + + // This is a sanity check to ensure that we don't end up with non-contiguous + // buffer writes. e.g. a chunk gets only partially written, but we continue + // attempting to write the remaining chunks. + // + // In practice, this should never really occur since the previous flush ensures + // enough space in FIFO to write the entire chunk to vexSerialWriteBuffer. + if count != chunk.len() { + break; + } + } + + Ok(written) + } + + fn flush(&mut self) -> io::Result<()> { + // This may block for up to a millisecond. + unsafe { + while (vex_sdk::vexSerialWriteFree(STDIO_CHANNEL) as usize) != STDOUT_BUF_SIZE { + vex_sdk::vexTasksRun(); + } + } + + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 4096; +pub const STDOUT_BUF_SIZE: usize = 2048; + +pub fn is_ebadf(_err: &io::Error) -> bool { + false +} + +pub fn panic_output() -> Option<impl io::Write> { + Some(Stdout::new()) +} diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs index 6bb7fc1a20e..3bd83dd760a 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -81,6 +81,13 @@ cfg_select! { ))] pub use unsupported::set_name; } + target_os = "vexos" => { + mod vexos; + pub use vexos::{sleep, yield_now}; + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, DEFAULT_MIN_STACK_SIZE}; + } all(target_os = "wasi", target_env = "p1") => { mod wasip1; pub use wasip1::{DEFAULT_MIN_STACK_SIZE, sleep, yield_now}; diff --git a/library/std/src/sys/thread/vexos.rs b/library/std/src/sys/thread/vexos.rs new file mode 100644 index 00000000000..d917dde4d0b --- /dev/null +++ b/library/std/src/sys/thread/vexos.rs @@ -0,0 +1,17 @@ +use crate::time::{Duration, Instant}; + +pub fn yield_now() { + unsafe { + vex_sdk::vexTasksRun(); + } +} + +pub fn sleep(dur: Duration) { + let start = Instant::now(); + + while start.elapsed() < dur { + unsafe { + vex_sdk::vexTasksRun(); + } + } +} diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index cff74857c47..d5c795093cf 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -29,6 +29,7 @@ cfg_select! { target_os = "uefi", target_os = "zkvm", target_os = "trusty", + target_os = "vexos", ) => { mod no_threads; pub use no_threads::{EagerStorage, LazyStorage, thread_local_inner}; @@ -98,6 +99,7 @@ pub(crate) mod guard { target_os = "uefi", target_os = "zkvm", target_os = "trusty", + target_os = "vexos", ) => { pub(crate) fn enable() { // FIXME: Right now there is no concept of "thread exit" on |
