feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"ad590bc6b12bb564d91d8f9ecc59fbe4901b50aa97df169a4db95828f6836f20","CHANGELOG.md":"4a1e9ebb88f3eb2449bcea02009ea059c315f2a9d84939c8127ca23db5137deb","Cargo.lock":"1b40b19da9014a386dd305be51a5d8f9c31f7a950ac573cd2de0be4573677075","Cargo.toml":"e15c00a1eb9060c1ed6970d00a6c2a71422162a3b76ee0bbbcbcc42bab76cc7d","Cargo.toml.orig":"5f0e8d26efd9782d5d75413914ee80571b26a3c4281e86301995051ff49e54df","LICENSE-APACHE":"0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"2eebbd31b87c1a743b4b8c41fbcd76bdebddb0948f450b3328c9bee4df183e00","build.rs":"b814195cff362354883f975a59995aae5cfe2e8095138e971ec76869f989001e","src/c_str.rs":"48fcf76d42ba868d86f8d19472998f79f3bd30f9e1c7224c38a5a8e23c6420ea","src/experimental/env.rs":"347ebb1d6e96d60f70cb3e959036fea134f8352d6cee391f2d7579fabbd47ecd","src/experimental/mod.rs":"17b03ca569820e93ebe8e0f6897641c8c6cbb47dbc1adcdc8df31171b57f731d","src/experimental/panic.rs":"9254e68a1f6f8ea66712841c75f91d868434ded4e96f3d24af45aab6448230aa","src/experimental/random.rs":"c559c872121cd82fdd10f9dc97089ead87f783752d262a2deb005151e4eb9ef2","src/experimental/time.rs":"db408ca41d8f1c48e47564469e246a8fe01f31e9187d340d4b727791fa15d6b0","src/fd.rs":"4b5d630160e99943fc1b64113dafd2f4b5f21c1fcfa0ceac04b2f364a0677cae","src/fs.rs":"f97f4da21836176705652f3eb6a88167954acd7677f32e50776349789d333e92","src/gen/build.rs":"e680d44a6191e71eb296c30736775cd1d718109eed08aff4b3b930f58c77fb41","src/io/error.rs":"b3b1d06de70a10c6630a311acba3585c058583046fedc8aaf31ec51d1dfb147f","src/io/impls.rs":"b7643576511dd9b76a0d935dcfd0417c20fd745cc49461790c6b773ea533a024","src/io/mod.rs":"80692fa6d6588a8003ccfcfad0776624f5f36c3941c2e55226a8e80f54a2a16a","src/io/stdio.rs":"83a8a5f23b635a157da83360a49ad7a0a310be4be448bafa15e3d48972b8568b","src/lib.rs":"da44fea9ec09b92386f3567bb394599aa7a28820e00b35346cab190d8968977c","src/macros.rs":"fbf98e83cd8d7a249e0d3da1e6258d2060baba8af97e243cfc81a8f76f2fb5cb","src/panicking.rs":"b9a0ec395676913125d9066696f2b5463d0bc2b91392a3b414f0bec97aa00d7e","src/process.rs":"f7b07f45587326d04d0afbc11032155a96b0c13356fb760bd22fb4f6bc8bf7f5","src/sys/arm_compat/errno.rs":"609676a1f28f7769dd959c37be4bd2bacd5cabae5fee03bce0a9d16ac22c0fca","src/sys/arm_compat/fs.rs":"daa99fb541ac6312fa9b46d61d9840957fdd72498cdb649f9907a02e7dec212d","src/sys/arm_compat/mod.rs":"2676904b1932cc1c827ff4f79506770fd1f2207ed3f576f33d638dcfa31430d3","src/sys/arm_compat/stdio.rs":"1b1e8ce5b715ebd05888dafc97a4607b61ac23515eb29e18b7fa912e53c62c3a","src/sys/arm_compat/syscall/aarch64.rs":"26edf61f918cadf4514c335cfd862607f84826cf01ff6ff904589e01ef71bbe3","src/sys/arm_compat/syscall/arm.rs":"1c80c737de8d8970663a8663574b252f8e145913f8cd8f2defe8104a3c8eaba6","src/sys/arm_compat/syscall/loongarch.rs":"70774cf34229bfb5dedf734c7d08849446b3bfedd3faa427082c1846b0ec5aff","src/sys/arm_compat/syscall/mod.rs":"481a590915203ef1974481d5203ebad741f5a0517c3bfb1fcf7e44fe21ac064d","src/sys/arm_compat/syscall/riscv.rs":"58b3894ccbe434d2ee0443b77da157e62556a9210f5885717ea233f86c94343c","src/sys/arm_compat/syscall/xtensa.rs":"c34798e89d02eff6a3d3f9224cca1740044e7c3eafdefee26204fdaf1677aa8c","src/sys/env.rs":"bfb31e99797fd3a7636bca87bee189c93f0951414dd96b9fbffc2340470c7e26","src/sys/errno.rs":"d52f6886147cf265ac96490601cd847d37bf51c5be18fc3e7a1d8e79adae0e38","src/sys/mips/errno.rs":"eb419ad5ecbad2a849e48aabf74dc8b8dd1fba77f81a0b4e8adb893a33522916","src/sys/mips/fs.rs":"d61a3320f1a97a54fd0177017fb3f1b989f06c55ea03335c4b434967e4375dc1","src/sys/mips/mod.rs":"4c6a4fa46a7b36be47245e3a9dcdc78d46be2be9e20c949ab4a43a0689980c90","src/sys/mips/stdio.rs":"aa3d42925b3e6ee55ad209e75d1e251af93f40a469fcaaed79b5ae828f845cd6","src/sys/mips/syscall/mips.rs":"6cdd0b8e2ce6174e57b55fee4616d900237eb212fbc41dce2369b6bf3ada27b1","src/sys/mips/syscall/mod.rs":"ceea0e8c6ae44366a9367e738c5e0db76dce0ae9402ba69d9bc907481a861153","src/sys/mod.rs":"96d577733716730b6bd4b6f62af46004d41fd10a9863c1e9c6e222ba39afdd49","src/sys/random.rs":"b2ae48264444ceceea7dd025419e78fe4dd5708292401e448ce52334fafa537a","src/sys/reg.rs":"cb52c8cc6ce28df7da0228a7ecb6924dd2bf26af923c9c923fb148fb5ca428ed","src/sys/time.rs":"d59d8695bba4209ef7c7c500c7ade81fd2645b2a7ffa5639c691380ea67e63be","src/utils.rs":"16b4c476507fe72bcd11568b2955382912a6d1d2ecb7a48af0775536859364f9","version.rs":"e8392b444a4be8908d86bd9b134162c369f19bdaa5e9fbe09bb3d3d887549c73"},"package":"f8e4abf97879f4e80db69a9fba7bd64998e9bdad25f58ef045a778e191172fd4"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "075a26b55575aff93d46eb99ff4939a101fdda62"
},
"path_in_vcs": ""
}
+191
View File
@@ -0,0 +1,191 @@
# Changelog
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org).
Releases may yanked if there is a security bug, a soundness bug, or a regression.
<!--
Note: In this file, do not use the hard wrap in the middle of a sentence for compatibility with GitHub comment style markdown rendering.
-->
## [Unreleased]
## [0.1.25] - 2026-03-08
- Improve LoongArch32 support for old nightly. ([07f2579](https://github.com/taiki-e/semihosting/commit/07f2579f8a53f8b22b90491f44425b66025c963e))
## [0.1.24] - 2026-01-15
- Support LoongArch32 and LoongArch64. ([#24](https://github.com/taiki-e/semihosting/pull/24))
- Add `experimental::random::{fill_bytes, fill_uninit_bytes}` under experimental `random` feature. ([#23](https://github.com/taiki-e/semihosting/pull/23), [a5031c3](https://github.com/taiki-e/semihosting/commit/a5031c312e26e50c37858cdc60d4e5ae368e0158), [592c59a](https://github.com/taiki-e/semihosting/commit/592c59a9ef46825437c24499c429cf120265017e))
- Documentation improvements.
## [0.1.23] - 2026-01-12
- `experimental::time` improvements:
- Add `Instant`. ([c412f9f](https://github.com/taiki-e/semihosting/commit/c412f9f48b0c9b08c8c88dade0b46c416ed838b6))
- Re-export `Duration` `TryFromFloatSecsError` from `core::time`. ([c412f9f](https://github.com/taiki-e/semihosting/commit/c412f9f48b0c9b08c8c88dade0b46c416ed838b6))
- Implement `core::error::Error` for `SystemTimeError`. ([c412f9f](https://github.com/taiki-e/semihosting/commit/c412f9f48b0c9b08c8c88dade0b46c416ed838b6))
- `sys` improvements:
- Add `read`, `read_uninit`, `write`, `close`, `arm_compat::sys_read_orig`, and `arm_compat::sys_write_orig`, and deprecate `arm_compat::sys_read` and `arm_compat::sys_write`. See deprecation notes of [`arm_compat::sys_read`](https://docs.rs/semihosting/0.1/semihosting/sys/arm_compat/fn.sys_read.html) and [`arm_compat::sys_write`](https://docs.rs/semihosting/0.1/semihosting/sys/arm_compat/fn.sys_write.html) for details. ([741b9bd](https://github.com/taiki-e/semihosting/commit/741b9bdec178f4a27e1e76e0582a011697625293))
- Add `arm_compat::sys_get_cmdline_uninit`, and deprecate `arm_compat::sys_get_cmdline` in favor of `sys_get_cmdline_uninit`. ([9f46540](https://github.com/taiki-e/semihosting/commit/9f4654092c498805c9ae238b541ceb92b870d57e), [af23159](https://github.com/taiki-e/semihosting/commit/af231592f4cf86b60fee151dc1cf52617a961015))
- Add `arm_compat::syscall::ParamRegR::c_str_len`. ([a30da16](https://github.com/taiki-e/semihosting/commit/a30da167c61593bef8f765dec8b3628eb376782c))
- Add `mips::syscall::ParamRegR::ref_`. ([1a3f6a9](https://github.com/taiki-e/semihosting/commit/1a3f6a9b7a4160c8e3ad5594ebde34a81406ba6f))
- Various optimizations:
- Optimize `process::{exit,abort}`. ([7d32f79](https://github.com/taiki-e/semihosting/commit/7d32f7940f2a18532c30f890198099a4f466981b))
- Optimize `experimental::env::args`. ([9f46540](https://github.com/taiki-e/semihosting/commit/9f4654092c498805c9ae238b541ceb92b870d57e))
- Optimize various semihosting calls on AArch64, Arm, RISC-V, Xtensa. ([f65bb43](https://github.com/taiki-e/semihosting/commit/f65bb435c028d036d4c2e1914eed485205a6aecc))
- Documentation improvements.
## [0.1.22] - 2026-01-10
- Add `sys::mips::mips_plog` (almost equivalent to `sys::arm_compat::sys_write0`). ([7a3cde1](https://github.com/taiki-e/semihosting/commit/7a3cde189ec8760df93cb51afc0996f4edc664ad))
- Improve `io::ErrorKind` detection on MIPS. ([911e563](https://github.com/taiki-e/semihosting/commit/911e563abbb57b0ba6e4ad19260427f8f7a340c7))
- Enable [release immutability](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases).
- Documentation improvements.
## [0.1.21] - 2025-11-30
- Fix build error on pre-1.80 Rust when `alloc` feature is enabled. ([adad165](https://github.com/taiki-e/semihosting/commit/adad165ae45d048c24bc984be80e236717b22815))
## [0.1.20] - 2025-03-19
- Add `io::ErrorKind::InvalidFilename` variant to reflect [upstream stabilization in Rust 1.87](https://github.com/rust-lang/rust/pull/134076). ([b6f25e3](https://github.com/taiki-e/semihosting/commit/b6f25e33031c6aa9c4198cf78157170f4030c7a6))
## [0.1.19] - 2025-02-19
- Change to open stdio handles (`io::Std{in,out,err}`) in "text" mode due to some tools doesn't support opening them in "binary" mode. ([#16](https://github.com/taiki-e/semihosting/issues/16))
This doesn't change the open behavior on files such as `fs::File`.
- Implement `From<alloc::ffi::NulError>` and `From<alloc::collections::TryReserveError>` for `io::Error` when `alloc` feature is enabled to align `std::io::Error`.
- Implement `io::{Read,Write,Seek}` for `&mut impl io::{Read,Write,Seek}` and `alloc::boxed::Box<impl {Read,Write,Seek}>` (when `alloc` feature is enabled) to align `std::io`.
- Implement `io::Read` for `&[u8]` and `alloc::collections::VecDeque<u8>` (when `alloc` feature is enabled) to align `std::io`.
- Implement `io::Write` for `&mut [u8]`, `alloc::vec::Vec<u8>` (when `alloc` feature is enabled), and `alloc::collections::VecDeque<u8>` (when `alloc` feature is enabled) to align `std::io`.
## [0.1.18] - 2025-01-06
- Add `io::ErrorKind::CrossesDevices` variant to reflect [upstream stabilization in Rust 1.85](https://github.com/rust-lang/rust/pull/130209). ([0a09ce5](https://github.com/taiki-e/semihosting/commit/0a09ce540784739f972e76fe719a573a744b98eb))
- Add `io::ErrorKind::QuotaExceeded` variant to reflect [upstream stabilization in Rust 1.85](https://github.com/rust-lang/rust/pull/130254). ([36b069a](https://github.com/taiki-e/semihosting/commit/36b069ab0e56b25bb64b5d7390ece5ce41534c94))
## [0.1.17] - 2024-12-04
- Respect [`RUSTC_BOOTSTRAP=-1` recently added in nightly](https://github.com/rust-lang/rust/pull/132993) in rustc version detection.
- Documentation improvements.
## [0.1.16] - 2024-10-13
- Add more `io::ErrorKind` variants to reflect [upstream stabilization in Rust 1.83](https://github.com/rust-lang/rust/pull/128316). ([9677c7b](https://github.com/taiki-e/semihosting/commit/9677c7be05fee821113e9d36b34e8815532e6f5c))
- Improve compile error messages. ([80f1153](https://github.com/taiki-e/semihosting/commit/80f115310a28e44c2d48b3cc714fc1048aa67386))
## [0.1.15] - 2024-09-15
- Add `process::{ExitCode,Termination}`.
## [0.1.14] - 2024-08-23
- Add `#[must_use]` to `OwnedFd::into_raw_fd`.
## [0.1.13] - 2024-07-22
- Open files in "binary" mode to match `std::fs::File`'s behavior. ([#12](https://github.com/taiki-e/semihosting/issues/12))
## [0.1.12] - 2024-07-09
- Fix [build issue with `esp` toolchain](https://github.com/taiki-e/semihosting/issues/11).
## [0.1.11] - 2024-06-16
**Note:** This release has been yanked due to an issue fixed in 0.1.12.
- Implement `core::error::Error` for `semihosting::io::Error` at Rust 1.81+. ([8701460](https://github.com/taiki-e/semihosting/commit/8701460101e5c9838bb09062435590f834837861))
## [0.1.10] - 2024-05-06
- Make `impl<Fd: AsFd>` impl take `?Sized`. ([2c7b911](https://github.com/taiki-e/semihosting/commit/2c7b9112a42b14f27def67f3b6fd35258c6f2f2b))
## [0.1.9] - 2024-04-21
- Add `trap-hlt` feature to use `HLT` instruction on Arm A+R profile. See the [documentation](https://github.com/taiki-e/semihosting#optional-features-trap-hlt) for details.
## [0.1.8] - 2024-04-21
- Respect `RUSTC_WRAPPER` in rustc version detection.
- Documentation improvements.
## [0.1.7] - 2024-03-21
- Support Xtensa (OpenOCD Semihosting) under the `openocd-semihosting` feature. ([#9](https://github.com/taiki-e/semihosting/pull/9))
## [0.1.6] - 2024-03-02
- Documentation improvements.
## [0.1.5] - 2023-12-27
- Expose raw syscall interface as public API. ([#7](https://github.com/taiki-e/semihosting/pull/7), thanks @t-moe)
## [0.1.4] - 2023-08-25
- Update `unwinding` to 0.2.
## [0.1.3] - 2023-07-27
- Fix build error on MIPS32r6 and MIPS64r6 since [nightly-2023-07-19's target_arch change](https://github.com/rust-lang/rust/pull/112374).
## [0.1.2] - 2023-05-06
- Enable `portable-atomic`'s `require-cas` feature to display helpful error messages to users on targets requiring additional action on the user side to provide atomic CAS.
## [0.1.1] - 2023-04-09
- Improve panic message on stable.
## [0.1.0] - 2023-03-22
Initial release
[Unreleased]: https://github.com/taiki-e/semihosting/compare/v0.1.25...HEAD
[0.1.25]: https://github.com/taiki-e/semihosting/compare/v0.1.24...v0.1.25
[0.1.24]: https://github.com/taiki-e/semihosting/compare/v0.1.23...v0.1.24
[0.1.23]: https://github.com/taiki-e/semihosting/compare/v0.1.22...v0.1.23
[0.1.22]: https://github.com/taiki-e/semihosting/compare/v0.1.21...v0.1.22
[0.1.21]: https://github.com/taiki-e/semihosting/compare/v0.1.20...v0.1.21
[0.1.20]: https://github.com/taiki-e/semihosting/compare/v0.1.19...v0.1.20
[0.1.19]: https://github.com/taiki-e/semihosting/compare/v0.1.18...v0.1.19
[0.1.18]: https://github.com/taiki-e/semihosting/compare/v0.1.17...v0.1.18
[0.1.17]: https://github.com/taiki-e/semihosting/compare/v0.1.16...v0.1.17
[0.1.16]: https://github.com/taiki-e/semihosting/compare/v0.1.15...v0.1.16
[0.1.15]: https://github.com/taiki-e/semihosting/compare/v0.1.14...v0.1.15
[0.1.14]: https://github.com/taiki-e/semihosting/compare/v0.1.13...v0.1.14
[0.1.13]: https://github.com/taiki-e/semihosting/compare/v0.1.12...v0.1.13
[0.1.12]: https://github.com/taiki-e/semihosting/compare/v0.1.11...v0.1.12
[0.1.11]: https://github.com/taiki-e/semihosting/compare/v0.1.10...v0.1.11
[0.1.10]: https://github.com/taiki-e/semihosting/compare/v0.1.9...v0.1.10
[0.1.9]: https://github.com/taiki-e/semihosting/compare/v0.1.8...v0.1.9
[0.1.8]: https://github.com/taiki-e/semihosting/compare/v0.1.7...v0.1.8
[0.1.7]: https://github.com/taiki-e/semihosting/compare/v0.1.6...v0.1.7
[0.1.6]: https://github.com/taiki-e/semihosting/compare/v0.1.5...v0.1.6
[0.1.5]: https://github.com/taiki-e/semihosting/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/taiki-e/semihosting/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/taiki-e/semihosting/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/taiki-e/semihosting/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/taiki-e/semihosting/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/taiki-e/semihosting/releases/tag/v0.1.0
+32
View File
@@ -0,0 +1,32 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "semihosting"
version = "0.1.25"
dependencies = [
"portable-atomic",
"unwinding",
]
[[package]]
name = "unwinding"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60612c845ef41699f39dc8c5391f252942c0a88b7d15da672eff0d14101bbd6d"
dependencies = [
"gimli",
]
+221
View File
@@ -0,0 +1,221 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.64"
name = "semihosting"
version = "0.1.25"
build = "build.rs"
exclude = [
"/.*",
"/tools",
"/target-specs",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = """
Semihosting for AArch64, Arm, RISC-V, LoongArch, MIPS32, MIPS64, and Xtensa.
"""
readme = "README.md"
keywords = ["qemu"]
categories = [
"embedded",
"hardware-support",
"no-std",
"no-std::no-alloc",
]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/taiki-e/semihosting"
[package.metadata.docs.rs]
features = [
"alloc",
"stdio",
"fs",
"args",
"time",
"random",
"panic-unwind",
]
targets = ["aarch64-unknown-linux-gnu"]
[package.metadata.cargo_check_external_types]
allowed_external_types = []
[features]
alloc = []
args = []
backtrace = [
"stdio",
"unwinding",
"unwinding/hide-trace",
]
fs = []
openocd-semihosting = []
panic-handler = []
panic-unwind = [
"alloc",
"panic-handler",
"unwinding",
"unwinding/fde-static",
"unwinding/panic",
"unwinding/personality",
]
portable-atomic = ["dep:portable-atomic"]
random = ["fs"]
stdio = []
time = []
trap-hlt = []
[lib]
name = "semihosting"
path = "src/lib.rs"
doc-scrape-examples = false
[dependencies.portable-atomic]
version = "1.3"
features = ["require-cas"]
optional = true
default-features = false
[dependencies.unwinding]
version = "0.2"
features = ["unwinder"]
optional = true
default-features = false
[dev-dependencies]
[lints.clippy]
all = "warn"
as_ptr_cast_mut = "warn"
as_underscore = "warn"
default_union_representation = "warn"
inline_asm_x86_att_syntax = "warn"
pedantic = "warn"
trailing_empty_array = "warn"
transmute_undefined_repr = "warn"
undocumented_unsafe_blocks = "warn"
unused_trait_names = "warn"
[lints.clippy.bool_assert_comparison]
level = "allow"
priority = 1
[lints.clippy.borrow_as_ptr]
level = "allow"
priority = 1
[lints.clippy.cast_lossless]
level = "allow"
priority = 1
[lints.clippy.collapsible_match]
level = "allow"
priority = 1
[lints.clippy.declare_interior_mutable_const]
level = "allow"
priority = 1
[lints.clippy.doc_markdown]
level = "allow"
priority = 1
[lints.clippy.float_cmp]
level = "allow"
priority = 1
[lints.clippy.incompatible_msrv]
level = "allow"
priority = 1
[lints.clippy.manual_assert]
level = "allow"
priority = 1
[lints.clippy.manual_range_contains]
level = "allow"
priority = 1
[lints.clippy.missing_errors_doc]
level = "allow"
priority = 1
[lints.clippy.module_name_repetitions]
level = "allow"
priority = 1
[lints.clippy.naive_bytecount]
level = "allow"
priority = 1
[lints.clippy.nonminimal_bool]
level = "allow"
priority = 1
[lints.clippy.range_plus_one]
level = "allow"
priority = 1
[lints.clippy.similar_names]
level = "allow"
priority = 1
[lints.clippy.single_match]
level = "allow"
priority = 1
[lints.clippy.single_match_else]
level = "allow"
priority = 1
[lints.clippy.struct_excessive_bools]
level = "allow"
priority = 1
[lints.clippy.struct_field_names]
level = "allow"
priority = 1
[lints.clippy.too_many_arguments]
level = "allow"
priority = 1
[lints.clippy.too_many_lines]
level = "allow"
priority = 1
[lints.clippy.type_complexity]
level = "allow"
priority = 1
[lints.rust]
deprecated_safe = "warn"
improper_ctypes = "warn"
improper_ctypes_definitions = "warn"
non_ascii_idents = "warn"
rust_2018_idioms = "warn"
single_use_lifetimes = "warn"
unnameable_types = "warn"
unreachable_pub = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = [
'cfg(target_arch,values("xtensa"))',
'cfg(target_arch,values("loongarch32"))',
]
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+251
View File
@@ -0,0 +1,251 @@
# semihosting
[![crates.io](https://img.shields.io/crates/v/semihosting?style=flat-square&logo=rust)](https://crates.io/crates/semihosting)
[![docs.rs](https://img.shields.io/badge/docs.rs-semihosting-blue?style=flat-square&logo=docs.rs)](https://docs.rs/semihosting)
[![license](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue?style=flat-square)](#license)
[![msrv](https://img.shields.io/badge/msrv-1.64-blue?style=flat-square&logo=rust)](https://www.rust-lang.org)
[![github actions](https://img.shields.io/github/actions/workflow/status/taiki-e/semihosting/ci.yml?branch=main&style=flat-square&logo=github)](https://github.com/taiki-e/semihosting/actions)
<!-- tidy:sync-markdown-to-rustdoc:start:src/lib.rs -->
Semihosting for AArch64, Arm, RISC-V, LoongArch, MIPS32, MIPS64, and Xtensa.
This library provides access to semihosting, a mechanism for programs running on the real or virtual (e.g., QEMU) target to communicate with I/O facilities on the host system. See the [Arm documentation](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst) for more information on semihosting.
APIs are categorized into the following four types:
- The top-level API (`semihosting::{io,fs,..}`) provides a subset of the standard library's similar APIs.
- `io`: Provide no-std io traits and `std{in,out,err}`. (`std{in,out,err}` requires `stdio` feature, others are unconditionally provided)
- `fs`: Provide methods to manipulate the contents of the host filesystem. (requires `fs` feature)
- `process`: Provide `abort` and `exit`.
- `dbg!`/`print{,ln}!`/`eprint{,ln}!`: macros to output to stdout/stderr. (requires `stdio` feature)
Note that some APIs are not strictly a subset of the standard library.
- API that uses types not available in `core` such as `Path` (technically, the same thing could be implemented, but it makes sense to use `CStr` directly, because when converting a long `Path`/`OsStr` to `CStr`, it needs to either [do an allocation](https://github.com/rust-lang/rust/blob/1.84.0/library/std/src/sys/pal/common/small_c_string.rs#L25-L26) or return an error)
- API that panics on failure in `std` (in no-std it makes sense to return `Result` since `panic=abort` is default)
- Helpers that are useful when using this library.
- `c!`: `CStr` literal macro. (Since Rust 1.77, this macro is soft-deprecated in favor of C string literals (`c"..."`).)
- `semihosting::sys` module provides low-level access to platform-specific semihosting interfaces.
- `semihosting::experimental` module provides experimental APIs. See [optional features](#optional-features) for more.
- `env`: Provide `args`.
- `time`: Provide `Instant` and `SystemTime`.
- `random`: Provide `fill_bytes` and `fill_uninit_bytes`.
- `panic`: Provide `catch_unwind`.
Additionally, this library provides a panic handler for semihosting, `-C panic=unwind` support, backtrace support, via [optional features](#optional-features).
## Platform Support
The following target architectures are supported:
| target_arch | Specification | `semihosting::sys` module | Note |
| ----------- | ------------- | ------------------------- | ---- |
| aarch64 | [Semihosting for AArch32 and AArch64](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst) | `sys::arm_compat` | |
| arm | [Semihosting for AArch32 and AArch64](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst) | `sys::arm_compat` | use `SVC` on A+R profile by default based on Arm's recommendation but it can be changed by [`trap-hlt` feature](#optional-features-trap-hlt). |
| riscv32/riscv64 | [RISC-V Semihosting](https://github.com/riscv-non-isa/riscv-semihosting/blob/1.0/riscv-semihosting.adoc) | `sys::arm_compat` | |
| loongarch32/loongarch64 | | `sys::arm_compat` | |
| xtensa | [OpenOCD Semihosting](https://github.com/espressif/openocd-esp32/blob/HEAD/src/target/espressif/esp_xtensa_semihosting.c) | `sys::arm_compat` | requires [`openocd-semihosting` feature](#optional-features-openocd-semihosting) |
| mips/mips32r6/mips64/mips64r6 | Unified Hosting Interface (MD01069) | `sys::mips` | |
The host must be running an emulator or a debugger attached to the target.
The following targets have been tested on CI. (qemu-system has been tested on Linux, macOS, and Windows hosts, and qemu-user on Linux host.)
| target | qemu-system | qemu-user | note |
| ----------------------------------------- | ---------------- | -------------- | --------- |
| `aarch64-unknown-none{,-softfloat}` | ✓ | ✓ | |
| `aarch64_be-unknown-none{,-softfloat}` | ✓ | ✓ | \[2] |
| `{arm,thumb}v4t-none-eabi` | ✓ | ✓ | |
| `{arm,thumb}v5te-none-eabi` | ✓ | ✓ | |
| `armv6-none-eabi{,hf}` | ✓ | ✓ | |
| `thumbv6-none-eabi` | ✓ | ✓ | |
| `{arm,thumb}v7a-none-eabi{,hf}` | ✓ | ✓ | |
| `{arm,thumb}v7r-none-eabi{,hf}` | ✓ | ✓ | |
| `armebv7r-none-eabi{,hf}` | * | ✓ | \[1] |
| `{arm,thumb}v8r-none-eabihf` | ✓ | ✓ | |
| `armebv8r-none-eabihf` | * | ✓ | \[1] |
| `thumbv6m-none-eabi` | ✓ | ✓ | |
| `thumbv7m-none-eabi` | ✓ | ✓ | |
| `thumbv7em-none-eabi{,hf}` | ✓ | ✓ | |
| `thumbv8m.base-none-eabi` | ✓ | ✓ | |
| `thumbv8m.main-none-eabi{,hf}` | ✓ | ✓ | |
| `riscv32*-unknown-none-elf` | ✓ | ✓ | |
| `riscv64*-unknown-none-elf` | ✓ | ✓ | |
| `loongarch32-unknown-none{,-softfloat}` | ✓ | N/A | \[2] \[3] |
| `loongarch64-unknown-none{,-softfloat}` | ✓ | ✓ | \[2] |
| `mips{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
| `mips{,el}-mti-none-elf` | ✓ | N/A | \[2] \[4] |
| `mips64{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
| `mipsisa32r6{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
| `mipsisa64r6{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
\[1] Due to [qemu-system bug about big-endian Arm](https://github.com/taiki-e/semihosting/issues/18), operations that contains reading/writing of strings such as file open (`stdio`, `fs`, `random`), `args`, and `SYS_SYSTEM` don't work.<br>
\[2] Tested with patched QEMU ([aarch64_be](https://patchew.org/QEMU/20260106-semihosting-cpu-tswap-v1-0-646576c25f56@eonerc.rwth-aachen.de/), [loongarch](https://lore.kernel.org/qemu-devel/20241222-semihosting-v1-1-8a770df60e9c@flygoat.com/), [mips](https://github.com/taiki-e/qemu/commit/c2d1c8dbc5cb878fbcce824180aa812213785124)). QEMU binaries that we are using for test are available on our QEMU fork ([aarch64_be/mips](https://github.com/taiki-e/qemu/releases/tag/patched-10.2.0), [loongarch](https://github.com/taiki-e/qemu/releases/tag/loongarch-semi-10.1.3)).<br>
\[3] Requires Rust 1.91+.<br>
\[4] Requires nightly due to `#![feature(asm_experimental_arch)]`.<br>
## Optional features
All features are disabled by default.
In general use cases, you probably only need the `stdio` feature that enables print-related macros and/or the `panic-handler` feature that exits with a non-zero error code on panic.
```toml
[dependencies]
semihosting = { version = "0.1", features = ["stdio", "panic-handler"] }
```
- **`alloc`**<br>
Use `alloc`.
- **`stdio`**<br>
Enable `semihosting::io::{stdin,stdout,stderr}` and `semihosting::{print*,eprint*,dbg}`.
- **`fs`**<br>
Enable `semihosting::fs`.
- **`panic-handler`**<br>
Provide panic handler based on `semihosting::process::exit`.
If the `stdio` feature is also enabled, this attempt to output panic message and
location to stderr.
- <a name="optional-features-trap-hlt"></a>**`trap-hlt`**<br>
Arm-specific: Use HLT instruction (except for M-profile architecture).
[Arm documentation](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#the-semihosting-interface) says:
> The `HLT` encodings are new in version 2.0 of the semihosting specification.
> Where possible, have semihosting callers continue to use the previously existing
> trap instructions to ensure compatibility with legacy semihosting implementations.
> These trap instructions are `HLT` for A64, `SVC` on A+R profile A32 or T32, and
> `BKPT` on M profile. However, it is necessary to change from SVC to HLT instructions
> to support AArch32 semihosting properly in a mixed AArch32/AArch64 system.
>
> ARM encourages semihosting callers to implement support for trapping using `HLT`
> on A32 and T32 as a configurable option. ARM strongly discourages semihosting
> callers from mixing the `HLT` and `SVC` mechanisms within the same executable.
Based on the Arm's recommendation, this is implemented as an optional feature.
Note:
- Enabling this feature on architectures other than Arm or on Arm M-profile architecture will result in a compile error.
- <a name="optional-features-openocd-semihosting"></a>**`openocd-semihosting`**<br>
Xtensa-specific: Use OpenOCD Semihosting.
Xtensa has two semihosting interfaces:
- Tensilica ISS SIMCALL used in Cadence tools and [QEMU](https://www.qemu.org/docs/master/about/emulation.html#supported-targets).
- Arm-semihosting-compatible semihosting interface used in [OpenOCD](https://github.com/espressif/openocd-esp32/blob/HEAD/src/target/espressif/esp_xtensa_semihosting.c) and [probe-rs](https://github.com/probe-rs/probe-rs/pull/2303). (This crate calls it "OpenOCD Semihosting", which is the same as the option name in [newlib-esp32](https://github.com/espressif/newlib-esp32/blob/esp-4.3.0_20240530/libgloss/xtensa/syscalls.c#L21).)
When this feature is enabled, this crate uses the latter interface.
This crate does not currently support SIMCALL-based semihosting, but users need to explicitly enable the feature to avoid accidentally selecting a different one than one actually want to use.
Note:
- Enabling this feature on architectures other than Xtensa will result in a compile error.
- **`portable-atomic`**<br>
Use [portable-atomic]'s atomic types.
portable-atomic provides atomic CAS on targets where the standard library does not provide atomic CAS.
To use the `panic-unwind` feature on such targets (e.g., RISC-V without A-extension), you need to enable this feature.
See [its documentation](https://github.com/taiki-e/portable-atomic#optional-features-critical-section) for details.
- **`args`**<br>
Enable `semihosting::experimental::env::args`.
Note:
- This feature is experimental (tracking issue: [#1](https://github.com/taiki-e/semihosting/issues/1))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- **`time`**<br>
Enable `semihosting::experimental::time`.
Note:
- This feature is experimental (tracking issue: [#2](https://github.com/taiki-e/semihosting/issues/2))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- **`random`**<br>
Enable `semihosting::experimental::random`.
Note:
- This feature is experimental (tracking issue: [#22](https://github.com/taiki-e/semihosting/issues/22))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- This implicitly enables the `fs` features.
- This is optimized on targets atomic CAS. You may want to use `portable-atomic` feature together if your target doesn't support atomic CAS (e.g., RISC-V without A-extension).
- **`panic-unwind`**<br>
Provide `-C panic=unwind` support for panic handler and enable
`semihosting::experimental::panic::catch_unwind`.
This currently uses [unwinding] crate to support unwinding.
See its documentation for supported platforms and requirements.
Note:
- This feature is experimental (tracking issue: [#3](https://github.com/taiki-e/semihosting/issues/3))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- This currently requires nightly compiler.
- This implicitly enables the `alloc` and `panic-handler` features.
- This uses atomic CAS. You need to use `portable-atomic` feature together if your target doesn't support atomic CAS (e.g., RISC-V without A-extension).
- When enabling this feature, you may need to rebuild the standard library with
`-C panic=unwind` for `catch_unwind` to work properly. The recommended way to
rebuild the standard library is passing `-Z build-std="core,alloc"` option to cargo.
- **`backtrace`**<br>
Provide backtrace support for panic handler.
This currently uses [unwinding] crate to support backtrace.
See its documentation for supported platforms and requirements.
Note:
- This feature is experimental (tracking issue: [#3](https://github.com/taiki-e/semihosting/issues/3))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- This implicitly enables the `stdio` feature.
- When enabling this, it is recommended to also enable the `panic-unwind` feature. Otherwise, a decent backtrace will not be displayed at this time. (Using [`-C force-unwind-tables`](https://doc.rust-lang.org/rustc/codegen-options/index.html#force-unwind-tables) may work, but has not been tested yet.)
- Currently, the backtrace generated is not human-readable.
```text
panicked at 'a', src/main.rs:86:13
stack backtrace:
0x84dc0
0x8ed80
0x8332c
0x83654
0x80644
0x803cc
0x809dc
0x800bc
```
You can use `addr2line` to resolve the addresses and [rustfilt] to demangle Rust symbols.
For example, run the following command (please replace `<path/to/binary>` with your binary path), then paste the addresses:
```sh
llvm-addr2line -fipe <path/to/binary> | rustfilt
```
[portable-atomic]: https://github.com/taiki-e/portable-atomic
[rustfilt]: https://github.com/luser/rustfilt
[unwinding]: https://github.com/nbdd0121/unwinding
<!-- tidy:sync-markdown-to-rustdoc:end -->
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or
[MIT license](LICENSE-MIT) at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
+153
View File
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// The rustc-cfg emitted by the build script are *not* public API.
#[path = "src/gen/build.rs"]
mod generated;
#[path = "version.rs"]
mod version;
use std::env;
use self::version::{Version, rustc_version};
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/gen/build.rs");
let target_arch = &*env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not set");
let version = match rustc_version() {
Some(version) => version,
None => {
if env::var_os("SEMIHOSTING_DENY_WARNINGS").is_some() {
panic!("unable to determine rustc version")
}
println!(
"cargo:warning={}: unable to determine rustc version; assuming latest stable rustc (1.{})",
env!("CARGO_PKG_NAME"),
Version::LATEST.minor
);
Version::LATEST
}
};
if version.minor >= 80 {
// Custom cfgs set by build script. Not public API.
// grep -F 'cargo:rustc-cfg=' build.rs | grep -Ev '^ *//' | sed -E 's/^.*cargo:rustc-cfg=//; s/(=\\)?".*$//' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/'
println!(
"cargo:rustc-check-cfg=cfg(semihosting_no_asm,semihosting_no_duration_checked_float,semihosting_no_error_in_core,semihosting_no_strict_provenance,semihosting_target_feature)"
);
// TODO: handle multi-line target_feature_fallback
// grep -F 'target_feature_fallback("' build.rs | grep -Ev '^ *//' | sed -E 's/^.*target_feature_fallback\(//; s/",.*$/"/' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/'
println!(
r#"cargo:rustc-check-cfg=cfg(semihosting_target_feature,values("mclass","thumb-mode"))"#
);
}
// Note that cfgs are `no_`*, not `has_*`. This allows treating as the latest
// stable rustc is used when the build script doesn't run. This is useful
// for non-cargo build systems that don't run the build script.
// duration_checked_float stabilized in Rust 1.66 (nightly-2022-10-25): https://github.com/rust-lang/rust/pull/102271
if !version.probe(66, 2022, 10, 24) {
println!("cargo:rustc-cfg=semihosting_no_duration_checked_float");
}
// error_in_core stabilized in Rust 1.81 (nightly-2024-06-09): https://github.com/rust-lang/rust/pull/125951
if !version.probe(81, 2024, 6, 8) {
println!("cargo:rustc-cfg=semihosting_no_error_in_core");
}
// strict_provenance/exposed_provenance APIs stabilized in Rust 1.84 (nightly-2024-10-22): https://github.com/rust-lang/rust/pull/130350
if !version.probe(84, 2024, 10, 21) {
println!("cargo:rustc-cfg=semihosting_no_strict_provenance");
}
match target_arch {
"arm" => {
let target = &*env::var("TARGET").expect("TARGET not set");
// https://github.com/rust-lang/rust/pull/123745 (includes https://github.com/rust-lang/cargo/pull/13560) merged in Rust 1.79 (nightly-2024-04-11).
if !version.probe(79, 2024, 4, 10) {
// HACK: If --target is specified, rustflags is not applied to the build
// script itself, so the build script will not be recompiled when rustflags
// is changed. That in itself is not a problem, but the old Cargo does
// not rerun the build script as well, which can be problematic.
// https://github.com/rust-lang/cargo/issues/13003
// This problem has been fixed in 1.79 so only older versions need a workaround.
println!("cargo:rerun-if-env-changed=CARGO_ENCODED_RUSTFLAGS");
println!("cargo:rerun-if-env-changed=RUSTFLAGS");
println!("cargo:rerun-if-env-changed=CARGO_BUILD_RUSTFLAGS");
let mut target_upper = target.replace(['-', '.'], "_");
target_upper.make_ascii_uppercase();
println!("cargo:rerun-if-env-changed=CARGO_TARGET_{target_upper}_RUSTFLAGS");
}
if needs_target_feature_fallback(&version) {
// See https://github.com/taiki-e/atomic-maybe-uninit/blob/HEAD/build.rs for details
let mut subarch =
target.strip_prefix("arm").or_else(|| target.strip_prefix("thumb")).unwrap();
subarch = subarch.strip_prefix("eb").unwrap_or(subarch); // ignore endianness
subarch = subarch.split('-').next().unwrap(); // ignore vender/os/env
subarch = subarch.split('.').next().unwrap(); // ignore .base/.main suffix
let mut mclass = false;
match subarch {
"v6m" | "v7em" | "v7m" | "v8m" => mclass = true,
_ => {}
}
target_feature_fallback("mclass", mclass);
// All builtin targets that start with "thumb" enable thumb-mode, and
// some builtin targets that start with "arm" are also enable thumb-mode.
let thumb_mode =
target.starts_with("thumb") || generated::ARM_BUT_THUMB_MODE.contains(&target);
target_feature_fallback("thumb-mode", thumb_mode);
}
}
"loongarch32" => {
// asm! on loongarch32 stabilized in Rust 1.91 (nightly-2025-08-11): https://github.com/rust-lang/rust/pull/144402
if !version.probe(91, 2025, 8, 10) {
println!("cargo:rustc-cfg=semihosting_no_asm");
}
}
_ => {}
}
}
// HACK: Currently, it seems that the only way to handle unstable target
// features on the stable is to parse the `-C target-feature` in RUSTFLAGS.
//
// - #[cfg(target_feature = "unstable_target_feature")] doesn't work on stable.
// - CARGO_CFG_TARGET_FEATURE excludes unstable target features on stable.
//
// As mentioned in the [RFC2045], unstable target features are also passed to LLVM
// (e.g., https://godbolt.org/z/4rr7rMcfG), so this hack works properly on stable.
//
// [RFC2045]: https://rust-lang.github.io/rfcs/2045-target-feature.html#backend-compilation-options
fn needs_target_feature_fallback(version: &Version) -> bool {
if version.nightly {
// In this case, cfg(target_feature = "...") would work, so skip emitting our own fallback target_feature cfg.
false
} else {
true
}
}
fn target_feature_fallback(name: &str, mut has_target_feature: bool) {
if let Some(rustflags) = env::var_os("CARGO_ENCODED_RUSTFLAGS") {
for mut flag in rustflags.to_string_lossy().split('\x1f') {
flag = flag.strip_prefix("-C").unwrap_or(flag);
if let Some(flag) = flag.strip_prefix("target-feature=") {
for s in flag.split(',') {
// TODO: Handles cases where a specific target feature
// implicitly enables another target feature.
match (s.as_bytes().first(), s.as_bytes().get(1..)) {
(Some(b'+'), Some(f)) if f == name.as_bytes() => has_target_feature = true,
(Some(b'-'), Some(f)) if f == name.as_bytes() => has_target_feature = false,
_ => {}
}
}
}
}
}
if has_target_feature {
println!("cargo:rustc-cfg=semihosting_target_feature=\"{name}\"");
}
}
+126
View File
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Provide safe abstraction (c! macro) for creating static C strings without runtime checks.
// (c"..." requires Rust 1.77)
/// [`CStr`] literal macro.
///
/// **Note:** Since Rust 1.77, this macro is soft-deprecated in favor of C string literals (`c"..."`).
///
/// [`Path`] is not available in `core`, so this crate uses [`CStr`] instead in the API where
/// `std` uses [`Path`]. This macro makes it safe and zero-cost to create a [`CStr`] from a literal.
///
/// ```no_run
/// use semihosting::{c, fs};
///
/// fs::write(c!("a.txt"), "abc")?;
/// // concat! in c! is also supported
/// fs::write(c!(concat!("b", ".txt")), "def")?;
/// # Ok::<(), semihosting::io::Error>(())
/// ```
///
/// This macro guarantees the correctness of the input by compile-time validation.
/// Incorrect input will cause compile-time errors.
///
/// ```compile_fail,E0080
/// use semihosting::c;
///
/// let s = c!("ab\0c"); // CStr must not contain any interior nul bytes.
/// ```
///
/// ```text
/// error[E0080]: evaluation of constant value failed
/// --> semihosting/src/c_str.rs:48:9
/// |
/// 48 | assert!(byte != 0, "input contained interior nul");
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'input contained interior nul', semihosting/src/c_str.rs:48:9
/// |
/// note: inside `const_c_str_check`
/// --> semihosting/src/c_str.rs:48:9
/// |
/// 48 | assert!(byte != 0, "input contained interior nul");
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/// note: inside `_`
/// --> src/c_str.rs:18:9
/// |
/// 5 | let s = c!("ab\0c"); // CStr must not contain any interior nul bytes.
/// | ^^^^^^^^^^^
/// ```
///
/// [`CStr`]: core::ffi::CStr
/// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
#[macro_export]
macro_rules! c {
($s:expr) => {{
const BYTES: &[u8] = $crate::__private::concat!($s, "\0").as_bytes();
const _: () = $crate::__private::const_c_str_check(BYTES);
#[allow(unused_unsafe)]
// SAFETY: we've checked `BYTES` is a valid C string
unsafe {
$crate::__private::CStr::from_bytes_with_nul_unchecked(BYTES)
}
}};
}
// Based on https://github.com/rust-lang/rust/blob/1.84.0/library/core/src/ffi/c_str.rs#L417
// - bytes must be nul-terminated.
// - bytes must not contain any interior nul bytes.
#[doc(hidden)]
pub const fn const_c_str_check(bytes: &[u8]) {
// Saturating so that an empty slice panics in the assert with a good
// message, not here due to underflow.
let mut i = bytes.len().saturating_sub(1);
assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");
// Ending null byte exists, skip to the rest.
while i != 0 {
i -= 1;
let byte = bytes[i];
assert!(byte != 0, "input contained interior nul");
}
}
#[allow(
clippy::alloc_instead_of_core,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
clippy::undocumented_unsafe_blocks,
clippy::wildcard_imports
)]
#[cfg(test)]
mod tests {
use core::ffi::CStr;
#[test]
fn test_c_macro() {
#[track_caller]
fn t(s: &CStr, raw: &[u8]) {
assert_eq!(s.to_bytes_with_nul(), raw);
}
t(c!(""), b"\0");
t(c!("a"), b"a\0");
t(c!("abc"), b"abc\0");
t(c!(concat!("abc", "d")), b"abcd\0");
}
#[test]
fn test_is_c_str() {
#[track_caller]
fn t(bytes: &[u8]) {
assert_eq!(
std::panic::catch_unwind(|| super::const_c_str_check(bytes)).is_ok(),
CStr::from_bytes_with_nul(bytes).is_ok()
);
}
t(b"\0");
t(b"a\0");
t(b"abc\0");
t(b"");
t(b"a");
t(b"abc");
t(b"\0a");
t(b"\0a\0");
t(b"ab\0c\0");
t(b"\0\0");
}
}
+38
View File
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Inspection and manipulation of the process's environment.
//!
//! This module is a subset of the [`std::env`] module, with [some differences](https://github.com/taiki-e/semihosting/issues/1).
//!
//! [`std::env`]: https://doc.rust-lang.org/std/env/index.html
#![allow(clippy::undocumented_unsafe_blocks)] // TODO
use core::{fmt, str};
use crate::{io, sys::env as sys};
/// An iterator over the arguments of a process, yielding a `Result<&str>` value for
/// each argument.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct Args<const BUF_SIZE: usize>(sys::ArgsBytes<BUF_SIZE>);
/// Returns the arguments that this program was started with.
pub fn args<const BUF_SIZE: usize>() -> io::Result<Args<BUF_SIZE>> {
sys::args_bytes().map(Args)
}
#[allow(clippy::copy_iterator)] // TODO(args)
impl<'a, const BUF_SIZE: usize> Iterator for &'a Args<BUF_SIZE> {
type Item = Result<&'a str, str::Utf8Error>;
fn next(&mut self) -> Option<Self::Item> {
let arg = sys::next(&self.0)?;
Some(str::from_utf8(arg))
}
}
impl<const BUF_SIZE: usize> fmt::Debug for Args<BUF_SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Args").finish_non_exhaustive()
}
}
+19
View File
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Experimental APIs.
//!
//! Note: All APIs in this module are experimental and outside of the normal semver guarantees and
//! minor or patch versions of semihosting may make breaking changes to them at any time.
#[cfg(feature = "args")]
#[cfg_attr(docsrs, doc(cfg(feature = "args")))]
pub mod env;
#[cfg(feature = "panic-unwind")]
#[cfg_attr(docsrs, doc(cfg(feature = "panic-unwind")))]
pub mod panic;
#[cfg(feature = "random")]
#[cfg_attr(docsrs, doc(cfg(feature = "random")))]
pub mod random;
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
pub mod time;
+24
View File
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Panic support.
//!
//! This module is a subset of the [`std::panic`] module.
//!
//! [`std::panic`]: https://doc.rust-lang.org/std/panic/index.html
use alloc::boxed::Box;
use core::any::Any;
use crate::atomic::{AtomicUsize, Ordering};
pub(crate) static PANICKED: AtomicUsize = AtomicUsize::new(0);
/// Invokes a closure, capturing the cause of an unwinding panic if one occurs.
pub fn catch_unwind<F: FnOnce() -> R, R>(f: F) -> Result<R, Box<dyn Any + Send>> {
let res = unwinding::panic::catch_unwind(f);
if res.is_err() {
let panicked = PANICKED.fetch_sub(1, Ordering::Release);
debug_assert!(panicked != 0);
}
res
}
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Random value generation.
use core::mem::MaybeUninit;
use crate::{io, sys::random as sys};
/// Fills `bytes` with random bytes.
///
/// # Underlying sources
///
/// This is currently implemented by reading the host system's `/dev/urandom`.
///
/// On the host system does not have `/dev/urandom` like Windows, this always returns an error.
///
/// Note that the sources used might change over time.
#[inline]
pub fn fill_bytes(bytes: &mut [u8]) -> io::Result<()> {
let len = bytes.len();
// SAFETY: transmuting initialized `&mut [u8]` to `&mut [MaybeUninit<u8>]` is safe unless uninitialized byte will be written to resulting slice.
let bytes = unsafe {
core::slice::from_raw_parts_mut(bytes.as_mut_ptr().cast::<MaybeUninit<u8>>(), len)
};
fill_uninit_bytes(bytes)?;
Ok(())
}
/// Fills `bytes` with random bytes.
///
/// Unlike [`fill_bytes`], this takes potentially uninitialized bytes.
///
/// See [`fill_bytes`] for details.
#[inline]
pub fn fill_uninit_bytes(bytes: &mut [MaybeUninit<u8>]) -> io::Result<&mut [u8]> {
sys::fill_bytes(bytes)
}
+320
View File
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Temporal quantification.
//!
//! This module is a subset of the [`std::time`] module.
//!
//! [`std::time`]: https://doc.rust-lang.org/std/time/index.html
// Based on https://github.com/rust-lang/rust/blob/1.92.0/library/std/src/time.rs.
#[doc(no_inline)]
pub use core::time::Duration;
#[cfg(not(semihosting_no_duration_checked_float))]
#[doc(no_inline)]
pub use core::time::TryFromFloatSecsError;
use core::{
fmt,
ops::{Add, AddAssign, Sub, SubAssign},
};
use crate::sys::time;
/// A measurement of a monotonically nondecreasing clock.
/// Opaque and useful only with [`Duration`].
///
/// See [`std::time::Instant` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.Instant.html
///
/// # Platform-specific behavior
///
/// An `Instant` is a wrapper around system-specific types and it may behave
/// differently depending on the underlying system.
///
/// The following semihosting calls are currently being used by `now()` to find out
/// the current time:
///
/// | Platform | Semihosting call | Representable precision |
/// | ------------------------------------------------------------- | ---------------- | ------------------------ |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_CLOCK] | 10 millisecond intervals |
/// | MIPS32, MIPS64 | (Unsupported) | - |
///
/// [SYS_CLOCK]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-clock-0x10
///
/// **Disclaimer:** These semihosting calls might change over time.
///
/// > Note: mathematical operations like [`add`] may panic if the underlying
/// > structure cannot represent the new point in time.
///
/// [`add`]: Instant::add
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(time::Instant);
/// A measurement of the system clock, useful for talking to
/// external entities like the file system or other processes.
///
/// See [`std::time::SystemTime` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.SystemTime.html
///
/// # Platform-specific behavior
///
/// The precision of `SystemTime` can depend on the underlying system.
///
/// The following semihosting calls are currently being used by `now()` to find out
/// the current time:
///
/// | Platform | Semihosting call | Representable precision |
/// | ------------------------------------------------------------- | ---------------- | ------------------------ |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_TIME] | second intervals |
/// | MIPS32, MIPS64 | (Unsupported) | - |
///
/// [SYS_TIME]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-time-0x11
///
/// **Disclaimer:** These semihosting calls might change over time.
///
/// > Note: mathematical operations like [`add`] may panic if the underlying
/// > structure cannot represent the new point in time.
///
/// [`add`]: SystemTime::add
/// [`UNIX_EPOCH`]: SystemTime::UNIX_EPOCH
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SystemTime(time::SystemTime);
/// An error returned from the `duration_since` and `elapsed` methods on
/// `SystemTime`, used to learn how far in the opposite direction a system time
///
/// See [`std::time::SystemTimeError` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.SystemTimeError.html
#[derive(Clone, Debug)]
pub struct SystemTimeError(Duration);
impl Instant {
// TODO(time): return result?
/// Returns an instant corresponding to "now".
#[must_use]
pub fn now() -> Self {
Self(time::Instant::now().unwrap())
}
/// Returns the amount of time elapsed from another instant to this one,
/// or zero duration if that instant is later than this one.
///
/// See [`std::time::Instant::duration_since` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.Instant.html#method.duration_since
#[must_use]
pub fn duration_since(&self, earlier: Self) -> Duration {
self.checked_duration_since(earlier).unwrap_or_default()
}
/// Returns the amount of time elapsed from another instant to this one,
/// or None if that instant is later than this one.
///
/// See [`std::time::Instant::checked_duration_since` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.Instant.html#method.checked_duration_since
#[must_use]
pub fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
self.0.checked_sub_instant(&earlier.0)
}
/// Returns the amount of time elapsed from another instant to this one,
/// or zero duration if that instant is later than this one.
///
/// See [`std::time::Instant::saturating_duration_since` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.Instant.html#method.saturating_duration_since
#[must_use]
pub fn saturating_duration_since(&self, earlier: Self) -> Duration {
self.checked_duration_since(earlier).unwrap_or_default()
}
/// Returns the amount of time elapsed since this instant.
///
/// See [`std::time::Instant::elapsed` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.Instant.html#method.elapsed
#[must_use]
pub fn elapsed(&self) -> Duration {
Self::now() - *self
}
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
pub fn checked_add(&self, duration: Duration) -> Option<Self> {
self.0.checked_add_duration(&duration).map(Self)
}
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
self.0.checked_sub_duration(&duration).map(Self)
}
}
impl Add<Duration> for Instant {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure. See [`Instant::checked_add`] for a version without panic.
fn add(self, other: Duration) -> Self {
self.checked_add(other).expect("overflow when adding duration to instant")
}
}
impl AddAssign<Duration> for Instant {
fn add_assign(&mut self, other: Duration) {
*self = *self + other;
}
}
impl Sub<Duration> for Instant {
type Output = Self;
fn sub(self, other: Duration) -> Self::Output {
self.checked_sub(other).expect("overflow when subtracting duration from instant")
}
}
impl SubAssign<Duration> for Instant {
fn sub_assign(&mut self, other: Duration) {
*self = *self - other;
}
}
impl Sub<Instant> for Instant {
type Output = Duration;
/// Returns the amount of time elapsed from another instant to this one,
/// or zero duration if that instant is later than this one.
///
/// See [`std::time::Instant` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.Instant.html#impl-Sub-for-Instant
fn sub(self, other: Self) -> Self::Output {
self.duration_since(other)
}
}
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl SystemTime {
/// An anchor in time which can be used to create new `SystemTime` instances or
/// learn about where in time a `SystemTime` lies.
///
/// See [`std::time::SystemTime::UNIX_EPOCH` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#associatedconstant.UNIX_EPOCH
pub const UNIX_EPOCH: Self = Self(time::UNIX_EPOCH);
// TODO(time): return result?
/// Returns the system time corresponding to "now".
#[must_use]
pub fn now() -> Self {
Self(time::SystemTime::now().unwrap())
}
/// Returns the amount of time elapsed from an earlier point in time.
///
/// See [`std::time::SystemTime::duration_since` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#method.duration_since
pub fn duration_since(&self, earlier: Self) -> Result<Duration, SystemTimeError> {
self.0.sub_time(&earlier.0).map_err(SystemTimeError)
}
/// Returns the difference from this system time to the
/// current clock time.
///
/// See [`std::time::SystemTime::elapsed` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#method.elapsed
pub fn elapsed(&self) -> Result<Duration, SystemTimeError> {
Self::now().duration_since(*self)
}
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
/// `SystemTime` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
pub fn checked_add(&self, duration: Duration) -> Option<Self> {
self.0.checked_add_duration(&duration).map(Self)
}
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
/// `SystemTime` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
self.0.checked_sub_duration(&duration).map(Self)
}
}
impl Add<Duration> for SystemTime {
type Output = Self;
/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure. See [`SystemTime::checked_add`] for a version without panic.
fn add(self, dur: Duration) -> Self::Output {
self.checked_add(dur).expect("overflow when adding duration to instant")
}
}
impl AddAssign<Duration> for SystemTime {
fn add_assign(&mut self, other: Duration) {
*self = *self + other;
}
}
impl Sub<Duration> for SystemTime {
type Output = Self;
fn sub(self, dur: Duration) -> Self::Output {
self.checked_sub(dur).expect("overflow when subtracting duration from instant")
}
}
impl SubAssign<Duration> for SystemTime {
fn sub_assign(&mut self, other: Duration) {
*self = *self - other;
}
}
impl fmt::Debug for SystemTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl SystemTimeError {
/// Returns the positive duration which represents how far forward the
/// second system time was from the first.
///
/// See [`std::time::SystemTimeError::duration` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/time/struct.SystemTimeError.html#method.duration
#[must_use]
pub fn duration(&self) -> Duration {
self.0
}
}
#[cfg(not(semihosting_no_error_in_core))]
impl core::error::Error for SystemTimeError {}
impl fmt::Display for SystemTimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("second time provided was later than self")
}
}
+223
View File
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Owned and borrowed Unix-like file descriptors.
//!
//! This is identical to [`std::os::fd`](https://doc.rust-lang.org/std/os/fd/index.html),
//! but available with no-std.
//!
//! Note that this crate does not provide `{As,Into,From}RawFd` traits. They have been effectively
//! obsoleted by io-safety, and now using `AsFd`, `From<... > for OwnedFd`, or `Into<OwnedFd>`
//! is recommended. To convert to `RawFd`, you need first convert it to `BorrowedFd` or
//! `OwnedFd` the above way and then call `BorrowedFd::as_raw_fd` or `OwnedFd::{as,into}_raw_fd`.
//! This redundancy is intentional, as it serves as a reminder that it is usually not
//! recommended.
#![allow(clippy::undocumented_unsafe_blocks)] // TODO
use core::{ffi::c_int, fmt, marker::PhantomData, mem::ManuallyDrop};
use crate::sys;
/// Raw file descriptors.
pub type RawFd = c_int;
// 16-bit targets has 16-bit c_int.
const _: () = {
#[cfg(not(target_pointer_width = "16"))]
assert!(core::mem::size_of::<RawFd>() == core::mem::size_of::<u32>());
#[cfg(target_pointer_width = "16")]
assert!(core::mem::size_of::<RawFd>() == core::mem::size_of::<u16>());
};
/// A borrowed file descriptor.
///
/// This has a lifetime parameter to tie it to the lifetime of something that
/// owns the file descriptor.
///
/// This uses `repr(transparent)` and has the representation of a host file
/// descriptor, so it can be used in FFI in places where a file descriptor is
/// passed as an argument, it is not captured or consumed, and it never has the
/// value `-1`.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct BorrowedFd<'fd> {
fd: RawFd,
_phantom: PhantomData<&'fd OwnedFd>,
}
/// An owned file descriptor.
///
/// This closes the file descriptor on drop.
///
/// This uses `repr(transparent)` and has the representation of a host file
/// descriptor, so it can be used in FFI in places where a file descriptor is
/// passed as a consumed argument or returned as an owned value, and it never
/// has the value `-1`.
#[repr(transparent)]
pub struct OwnedFd {
fd: RawFd,
}
impl BorrowedFd<'_> {
/// Return a `BorrowedFd` holding the given raw file descriptor.
///
/// # Safety
///
/// The resource pointed to by `fd` must remain open for the duration of
/// the returned `BorrowedFd`, and it must not have the value `-1`.
#[inline]
pub const unsafe fn borrow_raw(fd: RawFd) -> Self {
assert!(fd != -1);
Self { fd, _phantom: PhantomData }
}
/// Extracts the raw file descriptor.
#[allow(clippy::trivially_copy_pass_by_ref)] // align to AsRawFd::as_raw_fd
#[inline]
pub const fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl OwnedFd {
/// Constructs a new instance of `Self` from the given raw file descriptor.
///
/// # Safety
///
/// The resource pointed to by `fd` must be open and suitable for assuming
/// ownership. The resource must not require any cleanup other than `close`.
#[inline]
pub const unsafe fn from_raw_fd(fd: RawFd) -> Self {
assert!(fd != -1);
Self { fd }
}
/// Extracts the raw file descriptor.
#[inline]
pub const fn as_raw_fd(&self) -> RawFd {
self.fd
}
/// Consumes this object, returning the raw underlying file descriptor.
#[inline]
#[must_use = "losing the raw file descriptor may leak resources"]
pub fn into_raw_fd(self) -> RawFd {
let this = ManuallyDrop::new(self);
this.fd
}
}
impl Drop for OwnedFd {
#[inline]
fn drop(&mut self) {
#[cfg(feature = "stdio")]
if !sys::stdio::should_close(self) {
return;
}
// Note that errors are ignored when closing a file descriptor. The
// reason for this is that if an error occurs we don't actually know if
// the file descriptor was closed or not, and if we retried (for
// something like EINTR), we might close another valid file descriptor
// opened after we closed ours.
let _ = unsafe { sys::close(self.as_raw_fd()) };
}
}
impl fmt::Debug for BorrowedFd<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BorrowedFd").field("fd", &self.fd).finish()
}
}
impl fmt::Debug for OwnedFd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedFd").field("fd", &self.fd).finish()
}
}
/// A trait to borrow the file descriptor from an underlying object.
pub trait AsFd {
/// Borrows the file descriptor.
fn as_fd(&self) -> BorrowedFd<'_>;
}
impl<T: ?Sized + AsFd> AsFd for &T {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
T::as_fd(self)
}
}
impl<T: ?Sized + AsFd> AsFd for &mut T {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
T::as_fd(self)
}
}
impl AsFd for BorrowedFd<'_> {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
*self
}
}
impl AsFd for OwnedFd {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
// SAFETY: `OwnedFd` and `BorrowedFd` have the same validity
// invariants, and the `BorrowedFd` is bounded by the lifetime
// of `&self`.
unsafe { BorrowedFd::borrow_raw(self.fd) }
}
}
#[cfg(feature = "alloc")]
impl<T: ?Sized + AsFd> AsFd for alloc::boxed::Box<T> {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
(**self).as_fd()
}
}
#[cfg(feature = "alloc")]
impl<T: ?Sized + AsFd> AsFd for alloc::rc::Rc<T> {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
(**self).as_fd()
}
}
#[cfg(target_has_atomic = "ptr")]
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<T: ?Sized + AsFd> AsFd for alloc::sync::Arc<T> {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
(**self).as_fd()
}
}
#[cfg(any(feature = "stdio", feature = "fs"))]
macro_rules! impl_as_fd {
($($ty:ty),* $(,)?) => {$(
impl crate::fd::AsFd for $ty {
#[inline]
fn as_fd(&self) -> crate::fd::BorrowedFd<'_> {
self.0.as_fd()
}
}
)*};
}
#[cfg(feature = "fs")]
macro_rules! impl_from_fd {
($($ty:ty),* $(,)?) => {$(
impl From<$ty> for OwnedFd {
#[inline]
fn from(this: $ty) -> Self {
this.0
}
}
impl From<OwnedFd> for $ty {
#[inline]
fn from(owned_fd: OwnedFd) -> Self {
Self(owned_fd)
}
}
)*};
}
+277
View File
@@ -0,0 +1,277 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Host filesystem manipulation operations.
//!
//! This module contains basic methods to manipulate the contents of the host filesystem.
//!
//! This module is a subset of the [`std::fs`] module except that [`CStr`] is used for the path
//! instead of [`Path`].
//!
//! [`Path`] is not available in `core`, so this module uses [`CStr`] instead in the API where
//! `std` uses [`Path`]. When creating a path from a string literal, it is recommended to use the
//! C string literals (`c"..."`, available since Rust 1.77) or [c!](c!()) macro.
//!
//! ```no_run
//! use semihosting::{c, fs};
//!
//! // with C string literal
//! fs::write(c"a.txt", "abc")?;
//! // with c! macro
//! fs::write(c!("b.txt"), "123")?;
//! # Ok::<(), semihosting::io::Error>(())
//! ```
//!
//! # Platform-specific behavior
//!
//! The operations provided by this module are mapped to the corresponding operations of the host
//! system. The details of its operation depend on the host system.
//!
//! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html
//! [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
use core::{ffi::CStr, fmt};
use crate::{
fd::{AsFd as _, OwnedFd},
io::{self, Write as _},
sys,
};
/// Write a slice as the entire contents of a file.
///
/// See [`std::fs::write` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/fs/fn.write.html
pub fn write<P: AsRef<CStr>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
fn inner(path: &CStr, contents: &[u8]) -> io::Result<()> {
File::create(path)?.write_all(contents)
}
inner(path.as_ref(), contents.as_ref())
}
/// Removes a file from the host filesystem.
///
/// See [`std::fs::remove_file` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/fs/fn.remove_file.html
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_REMOVE] |
/// | MIPS32, MIPS64 | UHI_unlink |
///
/// [SYS_REMOVE]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-remove-0x0e
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "rm", alias = "unlink", alias = "DeleteFile")]
#[doc(alias = "SYS_REMOVE")] // arm_compat
#[doc(alias = "UHI_unlink")] // mips
pub fn remove_file<P: AsRef<CStr>>(path: P) -> io::Result<()> {
sys::fs::unlink(path.as_ref())
}
/// Rename a file or directory to a new name, replacing the original file if
/// `to` already exists.
///
/// See [`std::fs::rename` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/fs/fn.rename.html
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_RENAME] |
/// | MIPS32, MIPS64 | (Unsupported) |
///
/// [SYS_RENAME]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-rename-0x0f
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "mv", alias = "MoveFile", alias = "MoveFileEx")]
#[doc(alias = "SYS_RENAME")] // arm_compat
pub fn rename<P: AsRef<CStr>, Q: AsRef<CStr>>(from: P, to: Q) -> io::Result<()> {
sys::fs::rename(from.as_ref(), to.as_ref())
}
/// An object providing access to an open file on the host filesystem.
///
/// See [`std::fs::File` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/fs/struct.File.html
pub struct File(OwnedFd);
impl File {
/// Attempts to open a file in read-only mode.
pub fn open<P: AsRef<CStr>>(path: P) -> io::Result<Self> {
OpenOptions::new().read(true).open(path.as_ref())
}
/// Opens a file in write-only mode.
pub fn create<P: AsRef<CStr>>(path: P) -> io::Result<Self> {
OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref())
}
/// Returns a new OpenOptions object.
#[must_use]
pub fn options() -> OpenOptions {
OpenOptions::new()
}
/// Queries metadata about the underlying file.
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_FLEN] |
/// | MIPS32, MIPS64 | UHI_fstat |
///
/// [SYS_FLEN]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-flen-0x0c
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "SYS_FLEN")] // arm_compat
#[doc(alias = "UHI_fstat")] // mips
pub fn metadata(&self) -> io::Result<Metadata> {
sys::fs::metadata(self.as_fd()).map(Metadata)
}
}
impl_as_fd!(File);
impl_from_fd!(File);
impl fmt::Debug for File {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("File").field("fd", &self.as_fd().as_raw_fd()).finish()
}
}
impl io::Read for File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
sys::read(self.as_fd(), buf)
}
}
impl io::Write for File {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
sys::write(self.as_fd(), bytes)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Seek for File {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
sys::fs::seek(self.as_fd(), pos)
}
}
impl io::Read for &File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
sys::read(self.as_fd(), buf)
}
}
impl io::Write for &File {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
sys::write(self.as_fd(), bytes)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Seek for &File {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
sys::fs::seek(self.as_fd(), pos)
}
}
/// Options and flags which can be used to configure how a file is opened.
///
/// See [`std::fs::OpenOptions` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
#[derive(Clone, Debug)]
pub struct OpenOptions {
// generic
pub(crate) read: bool,
pub(crate) write: bool,
pub(crate) append: bool,
pub(crate) truncate: bool,
pub(crate) create: bool,
#[allow(dead_code)]
pub(crate) create_new: bool,
// system-specific
#[allow(dead_code)]
pub(crate) mode: u32,
}
#[allow(missing_docs)] // TODO
impl OpenOptions {
pub fn new() -> Self {
Self {
// generic
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
// system-specific
mode: 0o666,
}
}
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 append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.truncate = truncate;
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
self
}
// pub fn create_new(&mut self, create_new: bool) {
// self.create_new = create_new;
// }
// pub fn custom_flags(&mut self, flags: i32) {
// self.custom_flags = flags;
// }
// pub fn mode(&mut self, mode: u32) {
// self.mode = mode as mode_t;
// }
pub fn open<P: AsRef<CStr>>(&self, path: P) -> io::Result<File> {
sys::fs::open(path.as_ref(), self).map(File)
}
}
/// Metadata information about a file.
pub struct Metadata(sys::fs::Metadata);
impl Metadata {
/// Returns the size of the file, in bytes, this metadata is for.
#[must_use]
pub fn len(&self) -> u64 {
self.0.size()
}
}
impl fmt::Debug for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Metadata").finish_non_exhaustive()
}
}
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// This file is @generated by target_spec.sh.
// It is not intended for manual editing.
#[rustfmt::skip]
pub(crate) static ARM_BUT_THUMB_MODE: &[&str] = &[
"armv7-linux-androideabi",
"armv7-sony-vita-newlibeabihf",
];
+327
View File
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::fmt;
use crate::sys;
/// A specialized [`Result`] type for I/O operations.
///
/// See [`std::io::Result` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/type.Result.html
pub type Result<T> = core::result::Result<T, Error>;
/// The error type for I/O operations.
///
/// See [`std::io::Error` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/struct.Error.html
pub struct Error {
repr: Repr,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.repr, f)
}
}
macro_rules! const_error {
($kind:expr, $message:expr $(,)?) => {
$crate::io::Error::from_static_message({
const MESSAGE_DATA: $crate::io::error::SimpleMessage =
$crate::io::error::SimpleMessage { kind: $kind, message: $message };
&MESSAGE_DATA
})
};
}
#[allow(dead_code)]
impl Error {
pub(crate) const INVALID_UTF8: Self =
const_error!(ErrorKind::InvalidData, "stream did not contain valid UTF-8");
pub(crate) const READ_EXACT_EOF: Self =
const_error!(ErrorKind::UnexpectedEof, "failed to fill whole buffer");
pub(crate) const UNKNOWN_THREAD_COUNT: Self = const_error!(
ErrorKind::NotFound,
"The number of hardware threads is not known for the target platform",
);
pub(crate) const UNSUPPORTED_PLATFORM: Self =
const_error!(ErrorKind::Unsupported, "operation not supported on this platform");
pub(crate) const WRITE_ALL_EOF: Self =
const_error!(ErrorKind::WriteZero, "failed to write whole buffer");
pub(crate) const ZERO_TIMEOUT: Self =
const_error!(ErrorKind::InvalidInput, "cannot set a 0 duration timeout");
#[cfg(feature = "fs")]
pub(crate) const EINVAL: Self = Self { repr: Repr::Os(sys::EINVAL) };
}
#[cfg(feature = "alloc")]
impl From<alloc::ffi::NulError> for Error {
/// Converts a [`alloc::ffi::NulError`] into a [`Error`].
fn from(_: alloc::ffi::NulError) -> Error {
const_error!(ErrorKind::InvalidInput, "data provided contains a nul byte")
}
}
#[cfg(feature = "alloc")]
impl From<alloc::collections::TryReserveError> for Error {
/// Converts `TryReserveError` to an error with [`ErrorKind::OutOfMemory`].
///
/// `TryReserveError` won't be available as the error `source()`,
/// but this may change in the future.
fn from(_: alloc::collections::TryReserveError) -> Error {
// ErrorData::Custom allocates, which isn't great for handling OOM errors.
ErrorKind::OutOfMemory.into()
}
}
enum Repr {
Os(RawOsError),
Simple(ErrorKind),
SimpleMessage(&'static SimpleMessage),
}
/// The type of raw OS error codes returned by [`Error::raw_os_error`].
///
/// See [`std::io::RawOsError` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/nightly/std/io/type.RawOsError.html
pub type RawOsError = i32;
pub(crate) struct SimpleMessage {
pub(crate) kind: ErrorKind,
pub(crate) message: &'static str,
}
/// A list specifying general categories of I/O error.
///
/// See [`std::io::ErrorKind` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
NotFound,
PermissionDenied,
ConnectionRefused,
ConnectionReset,
HostUnreachable,
NetworkUnreachable,
ConnectionAborted,
NotConnected,
AddrInUse,
AddrNotAvailable,
NetworkDown,
BrokenPipe,
AlreadyExists,
WouldBlock,
NotADirectory,
IsADirectory,
DirectoryNotEmpty,
ReadOnlyFilesystem,
#[doc(hidden)]
__FilesystemLoop, // unstable https://github.com/rust-lang/rust/issues/130188
StaleNetworkFileHandle,
InvalidInput,
InvalidData,
TimedOut,
WriteZero,
StorageFull,
NotSeekable,
QuotaExceeded,
FileTooLarge,
ResourceBusy,
ExecutableFileBusy,
Deadlock,
CrossesDevices,
TooManyLinks,
InvalidFilename,
ArgumentListTooLong,
Interrupted,
Unsupported,
UnexpectedEof,
OutOfMemory,
#[doc(hidden)]
__InProgress, // unstable https://github.com/rust-lang/rust/issues/130840
Other,
// Uncategorized, // unstable, private api
}
impl ErrorKind {
pub(crate) fn as_str(self) -> &'static str {
#[allow(clippy::enum_glob_use)]
use ErrorKind::*;
match self {
AddrInUse => "address in use",
AddrNotAvailable => "address not available",
AlreadyExists => "entity already exists",
ArgumentListTooLong => "argument list too long",
BrokenPipe => "broken pipe",
ConnectionAborted => "connection aborted",
ConnectionRefused => "connection refused",
ConnectionReset => "connection reset",
CrossesDevices => "cross-device link or rename",
Deadlock => "deadlock",
DirectoryNotEmpty => "directory not empty",
ExecutableFileBusy => "executable file busy",
__FilesystemLoop => "filesystem loop or indirection limit (e.g. symlink loop)",
FileTooLarge => "file too large",
HostUnreachable => "host unreachable",
__InProgress => "in progress",
Interrupted => "operation interrupted",
InvalidData => "invalid data",
InvalidFilename => "invalid filename",
InvalidInput => "invalid input parameter",
IsADirectory => "is a directory",
NetworkDown => "network down",
NetworkUnreachable => "network unreachable",
NotADirectory => "not a directory",
NotConnected => "not connected",
NotFound => "entity not found",
NotSeekable => "seek on unseekable file",
Other => "other error",
OutOfMemory => "out of memory",
PermissionDenied => "permission denied",
QuotaExceeded => "quota exceeded",
ReadOnlyFilesystem => "read-only filesystem or storage medium",
ResourceBusy => "resource busy",
StaleNetworkFileHandle => "stale network file handle",
StorageFull => "no storage space",
TimedOut => "timed out",
TooManyLinks => "too many links",
// Uncategorized => "uncategorized error",
UnexpectedEof => "unexpected end of file",
Unsupported => "unsupported",
WouldBlock => "operation would block",
WriteZero => "write zero",
}
}
}
impl fmt::Display for ErrorKind {
/// Shows a human-readable description of the `ErrorKind`.
///
/// This is similar to `impl Display for Error`, but doesn't require first converting to Error.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// Intended for use for errors not exposed to the user, where allocating onto
/// the heap (for normal construction via Error::new) is too costly.
impl From<ErrorKind> for Error {
/// Converts an [`ErrorKind`] into an [`Error`].
///
/// This conversion creates a new error with a simple representation of error kind.
#[inline]
fn from(kind: ErrorKind) -> Self {
Self { repr: Repr::Simple(kind) }
}
}
impl Error {
#[inline]
pub(crate) const fn from_static_message(msg: &'static SimpleMessage) -> Error {
Self { repr: Repr::SimpleMessage(msg) }
}
// TODO(io,alloc): provide new,other when alloc feature is enabled?
// TODO(io): last_os_error: Arm semihosting has sys_errno, but MIPS UHI doesn't.
/// Creates a new instance of an `Error` from a particular OS error code.
#[inline]
#[must_use]
pub fn from_raw_os_error(os: RawOsError) -> Self {
Self { repr: Repr::Os(os) }
}
/// Returns the OS error that this error represents (if any).
#[inline]
#[must_use]
pub fn raw_os_error(&self) -> Option<RawOsError> {
match self.repr {
Repr::Os(code) => Some(code),
// Repr::Custom(..) |
Repr::Simple(..) | Repr::SimpleMessage(..) => None,
}
}
/// Returns the corresponding [`ErrorKind`] for this error.
#[inline]
#[must_use]
pub fn kind(&self) -> ErrorKind {
match self.repr {
Repr::Os(code) => sys::decode_error_kind(code),
// Repr::Custom(ref c) => c.kind,
Repr::Simple(kind) => kind,
Repr::SimpleMessage(msg) => msg.kind,
}
}
#[inline]
pub(crate) fn is_interrupted(&self) -> bool {
match self.repr {
Repr::Os(code) => sys::is_interrupted(code),
// Repr::Custom(ref c) => c.kind == ErrorKind::Interrupted,
Repr::Simple(kind) => kind == ErrorKind::Interrupted,
Repr::SimpleMessage(m) => m.kind == ErrorKind::Interrupted,
}
}
}
impl fmt::Debug for Repr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Os(code) => f
.debug_struct("Os")
.field("code", &code)
.field("kind", &sys::decode_error_kind(*code))
// TODO(io)
// .field("message", &sys::os::error_string(code))
.finish(),
// Self::Custom(c) => fmt::Debug::fmt(&c, fmt),
Self::Simple(kind) => f.debug_tuple("Kind").field(&kind).finish(),
Self::SimpleMessage(msg) => f
.debug_struct("Error")
.field("kind", &msg.kind)
.field("message", &msg.message)
.finish(),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.repr {
Repr::Os(code) => {
// TODO(io)
// let detail = sys::os::error_string(code);
// write!(f, "{detail} (os error {code})")
let detail = sys::decode_error_kind(code);
write!(f, "{detail} (os error {code})")
}
// Repr::Custom(ref c) => c.error.fmt(fmt),
Repr::Simple(kind) => f.write_str(kind.as_str()),
Repr::SimpleMessage(msg) => msg.message.fmt(f),
}
}
}
#[cfg(not(semihosting_no_error_in_core))]
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self.repr {
Repr::Os(..) | Repr::Simple(..) | Repr::SimpleMessage(..) => None,
// Repr::Custom(c) => c.error.source(),
}
}
}
+499
View File
@@ -0,0 +1,499 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::{cmp, fmt, mem};
use crate::io::{self, Read, Seek, SeekFrom, Write};
// -----------------------------------------------------------------------------
// Forwarding implementations
impl<R: ?Sized + Read> Read for &mut R {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(**self).read(buf)
}
// #[inline]
// fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
// (**self).read_buf(cursor)
// }
// #[inline]
// fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
// (**self).read_vectored(bufs)
// }
// #[inline]
// fn is_read_vectored(&self) -> bool {
// (**self).is_read_vectored()
// }
// #[inline]
// fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
// (**self).read_to_end(buf)
// }
// #[inline]
// fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
// (**self).read_to_string(buf)
// }
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
(**self).read_exact(buf)
}
// #[inline]
// fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
// (**self).read_buf_exact(cursor)
// }
}
impl<W: ?Sized + Write> Write for &mut W {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(**self).write(buf)
}
// #[inline]
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
// (**self).write_vectored(bufs)
// }
// #[inline]
// fn is_write_vectored(&self) -> bool {
// (**self).is_write_vectored()
// }
#[inline]
fn flush(&mut self) -> io::Result<()> {
(**self).flush()
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
(**self).write_all(buf)
}
// #[inline]
// fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> {
// (**self).write_all_vectored(bufs)
// }
#[inline]
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
(**self).write_fmt(fmt)
}
}
impl<S: ?Sized + Seek> Seek for &mut S {
#[inline]
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
(**self).seek(pos)
}
#[inline]
fn rewind(&mut self) -> io::Result<()> {
(**self).rewind()
}
// #[inline]
// fn stream_len(&mut self) -> io::Result<u64> {
// (**self).stream_len()
// }
// #[inline]
// fn stream_position(&mut self) -> io::Result<u64> {
// (**self).stream_position()
// }
// #[inline]
// fn seek_relative(&mut self, offset: i64) -> io::Result<()> {
// (**self).seek_relative(offset)
// }
}
#[cfg(feature = "alloc")]
impl<R: ?Sized + Read> Read for alloc::boxed::Box<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(**self).read(buf)
}
// #[inline]
// fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
// (**self).read_buf(cursor)
// }
// #[inline]
// fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
// (**self).read_vectored(bufs)
// }
// #[inline]
// fn is_read_vectored(&self) -> bool {
// (**self).is_read_vectored()
// }
// #[inline]
// fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
// (**self).read_to_end(buf)
// }
// #[inline]
// fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
// (**self).read_to_string(buf)
// }
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
(**self).read_exact(buf)
}
// #[inline]
// fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
// (**self).read_buf_exact(cursor)
// }
}
#[cfg(feature = "alloc")]
impl<W: ?Sized + Write> Write for alloc::boxed::Box<W> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(**self).write(buf)
}
// #[inline]
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
// (**self).write_vectored(bufs)
// }
// #[inline]
// fn is_write_vectored(&self) -> bool {
// (**self).is_write_vectored()
// }
#[inline]
fn flush(&mut self) -> io::Result<()> {
(**self).flush()
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
(**self).write_all(buf)
}
// #[inline]
// fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> {
// (**self).write_all_vectored(bufs)
// }
#[inline]
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
(**self).write_fmt(fmt)
}
}
#[cfg(feature = "alloc")]
impl<S: ?Sized + Seek> Seek for alloc::boxed::Box<S> {
#[inline]
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
(**self).seek(pos)
}
#[inline]
fn rewind(&mut self) -> io::Result<()> {
(**self).rewind()
}
// #[inline]
// fn stream_len(&mut self) -> io::Result<u64> {
// (**self).stream_len()
// }
// #[inline]
// fn stream_position(&mut self) -> io::Result<u64> {
// (**self).stream_position()
// }
// #[inline]
// fn seek_relative(&mut self, offset: i64) -> io::Result<()> {
// (**self).seek_relative(offset)
// }
}
// -----------------------------------------------------------------------------
// In-memory buffer implementations
/// Read is implemented for `&[u8]` by copying from the slice.
///
/// Note that reading updates the slice to point to the yet unread part.
/// The slice will be empty when EOF is reached.
impl Read for &[u8] {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let amt = cmp::min(buf.len(), self.len());
let (a, b) = self.split_at(amt);
// First check if the amount of bytes we want to read is small:
// `copy_from_slice` will generally expand to a call to `memcpy`, and
// for a single byte the overhead is significant.
if amt == 1 {
buf[0] = a[0];
} else {
buf[..amt].copy_from_slice(a);
}
*self = b;
Ok(amt)
}
// #[inline]
// fn read_buf(&mut self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
// let amt = cmp::min(cursor.capacity(), self.len());
// let (a, b) = self.split_at(amt);
//
// cursor.append(a);
//
// *self = b;
// Ok(())
// }
// #[inline]
// fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
// let mut n_read = 0;
// for buf in bufs {
// n_read += self.read(buf)?;
// if self.is_empty() {
// break;
// }
// }
//
// Ok(n_read)
// }
// #[inline]
// fn is_read_vectored(&self) -> bool {
// true
// }
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
if buf.len() > self.len() {
// `read_exact` makes no promise about the content of `buf` if it
// fails so don't bother about that.
*self = &self[self.len()..];
return Err(io::Error::READ_EXACT_EOF);
}
let (a, b) = self.split_at(buf.len());
// First check if the amount of bytes we want to read is small:
// `copy_from_slice` will generally expand to a call to `memcpy`, and
// for a single byte the overhead is significant.
if buf.len() == 1 {
buf[0] = a[0];
} else {
buf.copy_from_slice(a);
}
*self = b;
Ok(())
}
// #[inline]
// fn read_buf_exact(&mut self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
// if cursor.capacity() > self.len() {
// // Append everything we can to the cursor.
// cursor.append(*self);
// *self = &self[self.len()..];
// return Err(io::Error::READ_EXACT_EOF);
// }
// let (a, b) = self.split_at(cursor.capacity());
//
// cursor.append(a);
//
// *self = b;
// Ok(())
// }
// #[inline]
// fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
// let len = self.len();
// buf.try_reserve(len)?;
// buf.extend_from_slice(*self);
// *self = &self[len..];
// Ok(len)
// }
// #[inline]
// fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
// let content = str::from_utf8(self).map_err(|_| io::Error::INVALID_UTF8)?;
// let len = self.len();
// buf.try_reserve(len)?;
// buf.push_str(content);
// *self = &self[len..];
// Ok(len)
// }
}
/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting
/// its data.
///
/// Note that writing updates the slice to point to the yet unwritten part.
/// The slice will be empty when it has been completely overwritten.
///
/// If the number of bytes to be written exceeds the size of the slice, write operations will
/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of
/// kind `ErrorKind::WriteZero`.
impl Write for &mut [u8] {
#[inline]
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
let amt = cmp::min(data.len(), self.len());
let (a, b) = mem::take(self).split_at_mut(amt);
a.copy_from_slice(&data[..amt]);
*self = b;
Ok(amt)
}
// #[inline]
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
// let mut n_written = 0;
// for buf in bufs {
// n_written += self.write(buf)?;
// if self.is_empty() {
// break;
// }
// }
//
// Ok(n_written)
// }
// #[inline]
// fn is_write_vectored(&self) -> bool {
// true
// }
#[inline]
fn write_all(&mut self, data: &[u8]) -> io::Result<()> {
if self.write(data)? == data.len() { Ok(()) } else { Err(io::Error::WRITE_ALL_EOF) }
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Write is implemented for `Vec<u8>` by appending to the vector.
/// The vector will grow as needed.
#[cfg(feature = "alloc")]
impl Write for alloc::vec::Vec<u8> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
// #[inline]
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
// let len = bufs.iter().map(|b| b.len()).sum();
// self.reserve(len);
// for buf in bufs {
// self.extend_from_slice(buf);
// }
// Ok(len)
// }
// #[inline]
// fn is_write_vectored(&self) -> bool {
// true
// }
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.extend_from_slice(buf);
Ok(())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Read is implemented for `VecDeque<u8>` by consuming bytes from the front of the `VecDeque`.
#[cfg(feature = "alloc")]
impl Read for alloc::collections::VecDeque<u8> {
/// Fill `buf` with the contents of the "front" slice as returned by
/// [`as_slices`][`alloc::collections::VecDeque::as_slices`]. If the contained byte slices of the `VecDeque` are
/// discontiguous, multiple calls to `read` will be needed to read the entire content.
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let (ref mut front, _) = self.as_slices();
let n = Read::read(front, buf)?;
self.drain(..n);
Ok(n)
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
let (front, back) = self.as_slices();
// Use only the front buffer if it is big enough to fill `buf`, else use
// the back buffer too.
match split_at_mut_checked(buf, front.len()) {
None => buf.copy_from_slice(&front[..buf.len()]),
Some((buf_front, buf_back)) => match split_at_checked(back, buf_back.len()) {
Some((back, _)) => {
buf_front.copy_from_slice(front);
buf_back.copy_from_slice(back);
}
None => {
self.clear();
return Err(io::Error::READ_EXACT_EOF);
}
},
}
self.drain(..buf.len());
Ok(())
}
// #[inline]
// fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
// let (ref mut front, _) = self.as_slices();
// let n = cmp::min(cursor.capacity(), front.len());
// Read::read_buf(front, cursor)?;
// self.drain(..n);
// Ok(())
// }
// fn read_buf_exact(&mut self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
// let len = cursor.capacity();
// let (front, back) = self.as_slices();
//
// match front.split_at_checked(cursor.capacity()) {
// Some((front, _)) => cursor.append(front),
// None => {
// cursor.append(front);
// match back.split_at_checked(cursor.capacity()) {
// Some((back, _)) => cursor.append(back),
// None => {
// cursor.append(back);
// self.clear();
// return Err(io::Error::READ_EXACT_EOF);
// }
// }
// }
// }
//
// self.drain(..len);
// Ok(())
// }
// #[inline]
// fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
// // The total len is known upfront so we can reserve it in a single call.
// let len = self.len();
// buf.try_reserve(len)?;
//
// let (front, back) = self.as_slices();
// buf.extend_from_slice(front);
// buf.extend_from_slice(back);
// self.clear();
// Ok(len)
// }
// #[inline]
// fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
// // SAFETY: We only append to the buffer
// unsafe { io::append_to_string(buf, |buf| self.read_to_end(buf)) }
// }
}
// <[_]>::split_at_checked requires Rust 1.80.
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
fn split_at_checked<T>(this: &[T], mid: usize) -> Option<(&[T], &[T])> {
if mid <= this.len() { Some(this.split_at(mid)) } else { None }
}
// <[_]>::split_at_mut_checked requires Rust 1.80.
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
fn split_at_mut_checked<T>(this: &mut [T], mid: usize) -> Option<(&mut [T], &mut [T])> {
if mid <= this.len() { Some(this.split_at_mut(mid)) } else { None }
}
/// Write is implemented for `VecDeque<u8>` by appending to the `VecDeque`, growing it as needed.
#[cfg(feature = "alloc")]
impl Write for alloc::collections::VecDeque<u8> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend(buf);
Ok(buf.len())
}
// #[inline]
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
// let len = bufs.iter().map(|b| b.len()).sum();
// self.reserve(len);
// for buf in bufs {
// self.extend(&**buf);
// }
// Ok(len)
// }
// #[inline]
// fn is_write_vectored(&self) -> bool {
// true
// }
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.extend(buf);
Ok(())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Traits, helpers, and type definitions for core I/O functionality.
//!
//! This module is a subset of the [`std::io`] module except that the stdio functions returns
//! the `Result`.
//!
//! [`std::io`]: https://doc.rust-lang.org/std/io/index.html
// Based on nightly-2025-02-19's std::io module.
// TODO(io): io utilities e.g., Cursor?
pub use self::error::{Error, ErrorKind, RawOsError, Result};
#[macro_use]
mod error;
mod impls;
#[cfg(feature = "stdio")]
pub use self::stdio::{IsTerminal, Stderr, Stdin, Stdout, stderr, stdin, stdout};
#[cfg(feature = "stdio")]
mod stdio;
use core::fmt;
const _: fn() = || {
fn assert_dyn_compatibility<T: ?Sized>() {}
assert_dyn_compatibility::<dyn Read>();
assert_dyn_compatibility::<dyn Write>();
assert_dyn_compatibility::<dyn Seek>();
};
pub(crate) fn default_read_exact<R: ?Sized + Read>(this: &mut R, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match this.read(buf) {
Ok(0) => break,
Ok(n) => {
buf = &mut buf[n..];
}
Err(ref e) if e.is_interrupted() => {}
Err(e) => return Err(e),
}
}
if buf.is_empty() { Ok(()) } else { Err(Error::READ_EXACT_EOF) }
}
/// The `no_std` subset of `std::io::Read`.
///
/// Unless explicitly stated otherwise, API contracts adhere to `std::io::Read`.
///
/// See [`std::io::Read` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Read.html
pub trait Read {
/// Pull some bytes from this source into the specified buffer, returning
/// how many bytes were read.
///
/// See [`std::io::Read::read` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
// TODO(io,alloc)
// /// Read all bytes until EOF in this source, placing them into `buf`.
// ///
// /// See [`std::io::Read::read_to_end` documentation][std] for details.
// ///
// /// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_end
// #[cfg(feature = "alloc")]
// fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
// default_read_to_end(self, buf)
// }
//
// /// Read all bytes until EOF in this source, appending them to `buf`.
// ///
// /// See [`std::io::Read::read_to_string` documentation][std] for details.
// ///
// /// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_string
// #[cfg(feature = "alloc")]
// fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
// default_read_to_string(self, buf)
// }
/// Read the exact number of bytes required to fill `buf`.
///
/// See [`std::io::Read::read_exact` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
default_read_exact(self, buf)
}
}
/// The `no_std` subset of `std::io::Write`.
///
/// Unless explicitly stated otherwise, API contracts adhere to `std::io::Write`.
///
/// See [`std::io::Write` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html
pub trait Write {
/// Write a buffer into this writer, returning how many bytes were written.
///
/// See [`std::io::Write::write` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.write
fn write(&mut self, buf: &[u8]) -> Result<usize>;
/// Flush this output stream, ensuring that all intermediately buffered
/// contents reach their destination.
///
/// See [`std::io::Write::flush` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
fn flush(&mut self) -> Result<()>;
/// Attempts to write an entire buffer into this writer.
///
/// See [`std::io::Write::write_all` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => return Err(Error::WRITE_ALL_EOF),
Ok(n) => buf = &buf[n..],
Err(ref e) if e.is_interrupted() => {}
Err(e) => return Err(e),
}
}
Ok(())
}
/// Writes a formatted string into this writer, returning any error
/// encountered.
///
/// See [`std::io::Write::write_fmt` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_fmt
fn write_fmt(&mut self, f: fmt::Arguments<'_>) -> Result<()> {
// Create a shim which translates a Write to a fmt::Write and saves
// off I/O errors. instead of discarding them
struct Adapter<'a, T: ?Sized> {
inner: &'a mut T,
error: Result<()>,
}
impl<T: ?Sized + Write> fmt::Write for Adapter<'_, T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
match self.inner.write_all(s.as_bytes()) {
Ok(()) => Ok(()),
Err(e) => {
self.error = Err(e);
Err(fmt::Error)
}
}
}
}
let mut output = Adapter { inner: self, error: Ok(()) };
match fmt::write(&mut output, f) {
Ok(()) => Ok(()),
Err(..) => {
// check if the error came from the underlying `Write` or not
if output.error.is_err() {
output.error
} else {
// This shouldn't happen: the underlying stream did not error, but somehow
// the formatter still errored?
panic!(
"a formatting trait implementation returned an error when the underlying stream did not"
);
}
}
}
}
}
/// The `no_std` subset of `std::io::Seek`.
///
/// Unless explicitly stated otherwise, API contracts adhere to `std::io::Seek`.
///
/// See [`std::io::Seek` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Seek.html
pub trait Seek {
/// Seek to an offset, in bytes, in a stream.
///
/// See [`std::io::Seek::seek` documentation][std] for details.
///
/// [std]: https://doc.rust-lang.org/std/io/trait.Seek.html#tymethod.seek
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
/// Rewind to the beginning of a stream.
///
/// This is a convenience method, equivalent to `seek(SeekFrom::Start(0))`.
fn rewind(&mut self) -> Result<()> {
self.seek(SeekFrom::Start(0))?;
Ok(())
}
// /// Returns the current seek position from the start of the stream.
// ///
// /// This is equivalent to `self.seek(SeekFrom::Current(0))`.
// fn stream_position(&mut self) -> Result<u64> {
// self.seek(SeekFrom::Current(0))
// }
// /// Seeks relative to the current position.
// ///
// /// This is equivalent to `self.seek(SeekFrom::Current(offset))` but
// /// doesn't return the new position which can allow some implementations
// /// such as [`BufReader`] to perform more efficient seeks.
// fn seek_relative(&mut self, offset: i64) -> Result<()> {
// self.seek(SeekFrom::Current(offset))?;
// Ok(())
// }
}
/// Enumeration of possible methods to seek within an I/O object.
///
/// It is used by the [`Seek`] trait.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SeekFrom {
/// Sets the offset to the provided number of bytes.
Start(u64),
/// Sets the offset to the size of this object plus the specified number of
/// bytes.
///
/// It is possible to seek beyond the end of an object, but it's an error to
/// seek before byte 0.
End(i64),
// TODO(arm_compat): It appears that SeekFrom::Current cannot be implemented with APIs provided by Arm semihosting...
// /// Sets the offset to the current position plus the specified number of
// /// bytes.
// ///
// /// It is possible to seek beyond the end of an object, but it's an error to
// /// seek before byte 0.
// Current(i64),
}
+139
View File
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::fmt;
use crate::{fd::AsFd as _, io, sys};
/// Constructs a new handle to the standard input of the current process.
///
/// Unlike [`std::io::stdin`], this function returns the `Result`.
///
/// # Platform-specific behavior
///
/// Currently, this function will always success on MIPS32/MIPS64, On other architectures,
/// this may fail if semihosting is only partially supported.
///
/// Also, we have found that reading from stdin does not work well on MIPS32/MIPS64.
///
/// [`std::io::stdin`]: https://doc.rust-lang.org/std/io/fn.stdin.html
pub fn stdin() -> io::Result<Stdin> {
sys::stdio::stdin().map(Stdin)
}
/// Constructs a new handle to the standard output of the current process.
///
/// Unlike [`std::io::stdout`], this function returns the `Result`.
///
/// # Platform-specific behavior
///
/// Currently, this function will always success on MIPS32/MIPS64, On other architectures,
/// this may fail if semihosting is only partially supported.
///
/// [`std::io::stdout`]: https://doc.rust-lang.org/std/io/fn.stdout.html
pub fn stdout() -> io::Result<Stdout> {
sys::stdio::stdout().map(Stdout)
}
/// Constructs a new handle to the standard error of the current process.
///
/// Unlike [`std::io::stderr`], this function returns the `Result`.
///
/// # Platform-specific behavior
///
/// Currently, this function will always success on MIPS32/MIPS64, On other architectures,
/// this may fail if semihosting is only partially supported.
///
/// [`std::io::stderr`]: https://doc.rust-lang.org/std/io/fn.stderr.html
pub fn stderr() -> io::Result<Stderr> {
sys::stdio::stderr().map(Stderr)
}
/// A handle to the standard input stream of a process.
///
/// Created by the [`io::stdin`] method.
pub struct Stdin(sys::stdio::StdioFd);
/// A handle to the standard output stream of a process.
///
/// Created by the [`io::stdout`] method.
pub struct Stdout(sys::stdio::StdioFd);
/// A handle to the standard error stream of a process.
///
/// Created by the [`io::stderr`] method.
pub struct Stderr(sys::stdio::StdioFd);
impl_as_fd!(Stdin, Stdout, Stderr);
// TODO(stdio): std provides io trait implementations on &Std{in,out,err} as they uses locks.
impl io::Read for Stdin {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
sys::read(self.as_fd(), buf)
}
}
impl io::Write for Stdout {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
sys::write(self.as_fd(), bytes)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Write for Stderr {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
sys::write(self.as_fd(), bytes)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl fmt::Debug for Stdin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Stdin").finish_non_exhaustive()
}
}
impl fmt::Debug for Stdout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Stdout").finish_non_exhaustive()
}
}
impl fmt::Debug for Stderr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Stderr").finish_non_exhaustive()
}
}
/// Trait to determine if a descriptor/handle refers to a terminal/tty.
pub trait IsTerminal: crate::sealed::Sealed {
/// Returns `true` if the descriptor/handle refers to a terminal/tty.
///
/// On platforms where Rust does not know how to detect a terminal yet, this will return
/// `false`. This will also return `false` if an unexpected error occurred, such as from
/// passing an invalid file descriptor.
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_ISTTY] |
/// | MIPS32, MIPS64 | UHI_fstat |
///
/// [SYS_ISTTY]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-istty-0x09
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "isatty")]
#[doc(alias = "SYS_ISTTY")] // arm_compat
fn is_terminal(&self) -> bool;
}
macro_rules! impl_is_terminal {
($($t:ty),*$(,)?) => {$(
impl crate::sealed::Sealed for $t {}
impl crate::io::IsTerminal for $t {
#[inline]
fn is_terminal(&self) -> bool {
use crate::fd::AsFd as _;
crate::sys::stdio::is_terminal(self.as_fd())
}
}
)*}
}
impl_is_terminal!(Stdin, Stdout, Stderr);
#[cfg(feature = "fs")]
impl_is_terminal!(crate::fs::File);
+391
View File
@@ -0,0 +1,391 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
/*!
<!-- Note: Document from sync-markdown-to-rustdoc:start through sync-markdown-to-rustdoc:end
is synchronized from README.md. Any changes to that range are not preserved. -->
<!-- tidy:sync-markdown-to-rustdoc:start -->
Semihosting for AArch64, Arm, RISC-V, LoongArch, MIPS32, MIPS64, and Xtensa.
This library provides access to semihosting, a mechanism for programs running on the real or virtual (e.g., QEMU) target to communicate with I/O facilities on the host system. See the [Arm documentation](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst) for more information on semihosting.
APIs are categorized into the following four types:
- The top-level API (`semihosting::{io,fs,..}`) provides a subset of the standard library's similar APIs.
- `io`: Provide no-std io traits and `std{in,out,err}`. (`std{in,out,err}` requires `stdio` feature, others are unconditionally provided)
- `fs`: Provide methods to manipulate the contents of the host filesystem. (requires `fs` feature)
- `process`: Provide `abort` and `exit`.
- `dbg!`/`print{,ln}!`/`eprint{,ln}!`: macros to output to stdout/stderr. (requires `stdio` feature)
Note that some APIs are not strictly a subset of the standard library.
- API that uses types not available in `core` such as `Path` (technically, the same thing could be implemented, but it makes sense to use `CStr` directly, because when converting a long `Path`/`OsStr` to `CStr`, it needs to either [do an allocation](https://github.com/rust-lang/rust/blob/1.84.0/library/std/src/sys/pal/common/small_c_string.rs#L25-L26) or return an error)
- API that panics on failure in `std` (in no-std it makes sense to return `Result` since `panic=abort` is default)
- Helpers that are useful when using this library.
- `c!`: `CStr` literal macro. (Since Rust 1.77, this macro is soft-deprecated in favor of C string literals (`c"..."`).)
- `semihosting::sys` module provides low-level access to platform-specific semihosting interfaces.
- `semihosting::experimental` module provides experimental APIs. See [optional features](#optional-features) for more.
- `env`: Provide `args`.
- `time`: Provide `Instant` and `SystemTime`.
- `random`: Provide `fill_bytes` and `fill_uninit_bytes`.
- `panic`: Provide `catch_unwind`.
Additionally, this library provides a panic handler for semihosting, `-C panic=unwind` support, backtrace support, via [optional features](#optional-features).
## Platform Support
The following target architectures are supported:
| target_arch | Specification | `semihosting::sys` module | Note |
| ----------- | ------------- | ------------------------- | ---- |
| aarch64 | [Semihosting for AArch32 and AArch64](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst) | `sys::arm_compat` | |
| arm | [Semihosting for AArch32 and AArch64](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst) | `sys::arm_compat` | use `SVC` on A+R profile by default based on Arm's recommendation but it can be changed by [`trap-hlt` feature](#optional-features-trap-hlt). |
| riscv32/riscv64 | [RISC-V Semihosting](https://github.com/riscv-non-isa/riscv-semihosting/blob/1.0/riscv-semihosting.adoc) | `sys::arm_compat` | |
| loongarch32/loongarch64 | | `sys::arm_compat` | |
| xtensa | [OpenOCD Semihosting](https://github.com/espressif/openocd-esp32/blob/HEAD/src/target/espressif/esp_xtensa_semihosting.c) | `sys::arm_compat` | requires [`openocd-semihosting` feature](#optional-features-openocd-semihosting) |
| mips/mips32r6/mips64/mips64r6 | Unified Hosting Interface (MD01069) | `sys::mips` | |
The host must be running an emulator or a debugger attached to the target.
The following targets have been tested on CI. (qemu-system has been tested on Linux, macOS, and Windows hosts, and qemu-user on Linux host.)
| target | qemu-system | qemu-user | note |
| ----------------------------------------- | ---------------- | -------------- | --------- |
| `aarch64-unknown-none{,-softfloat}` | ✓ | ✓ | |
| `aarch64_be-unknown-none{,-softfloat}` | ✓ | ✓ | \[2] |
| `{arm,thumb}v4t-none-eabi` | ✓ | ✓ | |
| `{arm,thumb}v5te-none-eabi` | ✓ | ✓ | |
| `armv6-none-eabi{,hf}` | ✓ | ✓ | |
| `thumbv6-none-eabi` | ✓ | ✓ | |
| `{arm,thumb}v7a-none-eabi{,hf}` | ✓ | ✓ | |
| `{arm,thumb}v7r-none-eabi{,hf}` | ✓ | ✓ | |
| `armebv7r-none-eabi{,hf}` | * | ✓ | \[1] |
| `{arm,thumb}v8r-none-eabihf` | ✓ | ✓ | |
| `armebv8r-none-eabihf` | * | ✓ | \[1] |
| `thumbv6m-none-eabi` | ✓ | ✓ | |
| `thumbv7m-none-eabi` | ✓ | ✓ | |
| `thumbv7em-none-eabi{,hf}` | ✓ | ✓ | |
| `thumbv8m.base-none-eabi` | ✓ | ✓ | |
| `thumbv8m.main-none-eabi{,hf}` | ✓ | ✓ | |
| `riscv32*-unknown-none-elf` | ✓ | ✓ | |
| `riscv64*-unknown-none-elf` | ✓ | ✓ | |
| `loongarch32-unknown-none{,-softfloat}` | ✓ | N/A | \[2] \[3] |
| `loongarch64-unknown-none{,-softfloat}` | ✓ | ✓ | \[2] |
| `mips{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
| `mips{,el}-mti-none-elf` | ✓ | N/A | \[2] \[4] |
| `mips64{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
| `mipsisa32r6{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
| `mipsisa64r6{,el}-unknown-none` | ✓ | N/A | \[2] \[4] |
\[1] Due to [qemu-system bug about big-endian Arm](https://github.com/taiki-e/semihosting/issues/18), operations that contains reading/writing of strings such as file open (`stdio`, `fs`, `random`), `args`, and `SYS_SYSTEM` don't work.<br>
\[2] Tested with patched QEMU ([aarch64_be](https://patchew.org/QEMU/20260106-semihosting-cpu-tswap-v1-0-646576c25f56@eonerc.rwth-aachen.de/), [loongarch](https://lore.kernel.org/qemu-devel/20241222-semihosting-v1-1-8a770df60e9c@flygoat.com/), [mips](https://github.com/taiki-e/qemu/commit/c2d1c8dbc5cb878fbcce824180aa812213785124)). QEMU binaries that we are using for test are available on our QEMU fork ([aarch64_be/mips](https://github.com/taiki-e/qemu/releases/tag/patched-10.2.0), [loongarch](https://github.com/taiki-e/qemu/releases/tag/loongarch-semi-10.1.3)).<br>
\[3] Requires Rust 1.91+.<br>
\[4] Requires nightly due to `#![feature(asm_experimental_arch)]`.<br>
## Optional features
All features are disabled by default.
In general use cases, you probably only need the `stdio` feature that enables print-related macros and/or the `panic-handler` feature that exits with a non-zero error code on panic.
```toml
[dependencies]
semihosting = { version = "0.1", features = ["stdio", "panic-handler"] }
```
- **`alloc`**<br>
Use `alloc`.
- **`stdio`**<br>
Enable `semihosting::io::{stdin,stdout,stderr}` and `semihosting::{print*,eprint*,dbg}`.
- **`fs`**<br>
Enable `semihosting::fs`.
- **`panic-handler`**<br>
Provide panic handler based on `semihosting::process::exit`.
If the `stdio` feature is also enabled, this attempt to output panic message and
location to stderr.
- <a name="optional-features-trap-hlt"></a>**`trap-hlt`**<br>
Arm-specific: Use HLT instruction (except for M-profile architecture).
[Arm documentation](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#the-semihosting-interface) says:
> The `HLT` encodings are new in version 2.0 of the semihosting specification.
> Where possible, have semihosting callers continue to use the previously existing
> trap instructions to ensure compatibility with legacy semihosting implementations.
> These trap instructions are `HLT` for A64, `SVC` on A+R profile A32 or T32, and
> `BKPT` on M profile. However, it is necessary to change from SVC to HLT instructions
> to support AArch32 semihosting properly in a mixed AArch32/AArch64 system.
>
> ARM encourages semihosting callers to implement support for trapping using `HLT`
> on A32 and T32 as a configurable option. ARM strongly discourages semihosting
> callers from mixing the `HLT` and `SVC` mechanisms within the same executable.
Based on the Arm's recommendation, this is implemented as an optional feature.
Note:
- Enabling this feature on architectures other than Arm or on Arm M-profile architecture will result in a compile error.
- <a name="optional-features-openocd-semihosting"></a>**`openocd-semihosting`**<br>
Xtensa-specific: Use OpenOCD Semihosting.
Xtensa has two semihosting interfaces:
- Tensilica ISS SIMCALL used in Cadence tools and [QEMU](https://www.qemu.org/docs/master/about/emulation.html#supported-targets).
- Arm-semihosting-compatible semihosting interface used in [OpenOCD](https://github.com/espressif/openocd-esp32/blob/HEAD/src/target/espressif/esp_xtensa_semihosting.c) and [probe-rs](https://github.com/probe-rs/probe-rs/pull/2303). (This crate calls it "OpenOCD Semihosting", which is the same as the option name in [newlib-esp32](https://github.com/espressif/newlib-esp32/blob/esp-4.3.0_20240530/libgloss/xtensa/syscalls.c#L21).)
When this feature is enabled, this crate uses the latter interface.
This crate does not currently support SIMCALL-based semihosting, but users need to explicitly enable the feature to avoid accidentally selecting a different one than one actually want to use.
Note:
- Enabling this feature on architectures other than Xtensa will result in a compile error.
- **`portable-atomic`**<br>
Use [portable-atomic]'s atomic types.
portable-atomic provides atomic CAS on targets where the standard library does not provide atomic CAS.
To use the `panic-unwind` feature on such targets (e.g., RISC-V without A-extension), you need to enable this feature.
See [its documentation](https://github.com/taiki-e/portable-atomic#optional-features-critical-section) for details.
- **`args`**<br>
Enable `semihosting::experimental::env::args`.
Note:
- This feature is experimental (tracking issue: [#1](https://github.com/taiki-e/semihosting/issues/1))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- **`time`**<br>
Enable `semihosting::experimental::time`.
Note:
- This feature is experimental (tracking issue: [#2](https://github.com/taiki-e/semihosting/issues/2))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- **`random`**<br>
Enable `semihosting::experimental::random`.
Note:
- This feature is experimental (tracking issue: [#22](https://github.com/taiki-e/semihosting/issues/22))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- This implicitly enables the `fs` features.
- This is optimized on targets atomic CAS. You may want to use `portable-atomic` feature together if your target doesn't support atomic CAS (e.g., RISC-V without A-extension).
- **`panic-unwind`**<br>
Provide `-C panic=unwind` support for panic handler and enable
`semihosting::experimental::panic::catch_unwind`.
This currently uses [unwinding] crate to support unwinding.
See its documentation for supported platforms and requirements.
Note:
- This feature is experimental (tracking issue: [#3](https://github.com/taiki-e/semihosting/issues/3))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- This currently requires nightly compiler.
- This implicitly enables the `alloc` and `panic-handler` features.
- This uses atomic CAS. You need to use `portable-atomic` feature together if your target doesn't support atomic CAS (e.g., RISC-V without A-extension).
- When enabling this feature, you may need to rebuild the standard library with
`-C panic=unwind` for `catch_unwind` to work properly. The recommended way to
rebuild the standard library is passing `-Z build-std="core,alloc"` option to cargo.
- **`backtrace`**<br>
Provide backtrace support for panic handler.
This currently uses [unwinding] crate to support backtrace.
See its documentation for supported platforms and requirements.
Note:
- This feature is experimental (tracking issue: [#3](https://github.com/taiki-e/semihosting/issues/3))
and outside of the normal semver guarantees and minor or patch versions of semihosting may make
breaking changes to them at any time.
- This implicitly enables the `stdio` feature.
- When enabling this, it is recommended to also enable the `panic-unwind` feature. Otherwise, a decent backtrace will not be displayed at this time. (Using [`-C force-unwind-tables`](https://doc.rust-lang.org/rustc/codegen-options/index.html#force-unwind-tables) may work, but has not been tested yet.)
- Currently, the backtrace generated is not human-readable.
```text
panicked at 'a', src/main.rs:86:13
stack backtrace:
0x84dc0
0x8ed80
0x8332c
0x83654
0x80644
0x803cc
0x809dc
0x800bc
```
You can use `addr2line` to resolve the addresses and [rustfilt] to demangle Rust symbols.
For example, run the following command (please replace `<path/to/binary>` with your binary path), then paste the addresses:
```sh
llvm-addr2line -fipe <path/to/binary> | rustfilt
```
[portable-atomic]: https://github.com/taiki-e/portable-atomic
[rustfilt]: https://github.com/luser/rustfilt
[unwinding]: https://github.com/nbdd0121/unwinding
<!-- tidy:sync-markdown-to-rustdoc:end -->
*/
#![no_std]
#![doc(test(
no_crate_inject,
attr(
deny(warnings, rust_2018_idioms, single_use_lifetimes),
allow(dead_code, unused_variables)
)
))]
#![warn(unsafe_op_in_unsafe_fn)]
#![warn(
// Lints that may help when writing public library.
missing_debug_implementations,
missing_docs,
clippy::alloc_instead_of_core,
clippy::exhaustive_enums,
clippy::exhaustive_structs,
clippy::impl_trait_in_params,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
// clippy::missing_inline_in_public_items,
)]
#![allow(
clippy::inline_always,
clippy::len_without_is_empty,
clippy::missing_panics_doc,
clippy::must_use_candidate,
clippy::new_without_default
)]
#![cfg_attr(
any(
all(target_arch = "loongarch32", semihosting_no_asm),
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "xtensa",
),
feature(asm_experimental_arch)
)]
// docs.rs only (cfg is enabled by docs.rs, not build script)
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(
docsrs,
doc(auto_cfg(hide(semihosting_no_duration_checked_float, semihosting_no_error_in_core)))
)]
#![cfg_attr(semihosting_no_strict_provenance, allow(unstable_name_collisions))]
// 64-bit architecture's 32-bit ABI (e.g., AArch64 ILP32 ABI) are also
// unsupported yet (is there a semihosting interface defined for those ABIs?).
#[cfg(not(any(
all(target_arch = "aarch64", target_pointer_width = "64"),
target_arch = "arm",
target_arch = "riscv32",
all(target_arch = "riscv64", target_pointer_width = "64"),
target_arch = "loongarch32",
all(target_arch = "loongarch64", target_pointer_width = "64"),
target_arch = "mips",
target_arch = "mips32r6",
all(target_arch = "mips64", target_pointer_width = "64"),
all(target_arch = "mips64r6", target_pointer_width = "64"),
target_arch = "xtensa",
)))]
compile_error!(
"unsupported target; if you need support for this target, \
please submit an issue at <https://github.com/taiki-e/semihosting>"
);
#[cfg(target_arch = "xtensa")]
#[cfg(not(feature = "openocd-semihosting"))]
compile_error!(
"xtensa has two semihosting interfaces so you have to select implementation;\n\
please enable `openocd-semihosting` feature if you want to use OpenOCD Semihosting used in OpenOCD, probe-rs, etc.\n\
see <https://docs.rs/semihosting/latest/semihosting/#optional-features-openocd-semihosting> for more."
);
#[cfg(not(target_arch = "xtensa"))]
#[cfg(feature = "openocd-semihosting")]
compile_error!("`openocd-semihosting` feature is only available on Xtensa");
#[cfg(not(all(
target_arch = "arm",
not(any(target_feature = "mclass", semihosting_target_feature = "mclass")),
)))]
#[cfg(feature = "trap-hlt")]
compile_error!("`trap-hlt` feature is only available on Arm A+R profile");
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(doc)]
extern crate self as semihosting;
#[cfg(test)]
extern crate std;
#[macro_use]
mod utils;
#[cfg(any(
feature = "panic-unwind",
all(feature = "random", any(target_has_atomic = "32", feature = "portable-atomic")),
))]
cfg_sel!({
#[cfg(feature = "portable-atomic")]
{
use portable_atomic as atomic;
}
#[cfg(else)]
{
use core::sync::atomic;
}
});
#[macro_use]
mod macros;
#[macro_use]
mod c_str;
#[macro_use]
pub mod fd;
#[macro_use]
pub mod io;
#[cfg(any(feature = "args", feature = "panic-unwind", feature = "time", feature = "random"))]
pub mod experimental;
#[cfg(feature = "fs")]
pub mod fs;
#[cfg(feature = "panic-handler")]
mod panicking;
pub mod process;
pub mod sys;
#[cfg(feature = "stdio")]
mod sealed {
#[allow(unknown_lints, unnameable_types)] // Not public API. unnameable_types is available on Rust 1.79+
pub trait Sealed {}
}
// Not public API.
#[doc(hidden)]
pub mod __private {
#[doc(hidden)]
pub use core::{
concat,
ffi::CStr,
file, line,
result::Result::{Err, Ok},
stringify, write, writeln,
};
#[doc(hidden)]
pub use crate::c_str::const_c_str_check;
}
+70
View File
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![allow(missing_docs)] // TODO
#[cfg(feature = "stdio")]
#[macro_export]
macro_rules! print {
($($tt:tt)*) => {
if let $crate::__private::Ok(mut stdout) = $crate::io::stdout() {
use $crate::io::Write as _;
let _ = $crate::__private::write!(stdout, $($tt)*);
}
};
}
#[cfg(feature = "stdio")]
#[macro_export]
macro_rules! println {
($($tt:tt)*) => {
if let $crate::__private::Ok(mut stdout) = $crate::io::stdout() {
use $crate::io::Write as _;
let _ = $crate::__private::writeln!(stdout, $($tt)*);
}
};
}
#[cfg(feature = "stdio")]
#[macro_export]
macro_rules! eprint {
($($tt:tt)*) => {
if let $crate::__private::Ok(mut stderr) = $crate::io::stderr() {
use $crate::io::Write as _;
let _ = $crate::__private::write!(stderr, $($tt)*);
}
};
}
#[cfg(feature = "stdio")]
#[macro_export]
macro_rules! eprintln {
($($tt:tt)*) => {
if let $crate::__private::Ok(mut stderr) = $crate::io::stderr() {
use $crate::io::Write as _;
let _ = $crate::__private::writeln!(stderr, $($tt)*);
}
};
}
#[cfg(feature = "stdio")]
#[macro_export]
macro_rules! dbg {
() => {
$crate::eprintln!("[{}:{}]", $crate::__private::file!(), $crate::__private::line!())
};
($val:expr $(,)?) => {
match $val {
tmp => {
$crate::eprintln!(
"[{}:{}] {} = {:#?}",
$crate::__private::file!(),
$crate::__private::line!(),
$crate::__private::stringify!($val),
&tmp,
);
tmp
}
}
};
($($val:expr),+ $(,)?) => {
($($crate::dbg!($val)),+,)
};
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
#[allow(clippy::used_underscore_binding)]
#[inline(never)]
#[cfg_attr(not(test), panic_handler)]
fn _panic(_info: &core::panic::PanicInfo<'_>) -> ! {
#[cfg(feature = "stdio")]
eprintln!("{_info}");
#[cfg(feature = "panic-unwind")]
{
use crate::atomic::Ordering;
// TODO(panic-unwind): PANICKED is global, so if panics occur concurrently on thread 1 and
// thread 2 this could be mistakenly thought to be a double panic. However, I'm not sure if
// there is a way to handle that well without thread local.
if crate::experimental::panic::PANICKED.fetch_add(1, Ordering::AcqRel) != 0 {
#[cfg(feature = "stdio")]
eprintln!("panic during panic, aborting");
crate::process::abort()
}
}
#[cfg(feature = "backtrace")]
{
use core::{ffi::c_void, ptr};
use unwinding::abi::{_Unwind_Backtrace, _Unwind_GetIP, UnwindContext, UnwindReasonCode};
extern "C" fn callback(
unwind_ctx: &UnwindContext<'_>,
_arg: *mut c_void,
) -> UnwindReasonCode {
let ip = _Unwind_GetIP(unwind_ctx);
if ip == 0 {
UnwindReasonCode::NORMAL_STOP
} else {
eprintln!(" {ip:#x}");
UnwindReasonCode::NO_REASON
}
}
eprintln!("stack backtrace:");
_Unwind_Backtrace(callback, ptr::null_mut());
}
#[cfg(feature = "panic-unwind")]
{
let _code = unwinding::panic::begin_panic(alloc::boxed::Box::new(""));
#[cfg(feature = "stdio")]
eprintln!("failed to begin panic (unwind error code {})", _code.0);
}
crate::process::exit(101)
}
+132
View File
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! A module for working with processes.
//!
//! This module provides [`abort`] and [`exit`] for terminating the current process.
//!
//! See also [`semihosting::sys::arm_compat::sys_system`] for platform-specific
//! semihosting interface to run a system command on the host command-line interpreter.
use core::{convert::Infallible, fmt};
use crate::sys;
/// This type represents the status code the current process can return
/// to its parent under normal termination.
#[derive(Debug, Clone, Copy)]
pub struct ExitCode(u8);
impl ExitCode {
/// The canonical `ExitCode` for successful termination on this platform.
pub const SUCCESS: Self = Self(0);
/// The canonical `ExitCode` for unsuccessful termination on this platform.
pub const FAILURE: Self = Self(1);
// Note: the corresponding API in std is still unstable: https://github.com/rust-lang/rust/issues/97100
/// Exit the current process with the given `ExitCode`.
///
/// Note that this has the same caveats as [`process::exit()`][exit], namely that this function
/// terminates the process immediately, so no destructors on the current stack or any other
/// thread's stack will be run.
pub fn exit_process(self) -> ! {
exit(self.0 as i32)
}
}
/// The default value is [`ExitCode::SUCCESS`]
impl Default for ExitCode {
fn default() -> Self {
Self::SUCCESS
}
}
impl From<u8> for ExitCode {
/// Constructs an `ExitCode` from an arbitrary u8 value.
fn from(code: u8) -> Self {
Self(code)
}
}
/// Terminates the current process with the specified exit code.
///
/// Note that because this function never returns, and that it terminates the
/// process, no destructors on the current stack or any other thread's stack
/// will be run.
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | -------------------------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_EXIT] / [SYS_EXIT_EXTENDED] |
/// | MIPS32, MIPS64 | UHI_exit |
///
/// [SYS_EXIT]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-exit-0x18
/// [SYS_EXIT_EXTENDED]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-exit-extended-0x20
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "SYS_EXIT")] // arm_compat
#[doc(alias = "UHI_exit")] // mips
pub fn exit(code: i32) -> ! {
sys::exit(code)
}
/// Terminates the process in an abnormal fashion.
///
/// Note that because this function never returns, and that it terminates the
/// process, no destructors on the current stack or any other thread's stack
/// will be run.
#[cold]
pub fn abort() -> ! {
exit(134) // SIGABRT
}
/// A trait for implementing arbitrary return types in the `main` function.
pub trait Termination {
/// Is called to get the representation of the value as status code.
/// This status code is returned to the operating system.
fn report(self) -> ExitCode;
}
impl Termination for () {
#[inline]
fn report(self) -> ExitCode {
ExitCode::SUCCESS
}
}
// TODO: ! type is unstable: https://github.com/rust-lang/rust/issues/35121
// impl Termination for ! {
// fn report(self) -> ExitCode {
// self
// }
// }
impl Termination for Infallible {
fn report(self) -> ExitCode {
match self {}
}
}
impl Termination for ExitCode {
#[inline]
fn report(self) -> ExitCode {
self
}
}
impl<T: Termination, E: fmt::Debug> Termination for Result<T, E> {
#[allow(clippy::used_underscore_binding)]
fn report(self) -> ExitCode {
match self {
Ok(val) => val.report(),
Err(_err) => {
#[cfg(feature = "stdio")]
eprintln!("Error: {_err:?}");
ExitCode::FAILURE
}
}
}
}
+68
View File
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-errno-0x13
// > Whether `errno` is set or not, and to what value, is entirely host-specific,
// > except where the ISO C standard defines the behavior.
//
// For now, this only includes values matched in all host platforms officially
// supported by QEMU (i.e, Linux, macOS, FreeBSD, NetBSD, OpenBSD, Windows).
// https://github.com/qemu/qemu/blob/v10.2.0/docs/about/build-platforms.rst
// TODO(sys): should we expose this as public API of `sys` module?
#![allow(dead_code)]
use core::ffi::c_int;
pub(crate) const EPERM: c_int = 1;
pub(crate) const ENOENT: c_int = 2;
pub(crate) const ESRCH: c_int = 3;
pub(crate) const EINTR: c_int = 4;
pub(crate) const EIO: c_int = 5;
pub(crate) const ENXIO: c_int = 6;
pub(crate) const E2BIG: c_int = 7;
pub(crate) const ENOEXEC: c_int = 8;
pub(crate) const EBADF: c_int = 9;
pub(crate) const ECHILD: c_int = 10;
// pub(crate) const EAGAIN: c_int = 11; // linux, solarish, windows
// pub(crate) const EWOULDBLOCK: c_int = EAGAIN; // linux, solarish
// pub(crate) const EDEADLK: c_int = 11; // bsd-like
pub(crate) const ENOMEM: c_int = 12;
pub(crate) const EACCES: c_int = 13;
pub(crate) const EFAULT: c_int = 14;
// pub(crate) const ENOTBLK: c_int = 15; // linux, solarish, bsd-like
pub(crate) const EBUSY: c_int = 16;
pub(crate) const EEXIST: c_int = 17;
pub(crate) const EXDEV: c_int = 18;
pub(crate) const ENODEV: c_int = 19;
pub(crate) const ENOTDIR: c_int = 20;
pub(crate) const EISDIR: c_int = 21;
pub(crate) const EINVAL: c_int = 22;
pub(crate) const ENFILE: c_int = 23;
pub(crate) const EMFILE: c_int = 24;
pub(crate) const ENOTTY: c_int = 25;
// pub(crate) const ETXTBSY: c_int = 26; // linux, solarish, bsd-like
pub(crate) const EFBIG: c_int = 27;
pub(crate) const ENOSPC: c_int = 28;
pub(crate) const ESPIPE: c_int = 29;
pub(crate) const EROFS: c_int = 30;
pub(crate) const EMLINK: c_int = 31;
pub(crate) const EPIPE: c_int = 32;
pub(crate) const EDOM: c_int = 33;
pub(crate) const ERANGE: c_int = 34;
// pub(crate) const EAGAIN: c_int = 35; // bsd-like
// pub(crate) const EWOULDBLOCK: c_int = EAGAIN; // bsd-like
// pub(crate) const EDEADLK: c_int = 36; // windows
// pub(crate) const EDEADLOCK: c_int = EDEADLK; // windows
// pub(crate) const ETXTBSY: c_int = 139; // windows
// pub(crate) const EWOULDBLOCK: c_int = 140; // windows
+73
View File
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::ffi::CStr;
use super::{OpenMode, sys_flen, sys_open, sys_seek};
pub(crate) use super::{sys_remove as unlink, sys_rename as rename};
use crate::{
fd::{BorrowedFd, OwnedFd},
fs, io,
};
pub(crate) struct Metadata {
size: u64,
}
impl Metadata {
#[inline]
pub(crate) fn size(&self) -> u64 {
self.size
}
}
pub(crate) fn metadata(fd: BorrowedFd<'_>) -> io::Result<Metadata> {
Ok(Metadata { size: sys_flen(fd)? as u64 })
}
pub(crate) fn open(path: &CStr, options: &fs::OpenOptions) -> io::Result<OwnedFd> {
match (options.write, options.append) {
(true, false) => {}
(false, false) => {
if options.truncate || options.create {
return Err(io::Error::EINVAL);
}
}
(_, true) => {
if options.truncate {
return Err(io::Error::EINVAL);
}
}
}
// Refs: https://github.com/openocd-org/openocd/blob/HEAD/src/target/semihosting_common.c
let mode = match (options.read, options.write, options.append, options.create, options.truncate)
{
(true, false, false, false, false) => OpenMode::RDONLY_BINARY,
(true, true, false, false, false) => OpenMode::RDWR_BINARY,
(false, true, false, true, true) => OpenMode::WRONLY_TRUNC_BINARY,
(true, true, false, true, true) => OpenMode::RDWR_TRUNC_BINARY,
(false, true, true, true, false) => OpenMode::WRONLY_APPEND_BINARY,
(true, true, true, true, false) => OpenMode::RDWR_APPEND_BINARY,
_ => return Err(io::Error::UNSUPPORTED_PLATFORM),
};
sys_open(path, mode)
}
// TODO(arm_compat): Arm semihosting doesn't provide Large-file support (LFS).
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
pub(crate) fn seek(fd: BorrowedFd<'_>, pos: io::SeekFrom) -> io::Result<u64> {
let abs_pos = match pos {
io::SeekFrom::Start(pos) => pos,
io::SeekFrom::End(offset) => {
let len = sys_flen(fd)? as u64;
let pos = (len as i64).saturating_add(offset);
if pos.is_negative() {
return Err(io::Error::EINVAL);
}
pos as u64
} // io::SeekFrom::Current(_offset) => todo!(),
};
// sys_seek may succeed without this guard, but make the behavior consistent with other platforms.
let abs_pos = isize::try_from(abs_pos).map_err(|_| io::Error::EINVAL)?;
unsafe { sys_seek(fd, abs_pos as usize)? }
Ok(abs_pos as u64)
}
+641
View File
@@ -0,0 +1,641 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Low-level access to semihosting interfaces for Arm (AArch32 and AArch64) and
//! other architectures which support Arm-compatible semihosting interfaces
//! (RISC-V, LoongArch, Xtensa).
//!
//! Refs:
//! - Semihosting for AArch32 and AArch64 <https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst>
//! - RISC-V Semihosting <https://github.com/riscv-non-isa/riscv-semihosting/blob/1.0/riscv-semihosting.adoc>
//! - <https://github.com/qemu/qemu/blob/v10.2.0/semihosting/arm-compat-semi.c>
//! - <https://lore.kernel.org/qemu-devel/20241222-semihosting-v1-1-8a770df60e9c@flygoat.com/>
//! - <https://github.com/espressif/openocd-esp32/blob/HEAD/src/target/espressif/esp_xtensa_semihosting.c>
#![allow(clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)] // TODO
pub(crate) mod errno;
#[cfg(feature = "fs")]
pub(crate) mod fs;
#[cfg(feature = "stdio")]
pub(crate) mod stdio;
pub mod syscall;
use core::{
ffi::{CStr, c_void},
mem::{self, MaybeUninit},
};
use self::syscall::{
OperationNumber, ParamRegR, ParamRegW, syscall, syscall_noreturn_readonly,
syscall_param_unchanged, syscall_param_unchanged_readonly, syscall_readonly,
syscall0_param_unchanged,
};
use crate::{
fd::{BorrowedFd, OwnedFd, RawFd},
io,
utils::slice_assume_init_mut,
};
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(usize)]
#[non_exhaustive]
pub enum ExitReason {
// Reason codes related to hardware exceptions:
ADP_Stopped_BranchThroughZero = 0x20000,
ADP_Stopped_UndefinedInstr = 0x20001,
ADP_Stopped_SoftwareInterrupt = 0x20002,
ADP_Stopped_PrefetchAbort = 0x20003,
ADP_Stopped_DataAbort = 0x20004,
ADP_Stopped_AddressException = 0x20005,
ADP_Stopped_IRQ = 0x20006,
ADP_Stopped_FIQ = 0x20007,
// Reason codes related to software events:
ADP_Stopped_BreakPoint = 0x20020,
ADP_Stopped_WatchPoint = 0x20021,
ADP_Stopped_StepComplete = 0x20022,
ADP_Stopped_RunTimeErrorUnknown = 0x20023,
ADP_Stopped_InternalError = 0x20024,
ADP_Stopped_UserInterruption = 0x20025,
ADP_Stopped_ApplicationExit = 0x20026,
ADP_Stopped_StackOverflow = 0x20027,
ADP_Stopped_DivisionByZero = 0x20028,
ADP_Stopped_OSSpecific = 0x20029,
}
// For `O_*` flags mapping, see https://github.com/openocd-org/openocd/blob/HEAD/src/target/semihosting_common.c
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(usize)]
#[non_exhaustive]
pub enum OpenMode {
/// `fopen` mode: `r`<br>
/// `O_*` flags: `O_RDONLY`
#[doc(alias = "r")]
#[doc(alias = "O_RDONLY")]
RDONLY = 0,
/// `fopen` mode: `rb`<br>
/// `O_*` flags: `O_RDONLY | O_BINARY`
#[doc(alias = "rb")]
#[doc(alias = "O_RDONLY", alias = "O_BINARY")]
RDONLY_BINARY = 1,
/// `fopen` mode: `r+`<br>
/// `O_*` flags: `O_RDWR`
#[doc(alias = "r+")]
#[doc(alias = "O_RDWR")]
RDWR = 2,
/// `fopen` mode: `r+b`<br>
/// `O_*` flags: `O_RDWR | O_BINARY`
#[doc(alias = "r+b")]
#[doc(alias = "O_RDWR", alias = "O_BINARY")]
RDWR_BINARY = 3,
/// `fopen` mode: `w`<br>
/// `O_*` flags: `O_WRONLY | O_CREAT | O_TRUNC`
#[doc(alias = "w")]
#[doc(alias = "O_WRONLY", alias = "O_CREAT", alias = "O_TRUNC")]
WRONLY_TRUNC = 4,
/// `fopen` mode: `wb`<br>
/// `O_*` flags: `O_WRONLY | O_CREAT | O_TRUNC | O_BINARY`
#[doc(alias = "wb")]
#[doc(alias = "O_WRONLY", alias = "O_CREAT", alias = "O_TRUNC", alias = "O_BINARY")]
WRONLY_TRUNC_BINARY = 5,
/// `fopen` mode: `w+`<br>
/// `O_*` flags: `O_RDWR | O_CREAT | O_TRUNC`
#[doc(alias = "w+")]
#[doc(alias = "O_RDWR", alias = "O_CREAT", alias = "O_TRUNC")]
RDWR_TRUNC = 6,
/// `fopen` mode: `w+b`<br>
/// `O_*` flags: `O_RDWR | O_CREAT | O_TRUNC | O_BINARY`
#[doc(alias = "w+b")]
#[doc(alias = "O_RDWR", alias = "O_CREAT", alias = "O_TRUNC", alias = "O_BINARY")]
RDWR_TRUNC_BINARY = 7,
/// `fopen` mode: `a`<br>
/// `O_*` flags: `O_WRONLY | O_CREAT | O_APPEND`
#[doc(alias = "a")]
#[doc(alias = "O_WRONLY", alias = "O_CREAT", alias = "O_APPEND")]
WRONLY_APPEND = 8,
/// `fopen` mode: `ab`<br>
/// `O_*` flags: `O_WRONLY | O_CREAT | O_APPEND | O_BINARY`
#[doc(alias = "ab")]
#[doc(alias = "O_WRONLY", alias = "O_CREAT", alias = "O_APPEND", alias = "O_BINARY")]
WRONLY_APPEND_BINARY = 9,
/// `fopen` mode: `a+`<br>
/// `O_*` flags: `O_RDWR | O_CREAT | O_APPEND`
#[doc(alias = "a+")]
#[doc(alias = "O_RDWR", alias = "O_CREAT", alias = "O_APPEND")]
RDWR_APPEND = 10,
/// `fopen` mode: `a+b`<br>
/// `O_*` flags: `O_RDWR | O_CREAT | O_APPEND | O_BINARY`
#[doc(alias = "a+b`")]
#[doc(alias = "O_RDWR", alias = "O_CREAT", alias = "O_APPEND", alias = "O_BINARY")]
RDWR_APPEND_BINARY = 11,
}
#[allow(missing_docs)]
#[allow(clippy::exhaustive_structs)] // TODO(semver)
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct HeapInfo {
pub heap_base: *mut c_void,
pub heap_limit: *mut c_void,
pub stack_base: *mut c_void,
pub stack_limit: *mut c_void,
}
// TODO(semver): Remove
#[allow(missing_docs)]
#[allow(clippy::exhaustive_structs)]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct CommandLine {
pub ptr: *mut u8,
pub size: usize,
}
#[cold]
fn from_errno() -> io::Error {
io::Error::from_raw_os_error(sys_errno())
}
/// [SYS_CLOCK (0x10)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-clock-0x10)
pub fn sys_clock() -> io::Result<usize> {
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | other | -1 | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
let ret = unsafe { syscall0_param_unchanged(OperationNumber::SYS_CLOCK) };
if ret.signed() == -1 { Err(from_errno()) } else { Ok(ret.unsigned()) }
}
/// [SYS_CLOSE (0x02)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-close-0x02)
/// (Equivalent to [`sys::close`](crate::sys::close))
pub unsafe fn sys_close(fd: RawFd) -> io::Result<()> {
let block = [ParamRegR::raw_fd(fd)];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | -1 | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_CLOSE, ParamRegR::block(&block))
};
if ret.unsigned() == 0 {
Ok(())
} else {
debug_assert_eq!(ret.signed(), -1);
Err(from_errno())
}
}
pub(crate) use self::sys_close as close;
/// [SYS_ELAPSED (0x30)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-elapsed-0x30)
pub fn sys_elapsed() -> io::Result<u64> {
// On 32-bit, the parameter is a pointer to two 32-bit field data block
// On 64-bit, the parameter is a pointer to one 64-bit field data block
let mut block = [0_u64];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | -1 | |
// | PARAMETER REGISTER | - (unchanged) | -1 | |
// | block | updated | - (unmentioned) | |
let ret = unsafe { syscall(OperationNumber::SYS_ELAPSED, ParamRegW::ref_(&mut block)) };
if ret.unsigned() == 0 {
Ok(block[0])
} else {
debug_assert_eq!(ret.signed(), -1);
Err(from_errno())
}
}
/// [SYS_ERRNO (0x13)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-errno-0x13)
pub fn sys_errno() -> io::RawOsError {
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | updated | |
// | PARAMETER REGISTER | - (unmentioned) | param_unchanged |
let ret = unsafe { syscall0_param_unchanged(OperationNumber::SYS_ERRNO) };
ret.errno()
}
/// [SYS_EXIT (0x18)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-exit-0x18)
// TODO(semver): change return type to !?
pub fn sys_exit(reason: ExitReason) {
#[cfg(target_pointer_width = "32")]
let arg = ParamRegR::unsigned(reason as usize);
#[cfg(target_pointer_width = "64")]
let block = [ParamRegR::unsigned(reason as usize), ParamRegR::unsigned(0)];
#[cfg(target_pointer_width = "64")]
let arg = ParamRegR::block(&block);
// > No return is expected from these calls. However, it is possible for the
// > debugger to request that the application continues by performing an
// > RDI_Execute request or equivalent. In this case, execution continues
// > with the registers as they were on entry to the operation, or as
// > subsequently modified by the debugger.
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | ? | |
// | PARAMETER REGISTER | ? | |
// | block | - (unmentioned) | readonly |
unsafe {
syscall_readonly(OperationNumber::SYS_EXIT, arg);
}
}
pub(crate) fn exit(code: i32) -> ! {
let reason = ExitReason::ADP_Stopped_ApplicationExit;
#[allow(clippy::cast_sign_loss)]
let subcode = code as isize as usize;
// On 64-bit system, SYS_EXIT_EXTENDED call is identical to the behavior of the mandatory SYS_EXIT.
#[cfg(target_pointer_width = "64")]
unsafe {
let block = [ParamRegR::unsigned(reason as usize), ParamRegR::unsigned(subcode)];
syscall_noreturn_readonly(OperationNumber::SYS_EXIT, ParamRegR::block(&block))
}
#[cfg(target_pointer_width = "32")]
{
// TODO: check sh_ext_exit_extended first
sys_exit_extended(reason, subcode);
// If SYS_EXIT_EXTENDED is not supported, above call doesn't exit program, so call SYS_EXIT.
let reason = match code {
0 => ExitReason::ADP_Stopped_ApplicationExit,
_ => ExitReason::ADP_Stopped_RunTimeErrorUnknown,
};
unsafe {
syscall_noreturn_readonly(
OperationNumber::SYS_EXIT,
ParamRegR::unsigned(reason as usize),
)
}
}
}
/// [SYS_EXIT_EXTENDED (0x20)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-exit-extended-0x20)
pub fn sys_exit_extended(reason: ExitReason, subcode: usize) {
let block = [ParamRegR::unsigned(reason as usize), ParamRegR::unsigned(subcode)];
#[cfg(target_pointer_width = "32")]
let number = OperationNumber::SYS_EXIT_EXTENDED;
// On 64-bit system, SYS_EXIT_EXTENDED call is identical to the behavior of the mandatory SYS_EXIT.
#[cfg(target_pointer_width = "64")]
let number = OperationNumber::SYS_EXIT;
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | ? | |
// | PARAMETER REGISTER | ? | |
// | block | - (unmentioned) | readonly |
unsafe {
syscall_readonly(number, ParamRegR::block(&block));
}
}
/// [SYS_FLEN (0x0C)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-flen-0x0c)
pub fn sys_flen(fd: BorrowedFd<'_>) -> io::Result<usize> {
let block = [ParamRegR::fd(fd)];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | other | -1 | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_FLEN, ParamRegR::block(&block))
};
if ret.signed() == -1 { Err(from_errno()) } else { Ok(ret.unsigned()) }
}
/// Deprecated: use [`sys_get_cmdline_uninit`] instead
///
/// # Safety
///
/// CommandLine::ptr must be valid for at least the size specified in CommandLine::size.
#[deprecated(note = "use safer [`sys_get_cmdline_uninit`] instead")] // TODO(semver): Remove
pub unsafe fn sys_get_cmdline(cmdline: &mut CommandLine) -> io::Result<()> {
let len = cmdline.size;
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | -1 | |
// | PARAMETER REGISTER | - (unchanged) | - (unmentioned) | param_unchanged |
// | cmdline / ptr | updated | - (unmentioned) | |
let ret = unsafe {
syscall_param_unchanged(OperationNumber::SYS_GET_CMDLINE, ParamRegW::ref_(cmdline))
};
if ret.unsigned() == 0 {
debug_assert!(!cmdline.ptr.is_null());
let size = cmdline.size;
debug_assert!(size < len); // len contains trailing nul
Ok(())
} else {
debug_assert_eq!(ret.signed(), -1);
Err(from_errno())
}
}
/// [SYS_GET_CMDLINE (0x15)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-get-cmdline-0x15)
pub fn sys_get_cmdline_uninit(buf: &mut [MaybeUninit<u8>]) -> io::Result<&mut [u8]> {
let len = buf.len();
let mut block = [ParamRegW::buf(buf), ParamRegW::unsigned(len)];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | -1 | |
// | PARAMETER REGISTER | - (unchanged) | - (unmentioned) | param_unchanged |
// | block / buf | updated | - (unmentioned) | |
let ret = unsafe {
syscall_param_unchanged(OperationNumber::SYS_GET_CMDLINE, ParamRegW::block(&mut block))
};
if ret.unsigned() == 0 {
debug_assert!(!block[0].to_ret().ptr().is_null());
let size = block[1].to_ret().unsigned();
debug_assert!(size < len); // len contains trailing nul
Ok(unsafe { slice_assume_init_mut(&mut buf[..size]) })
} else {
debug_assert_eq!(ret.signed(), -1);
Err(from_errno())
}
}
/// [SYS_HEAPINFO (0x16)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-heapinfo-0x16)
pub fn sys_heapinfo() -> HeapInfo {
let mut buf: HeapInfo = unsafe { mem::zeroed() };
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | ? (unmentioned) | ? (unmentioned) | |
// | PARAMETER REGISTER | - (unchanged) | - (unmentioned) | param_unchanged |
// | buf | updated | - (unmentioned) | |
unsafe {
syscall_param_unchanged(OperationNumber::SYS_HEAPINFO, ParamRegW::ref_(&mut buf));
}
buf
}
/// [SYS_ISERROR (0x08)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-iserror-0x08)
pub fn sys_iserror(res: isize) -> bool {
let block = [ParamRegR::signed(res)];
// | | is not an error | is an error | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | nonzero | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_ISERROR, ParamRegR::block(&block))
};
ret.unsigned() != 0
}
/// [SYS_ISTTY (0x09)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-istty-0x09)
pub fn sys_istty(fd: BorrowedFd<'_>) -> io::Result<bool> {
let block = [ParamRegR::fd(fd)];
// | | is a tty | is a file | on failure | |
// | ------------------ | --------------- | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 1 | 0 | other | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block | - (unmentioned) | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_ISTTY, ParamRegR::block(&block))
};
match ret.unsigned() {
1 => Ok(true),
0 => Ok(false),
_ => Err(from_errno()), // TODO: some host system doesn't set errno
}
}
/// [SYS_OPEN (0x01)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-open-0x01)
pub fn sys_open(path: &CStr, mode: OpenMode) -> io::Result<OwnedFd> {
let block =
[ParamRegR::c_str(path), ParamRegR::unsigned(mode as usize), ParamRegR::c_str_len(path)];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | nonzero | -1 | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block / path | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_OPEN, ParamRegR::block(&block))
};
match ret.raw_fd() {
Some(fd) => {
debug_assert_ne!(ret.unsigned(), 0);
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
None => Err(from_errno()),
}
}
/// Deprecated: use [`sys::read`](crate::sys::read), [`sys::read_uninit`](crate::sys::read_uninit), or [`sys_read_orig`] instead
///
///
/// **Note:** Unlike SYS_READ's original behavior, this returns the number of bytes read.
/// - If you want to continue to use uninitialized buffer, use [`sys::read_uninit`](crate::sys::read_uninit) instead.
/// - If you want the current behavior, use [`sys::read`](crate::sys::read) instead.
/// - If you want the SYS_READ's original behavior, use [`sys_read_orig`] instead.
///
/// This function may be changed to behave as [`sys_read_orig`] in a future breaking release.
#[deprecated(
note = "use [`sys::read`](crate::sys::read), [`sys::read_uninit`](crate::sys::read_uninit), or [`sys_read_orig`] instead"
)] // TODO(semver)
pub fn sys_read(fd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
Ok(read_uninit(fd, buf)?.0.len())
}
/// [SYS_READ (0x06)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-read-0x06)
pub fn sys_read_orig(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result<usize> {
let num_read = read(fd, buf)?;
Ok(buf.len() - num_read)
}
pub(crate) fn read(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result<usize> {
let len = buf.len();
// SAFETY: transmuting initialized `&mut [u8]` to `&mut [MaybeUninit<u8>]` is safe unless uninitialized byte will be written to resulting slice.
let buf =
unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<MaybeUninit<u8>>(), len) };
Ok(read_uninit(fd, buf)?.0.len())
}
pub(crate) fn read_uninit<'a>(
fd: BorrowedFd<'_>,
buf: &'a mut [MaybeUninit<u8>],
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
let len = buf.len();
let mut block = [ParamRegW::fd(fd), ParamRegW::buf(buf), ParamRegW::unsigned(len)];
// | | fully filled | on EOF | partly filled | on failure | |
// | ------------------ | --------------- | --------------- | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | == len | < len | > len || < 0 | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block / buf | updated | - (unmentioned) | updated | ? (unmentioned) | |
let ret =
unsafe { syscall_param_unchanged(OperationNumber::SYS_READ, ParamRegW::block(&mut block)) };
let not_read = ret.unsigned();
if not_read <= len {
let (filled, rest) = buf.split_at_mut(buf.len() - not_read);
Ok((unsafe { slice_assume_init_mut(filled) }, rest))
} else {
debug_assert!(ret.signed().is_negative());
Err(from_errno())
}
}
/// [SYS_READC (0x07)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-readc-0x07)
pub fn sys_readc() -> u8 {
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | updated | |
// | PARAMETER REGISTER | - (unmentioned) | param_unchanged |
let ret = unsafe { syscall0_param_unchanged(OperationNumber::SYS_READC) };
ret.u8()
}
/// [SYS_REMOVE (0x0E)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-remove-0x0e)
/// (Equivalent to [`fs::remove_file`](crate::fs::remove_file))
pub fn sys_remove(path: &CStr) -> io::Result<()> {
let block = [ParamRegR::c_str(path), ParamRegR::c_str_len(path)];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | nonzero | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block / path | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_REMOVE, ParamRegR::block(&block))
};
if ret.unsigned() == 0 { Ok(()) } else { Err(from_errno()) }
}
/// [SYS_RENAME (0x0F)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-rename-0x0f)
/// (Equivalent to [`fs::rename`](crate::fs::rename))
pub fn sys_rename(from: &CStr, to: &CStr) -> io::Result<()> {
let block = [
ParamRegR::c_str(from),
ParamRegR::c_str_len(from),
ParamRegR::c_str(to),
ParamRegR::c_str_len(to),
];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | nonzero | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block / from / to | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_RENAME, ParamRegR::block(&block))
};
if ret.unsigned() == 0 { Ok(()) } else { Err(from_errno()) }
}
// TODO(arm_compat): resolve safety
// > The effect of seeking outside the current extent of the file object is undefined.
/// [SYS_SEEK (0x0A)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-seek-0x0a)
pub unsafe fn sys_seek(fd: BorrowedFd<'_>, abs_pos: usize) -> io::Result<()> {
let block = [ParamRegR::fd(fd), ParamRegR::unsigned(abs_pos)];
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | 0 | negative | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_SEEK, ParamRegR::block(&block))
};
if ret.unsigned() == 0 {
Ok(())
} else {
debug_assert!(ret.signed().is_negative());
Err(from_errno())
}
}
/// [SYS_SYSTEM (0x12)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-system-0x12)
pub fn sys_system(cmd: &CStr) -> usize {
// On exit, the RETURN REGISTER contains the return status.
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | updated | |
// | PARAMETER REGISTER | - (unmentioned) | param_unchanged |
// | block / cmd | - (unmentioned) | readonly |
let block = [ParamRegR::c_str(cmd), ParamRegR::c_str_len(cmd)];
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_SYSTEM, ParamRegR::block(&block))
};
ret.unsigned()
}
/// [SYS_TICKFREQ (0x31)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-tickfreq-0x31)
pub fn sys_tickfreq() -> io::Result<usize> {
// | | on success | on failure | |
// | ------------------ | --------------- | --------------- | --------------- |
// | RETURN REGISTER | other | -1 | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
let ret = unsafe { syscall0_param_unchanged(OperationNumber::SYS_TICKFREQ) };
if ret.signed() == -1 { Err(from_errno()) } else { Ok(ret.unsigned()) }
}
/// [SYS_TIME (0x11)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-time-0x11)
#[allow(clippy::unnecessary_wraps)] // TODO(semver): change in next breaking release
pub fn sys_time() -> io::Result<usize> {
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | updated | |
// | PARAMETER REGISTER | - (unmentioned) | param_unchanged |
let ret = unsafe { syscall0_param_unchanged(OperationNumber::SYS_TIME) };
Ok(ret.unsigned())
}
/// Deprecated: use [`sys::write`](crate::sys::write) or [`sys_write_orig`] instead
///
/// **Note:** Unlike SYS_WRITE's original behavior, this returns the number of bytes written.
/// - If you want the current behavior, use [`sys::write`](crate::sys::write) instead.
/// - If you want the SYS_WRITE's original behavior, use [`sys_write_orig`] instead.
///
/// This function may be changed to behave as [`sys_write_orig`] in a future breaking release.
#[deprecated(note = "use [`sys::write`](crate::sys::write) or [`sys_write_orig`] instead")] // TODO(semver)
pub fn sys_write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
write(fd, buf)
}
/// [SYS_WRITE (0x05)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-write-0x05)
pub fn sys_write_orig(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
let len = buf.len();
let block = [ParamRegR::fd(fd), ParamRegR::buf(buf), ParamRegR::unsigned(len)];
// | | fully filled | partly filled / on failure | |
// | ------------------ | --------------- | -------------------------- | --------------- |
// | RETURN REGISTER | 0 | nonzero | |
// | PARAMETER REGISTER | - (unmentioned) | - (unmentioned) | param_unchanged |
// | block / buf | - (unmentioned) | - (unmentioned) | readonly |
let ret = unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_WRITE, ParamRegR::block(&block))
};
let not_written = ret.unsigned();
#[allow(clippy::comparison_chain)]
if not_written < len {
Ok(not_written)
} else if not_written == len {
if len == 0 {
Ok(not_written)
} else {
// At least on qemu-system-arm 7.2, if fd is a read-only file,
// this is returned instead of an error.
Err(from_errno())
}
} else {
debug_assert!(ret.signed().is_negative());
Err(from_errno())
}
}
pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
let not_written = sys_write_orig(fd, buf)?;
Ok(buf.len() - not_written)
}
/// [SYS_WRITEC (0x03)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-writec-0x03)
pub fn sys_writec(character: u8) {
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | corrupted | |
// | PARAMETER REGISTER | - (unmentioned) | param_unchanged |
// | character | - (unmentioned) | readonly |
unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_WRITEC, ParamRegR::ref_(&character));
}
}
/// [SYS_WRITE0 (0x04)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-write0-0x04)
pub fn sys_write0(string: &CStr) {
// | | always | |
// | ------------------ | --------------- | --------------- |
// | RETURN REGISTER | corrupted | |
// | PARAMETER REGISTER | - (unmentioned) | param_unchanged |
// | string | - (unmentioned) | readonly |
unsafe {
syscall_param_unchanged_readonly(OperationNumber::SYS_WRITE0, ParamRegR::c_str(string));
}
}
+38
View File
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{OpenMode, sys_istty, sys_open};
use crate::{
fd::{BorrowedFd, OwnedFd},
io,
};
// From https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-open-0x01:
// > ARM targets interpret the special path name `:tt` as meaning the console
// > input stream, for an open-read or the console output stream, for an open-write.
// > Opening these streams is performed as part of the standard startup code for
// > those applications that reference the C `stdio` streams.
// And, if the SH_EXT_STDOUT_STDERR semihosting extension is supported:
// > If the special path name `:tt` is opened with an `fopen` mode requesting write access (`w`, `wb`, `w+`, or `w+b`), then this is a request to open `stdout`.
// > If the special path name `:tt` is opened with a mode requesting append access (`a`, `ab`, `a+`, or `a+b`), then this is a request to open `stderr`.
pub(crate) type StdioFd = OwnedFd;
pub(crate) fn stdin() -> io::Result<StdioFd> {
sys_open(c!(":tt"), OpenMode::RDONLY)
}
pub(crate) fn stdout() -> io::Result<StdioFd> {
sys_open(c!(":tt"), OpenMode::WRONLY_TRUNC)
}
pub(crate) fn stderr() -> io::Result<StdioFd> {
// if failed, redirect to stdout
sys_open(c!(":tt"), OpenMode::WRONLY_APPEND).or_else(|_| stdout())
}
#[inline]
pub(crate) fn should_close(_fd: &OwnedFd) -> bool {
// In Arm semihosting, stdio streams are handled like normal fd.
true
}
pub(crate) fn is_terminal(fd: BorrowedFd<'_>) -> bool {
sys_istty(fd).unwrap_or(false)
}
+128
View File
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::arch::asm;
use super::{OperationNumber, ParamRegR, ParamRegW, RetReg};
macro_rules! trap {
() => {
"hlt 0xF000" // .inst 0xD45E0000
};
}
/// Raw semihosting call with a parameter that will be read + modified by the host.
#[inline]
pub unsafe fn syscall(number: OperationNumber, parameter: ParamRegW<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
in("w0") number.0, // OPERATION NUMBER REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// the PARAMETER REGISTER may be changed.
inout("x1") parameter.0 => _, // PARAMETER REGISTER
lateout("x0") ret, // RETURN REGISTER
options(nostack, preserves_flags),
);
}
RetReg(ret)
}
/// Raw semihosting call with a parameter that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall_readonly(number: OperationNumber, parameter: ParamRegR<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
in("w0") number.0, // OPERATION NUMBER REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// the PARAMETER REGISTER may be changed.
inout("x1") parameter.0 => _, // PARAMETER REGISTER
lateout("x0") ret, // RETURN REGISTER
options(nostack, preserves_flags, readonly),
);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged(
number: OperationNumber,
parameter: ParamRegW<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
in("w0") number.0, // OPERATION NUMBER REGISTER
in("x1") parameter.0, // PARAMETER REGISTER
lateout("x0") ret, // RETURN REGISTER
options(nostack, preserves_flags),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
in("w0") number.0, // OPERATION NUMBER REGISTER
inout("x1") parameter.0 => param_new, // PARAMETER REGISTER
lateout("x0") ret, // RETURN REGISTER
options(nostack, preserves_flags),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
in("w0") number.0, // OPERATION NUMBER REGISTER
in("x1") parameter.0, // PARAMETER REGISTER
lateout("x0") ret, // RETURN REGISTER
options(nostack, preserves_flags, readonly),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
in("w0") number.0, // OPERATION NUMBER REGISTER
inout("x1") parameter.0 => param_new, // PARAMETER REGISTER
lateout("x0") ret, // RETURN REGISTER
options(nostack, preserves_flags, readonly),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_noreturn_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> ! {
unsafe {
asm!(
trap!(),
// An infinite loop to prevent the noreturn contract from being violated when a
// semihosting call doesn't work for some reason.
"2:",
"b 2b",
in("w0") number.0, // OPERATION NUMBER REGISTER
in("x1") parameter.0, // PARAMETER REGISTER
options(nostack, noreturn, preserves_flags, readonly),
)
}
}
+180
View File
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::arch::asm;
use super::{OperationNumber, ParamRegR, ParamRegW, RetReg};
// Semihosting Trap Instruction
cfg_sel!({
// M-profile architecture
#[cfg(any(target_feature = "mclass", semihosting_target_feature = "mclass"))]
{
// T32
macro_rules! trap {
() => {
"bkpt 0xAB" // .inst 0xBEAB
};
}
}
// HLT
// https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#the-semihosting-interface
// > This requirement includes supporting the HLT encodings on ARMv7 and earlier processors,
// > even though HLT is only defined as an instruction in ARMv8. This may require the semihosting
// > implementation to trap the UNDEF exception.
// >
// > The HLT encodings are new in version 2.0 of the semihosting specification. Where possible,
// > have semihosting callers continue to use the previously existing trap instructions to ensure
// > compatibility with legacy semihosting implementations. These trap instructions are HLT for A64,
// > SVC on A+R profile A32 or T32, and BKPT on M profile. However, it is necessary to change from
// > SVC to HLT instructions to support AArch32 semihosting properly in a mixed AArch32/AArch64 system.
// >
// > ARM encourages semihosting callers to implement support for trapping using HLT on A32 and T32
// > as a configurable option. ARM strongly discourages semihosting callers from mixing the HLT and
// > SVC mechanisms within the same executable.
#[cfg(feature = "trap-hlt")]
{
// T32
#[cfg(any(target_feature = "thumb-mode", semihosting_target_feature = "thumb-mode"))]
macro_rules! trap {
() => {
".inst 0xBABC" // hlt 0x3C
};
}
// A32
#[cfg(not(any(target_feature = "thumb-mode", semihosting_target_feature = "thumb-mode")))]
macro_rules! trap {
() => {
".inst 0xE10F0070" // hlt 0xF000
};
}
}
// SVC
#[cfg(else)]
{
// T32
#[cfg(any(target_feature = "thumb-mode", semihosting_target_feature = "thumb-mode"))]
macro_rules! trap {
() => {
"svc 0xAB" // .inst 0xDFAB
};
}
// A32
#[cfg(not(any(target_feature = "thumb-mode", semihosting_target_feature = "thumb-mode")))]
macro_rules! trap {
() => {
"svc 0x123456" // .inst 0xEF123456
};
}
}
});
/// Raw semihosting call with a parameter that will be read + modified by the host.
#[inline]
pub unsafe fn syscall(number: OperationNumber, parameter: ParamRegW<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("r0") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("r1") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
RetReg(ret)
}
/// Raw semihosting call with a parameter that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall_readonly(number: OperationNumber, parameter: ParamRegR<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("r0") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("r1") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged(
number: OperationNumber,
parameter: ParamRegW<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("r0") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("r1") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("r0") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("r1") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("r0") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("r1") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("r0") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("r1") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_noreturn_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> ! {
unsafe {
asm!(
trap!(),
// An infinite loop to prevent the noreturn contract from being violated when a
// semihosting call doesn't work for some reason.
"2:",
"b 2b",
in("r0") number.0, // OPERATION NUMBER REGISTER
in("r1") parameter.0, // PARAMETER REGISTER
options(nostack, noreturn, preserves_flags, readonly),
)
}
}
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::arch::asm;
use super::{OperationNumber, ParamRegR, ParamRegW, RetReg};
// TODO: cast in `number.0 as usize` is probably needless per
// https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#the-semihosting-interface
// > The operation number is passed in W0 for the 64-bit ABI, which is the
// > bottom 32 bits of the 64-bit register X0. Semihosting implementations must
// > not assume that the top 32 bits of X0 are 0.
macro_rules! trap {
() => {
"dbcl 0xab"
};
}
/// Raw semihosting call with a parameter that will be read + modified by the host.
#[inline]
pub unsafe fn syscall(number: OperationNumber, parameter: ParamRegW<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("$a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("$a1") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
RetReg(ret)
}
/// Raw semihosting call with a parameter that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall_readonly(number: OperationNumber, parameter: ParamRegR<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("$a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("$a1") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged(
number: OperationNumber,
parameter: ParamRegW<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("$a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("$a1") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("$a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("$a1") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("$a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("$a1") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("$a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("$a1") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_noreturn_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> ! {
unsafe {
asm!(
trap!(),
// An infinite loop to prevent the noreturn contract from being violated when a
// semihosting call doesn't work for some reason.
"2:",
"b 2b",
in("$a0") number.0 as usize, // OPERATION NUMBER REGISTER
in("$a1") parameter.0, // PARAMETER REGISTER
options(nostack, noreturn, preserves_flags, readonly),
)
}
}
+104
View File
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Raw semihosting call.
#[allow(clippy::needless_pass_by_value)]
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
#[cfg_attr(target_arch = "arm", path = "arm.rs")]
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), path = "riscv.rs")]
#[cfg_attr(any(target_arch = "loongarch32", target_arch = "loongarch64"), path = "loongarch.rs")]
#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
mod arch;
pub use self::arch::{syscall, syscall_readonly};
pub(crate) use self::arch::{
syscall_noreturn_readonly, syscall_param_unchanged, syscall_param_unchanged_readonly,
};
pub use crate::sys::reg::{ParamRegR, ParamRegW, RetReg};
/// Semihosting operation number.
///
/// - `0x00-0x31` Used by Arm.
/// - `0x32-0xFF` Reserved for future use by Arm.
/// - `0x100-0x1FF` Reserved for user applications.
/// - `0x200-0xFFFFFFFF` Undefined and currently unused.
#[derive(Debug, Clone, Copy)]
pub struct OperationNumber(u32);
impl OperationNumber {
/// [SYS_OPEN (0x01)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-open-0x01)
pub const SYS_OPEN: Self = Self(0x01);
/// [SYS_CLOSE (0x02)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-close-0x02)
pub const SYS_CLOSE: Self = Self(0x02);
/// [SYS_WRITEC (0x03)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-writec-0x03)
pub const SYS_WRITEC: Self = Self(0x03);
/// [SYS_WRITE0 (0x04)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-write0-0x04)
pub const SYS_WRITE0: Self = Self(0x04);
/// [SYS_WRITE (0x05)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-write-0x05)
pub const SYS_WRITE: Self = Self(0x05);
/// [SYS_READ (0x06)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-read-0x06)
pub const SYS_READ: Self = Self(0x06);
/// [SYS_READC (0x07)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-readc-0x07)
pub const SYS_READC: Self = Self(0x07);
/// [SYS_ISERROR (0x08)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-iserror-0x08)
pub const SYS_ISERROR: Self = Self(0x08);
/// [SYS_ISTTY (0x09)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-istty-0x09)
pub const SYS_ISTTY: Self = Self(0x09);
/// [SYS_SEEK (0x0A)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-seek-0x0a)
pub const SYS_SEEK: Self = Self(0x0A);
/// [SYS_FLEN (0x0C)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-flen-0x0c)
pub const SYS_FLEN: Self = Self(0x0C);
// /// [SYS_TMPNAM (0x0D)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-tmpnam-0x0d)
// #[deprecated = "tmpnam is deprecated as not secure on most host systems"]
// pub const SYS_TMPNAM : Self = Self(0x0D);
/// [SYS_REMOVE (0x0E)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-remove-0x0e)
pub const SYS_REMOVE: Self = Self(0x0E);
/// [SYS_RENAME (0x0F)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-rename-0x0f)
pub const SYS_RENAME: Self = Self(0x0F);
/// [SYS_CLOCK (0x10)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-clock-0x10)
pub const SYS_CLOCK: Self = Self(0x10);
/// [SYS_TIME (0x11)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-time-0x11)
pub const SYS_TIME: Self = Self(0x11);
/// [SYS_SYSTEM (0x12)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-system-0x12)
pub const SYS_SYSTEM: Self = Self(0x12);
/// [SYS_ERRNO (0x13)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-errno-0x13)
pub const SYS_ERRNO: Self = Self(0x13);
/// [SYS_GET_CMDLINE (0x15)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-get_cmdline-0x15)
pub const SYS_GET_CMDLINE: Self = Self(0x15);
/// [SYS_HEAPINFO (0x16)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-heapinfo-0x16)
pub const SYS_HEAPINFO: Self = Self(0x16);
/// [SYS_EXIT (0x18)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-exit-0x18)
#[doc(alias = "angel_SWIreason_ReportException")] // old name
pub const SYS_EXIT: Self = Self(0x18);
/// [SYS_EXIT_EXTENDED (0x20)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-exit_extended-0x20)
pub const SYS_EXIT_EXTENDED: Self = Self(0x20);
/// [SYS_ELAPSED (0x30)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-elapsed-0x30)
pub const SYS_ELAPSED: Self = Self(0x30);
/// [SYS_TICKFREQ (0x31)](https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-tickfreq-0x31)
pub const SYS_TICKFREQ: Self = Self(0x31);
// https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#the-semihosting-interface
// > Previous versions of the semihosting specification included the operation numbers
// > 0x17 (angel_SWIreason_EnterSVC) and 0x19 (angelSWI_Reason_SyncCacheRange).
// > These semihosting operation numbers are now reserved, and are not to be used or implemented.
#[allow(missing_docs)]
#[inline] // inline to help compiler to remove assertion
pub const fn user_defined(number: u32) -> Self {
assert!(number >= 0x100 && number <= 0x1FF);
Self(number)
}
}
/// `syscall_readonly(number, null)`
#[inline]
pub unsafe fn syscall0(number: OperationNumber) -> RetReg {
// In most operations that don't have parameters, such as SYS_ERRNO, and
// SYS_CLOCK, the PARAMETER REGISTER must be zero.
unsafe { syscall_readonly(number, ParamRegR::unsigned(0)) }
}
/// `syscall_param_unchanged_readonly(number, null)`
#[inline]
pub(crate) unsafe fn syscall0_param_unchanged(number: OperationNumber) -> RetReg {
// In most operations that don't have parameters, such as SYS_ERRNO, and
// SYS_CLOCK, the PARAMETER REGISTER must be zero.
unsafe { syscall_param_unchanged_readonly(number, ParamRegR::unsigned(0)) }
}
+134
View File
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::arch::asm;
use super::{OperationNumber, ParamRegR, ParamRegW, RetReg};
// TODO: cast in `number.0 as usize` is probably needless per
// https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#the-semihosting-interface
// > The operation number is passed in W0 for the 64-bit ABI, which is the
// > bottom 32 bits of the 64-bit register X0. Semihosting implementations must
// > not assume that the top 32 bits of X0 are 0.
macro_rules! trap {
() => {
".balign 16
.option push
.option norvc
slli zero, zero, 0x1F
ebreak
srai zero, zero, 0x7
.option pop"
};
}
/// Raw semihosting call with a parameter that will be read + modified by the host.
#[inline]
pub unsafe fn syscall(number: OperationNumber, parameter: ParamRegW<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("a1") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
RetReg(ret)
}
/// Raw semihosting call with a parameter that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall_readonly(number: OperationNumber, parameter: ParamRegR<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("a1") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged(
number: OperationNumber,
parameter: ParamRegW<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("a1") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("a1") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("a1") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("a0") number.0 as usize => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("a1") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_noreturn_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> ! {
unsafe {
asm!(
trap!(),
// An infinite loop to prevent the noreturn contract from being violated when a
// semihosting call doesn't work for some reason.
"2:",
"j 2b",
in("a0") number.0 as usize, // OPERATION NUMBER REGISTER
in("a1") parameter.0, // PARAMETER REGISTER
options(nostack, noreturn, preserves_flags, readonly),
)
}
}
+122
View File
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::arch::asm;
use super::{OperationNumber, ParamRegR, ParamRegW, RetReg};
macro_rules! trap {
() => {
"break 1, 14"
};
}
/// Raw semihosting call with a parameter that will be read + modified by the host.
#[inline]
pub unsafe fn syscall(number: OperationNumber, parameter: ParamRegW<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("a2") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("a3") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
RetReg(ret)
}
/// Raw semihosting call with a parameter that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall_readonly(number: OperationNumber, parameter: ParamRegR<'_>) -> RetReg {
let ret;
unsafe {
asm!(
trap!(),
inout("a2") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
// Use inout because operation such as SYS_ELAPSED suggest that
// PARAMETER REGISTER may be changed.
inout("a3") parameter.0 => _, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged(
number: OperationNumber,
parameter: ParamRegW<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("a2") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("a3") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("a2") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("a3") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_param_unchanged_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> RetReg {
let ret;
#[cfg(not(debug_assertions))]
unsafe {
asm!(
trap!(),
inout("a2") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
in("a3") parameter.0, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
}
#[cfg(debug_assertions)]
unsafe {
let param_new;
asm!(
trap!(),
inout("a2") number.0 => ret, // OPERATION NUMBER REGISTER => RETURN REGISTER
inout("a3") parameter.0 => param_new, // PARAMETER REGISTER
options(nostack, preserves_flags, readonly),
);
assert_eq!(parameter.0, param_new);
}
RetReg(ret)
}
#[inline]
pub(crate) unsafe fn syscall_noreturn_readonly(
number: OperationNumber,
parameter: ParamRegR<'_>,
) -> ! {
unsafe {
asm!(
trap!(),
// An infinite loop to prevent the noreturn contract from being violated when a
// semihosting call doesn't work for some reason.
"2:",
"j 2b",
in("a2") number.0, // OPERATION NUMBER REGISTER
in("a3") parameter.0, // PARAMETER REGISTER
options(nostack, noreturn, preserves_flags, readonly),
)
}
}
+171
View File
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::{cell::Cell, mem::MaybeUninit};
use crate::{io, utils::slice_assume_init_ref};
const NUL: u8 = b'\0';
pub(crate) struct ArgsBytes<const BUF_SIZE: usize> {
// Invariant: self.buf[..self.size] is initialized.
buf: [MaybeUninit<u8>; BUF_SIZE],
next: Cell<usize>,
size: usize,
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
next_fn: for<'a> fn(&'a ArgsBytes<BUF_SIZE>) -> Option<&'a [u8]>,
}
impl<const BUF_SIZE: usize> ArgsBytes<BUF_SIZE> {
const UNINIT_BUF: [MaybeUninit<u8>; BUF_SIZE] = [MaybeUninit::uninit(); BUF_SIZE];
}
pub(crate) fn next_from_cmdline<const BUF_SIZE: usize>(
args: &ArgsBytes<BUF_SIZE>,
) -> Option<&[u8]> {
if args.next.get() >= args.size {
return None;
}
// SAFETY: safe due to buf's invariant.
let buf = unsafe { slice_assume_init_ref(args.buf.get_unchecked(..args.size)) };
let mut start = args.next.get();
let mut end = None;
let is_blank = |b: u8| b == b' ' || b == b'\t';
let mut delim = NUL;
let mut in_argument = false;
while args.next.get() < args.size {
let b = buf[args.next.get()];
if !in_argument {
if is_blank(b) {
end = Some(args.next.get());
args.next.set(args.next.get() + 1);
break;
}
if b == b'"' || b == b'\'' {
delim = b;
start += 1;
}
in_argument = true;
} else if delim != NUL {
if b == delim {
end = Some(args.next.get());
args.next.set(args.next.get() + 2);
break;
}
} else if is_blank(b) {
end = Some(args.next.get());
args.next.set(args.next.get() + 1);
break;
}
args.next.set(args.next.get() + 1);
}
Some(&buf[start..end.unwrap_or_else(|| args.next.get())])
}
#[cfg(any(
test,
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
#[cfg_attr(test, allow(dead_code))] // TODO(env): unit test
fn next_from_args<const BUF_SIZE: usize>(args: &ArgsBytes<BUF_SIZE>) -> Option<&[u8]> {
if args.next.get() >= args.size {
return None;
}
// SAFETY: safe due to buf's invariant.
let buf = unsafe { slice_assume_init_ref(args.buf.get_unchecked(..args.size)) };
let start = args.next.get();
let mut end = None;
while args.next.get() < args.size {
let b = buf[args.next.get()];
if b == NUL {
end = Some(args.next.get());
args.next.set(args.next.get() + 1);
break;
}
args.next.set(args.next.get() + 1);
}
let end = end.unwrap_or_else(|| args.next.get());
let last = end.saturating_sub(1);
if start != last {
let start_b = buf[start];
let last_b = buf[last];
if start_b == b'"' && last_b == b'"' || start_b == b'\'' && last_b == b'\'' {
return Some(&buf[start + 1..last]);
}
}
Some(&buf[start..end])
}
cfg_sel!({
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
{
pub(crate) use self::next_from_cmdline as next;
use crate::sys::arm_compat::sys_get_cmdline_uninit;
pub(crate) fn args_bytes<const BUF_SIZE: usize>() -> io::Result<ArgsBytes<BUF_SIZE>> {
let mut buf = ArgsBytes::<BUF_SIZE>::UNINIT_BUF;
let size = sys_get_cmdline_uninit(&mut buf)?.len();
Ok(ArgsBytes { buf, next: Cell::new(0), size })
}
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
{
use crate::sys::mips::{mips_argc, mips_argn, mips_argnlen};
pub(crate) fn args_bytes<const BUF_SIZE: usize>() -> io::Result<ArgsBytes<BUF_SIZE>> {
let mut buf = ArgsBytes::<BUF_SIZE>::UNINIT_BUF;
let argc = mips_argc();
let mut start = 0;
for i in 0..argc {
let len = mips_argnlen(i)? + 1;
if start + len > BUF_SIZE {
return Err(io::ErrorKind::ArgumentListTooLong.into());
}
// SAFETY: pointer is valid because we got it from a reference,
// and we've checked that the buffer has enough size.
unsafe { mips_argn(i, buf.as_mut_ptr().add(start).cast::<u8>())? }
start += len;
}
Ok(ArgsBytes {
buf,
next: Cell::new(0),
size: start,
next_fn: if argc == 1 { next_from_cmdline } else { next_from_args },
})
}
#[inline]
pub(crate) fn next<const BUF_SIZE: usize>(args: &ArgsBytes<BUF_SIZE>) -> Option<&[u8]> {
(args.next_fn)(args)
}
}
#[cfg(else)]
{
pub(crate) fn args_bytes<const BUF_SIZE: usize>() -> io::Result<ArgsBytes<BUF_SIZE>> {
Err(io::ErrorKind::Unsupported.into())
}
pub(crate) fn next<const BUF_SIZE: usize>(_args: &ArgsBytes<BUF_SIZE>) -> Option<&[u8]> {
unreachable!()
}
}
});
+151
View File
@@ -0,0 +1,151 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::ffi::c_int;
use crate::{
io::{self, RawOsError},
sys::arch::errno,
};
#[inline]
pub(crate) fn is_interrupted(errno: RawOsError) -> bool {
errno as c_int == errno::EINTR
}
// Adapted from https://github.com/rust-lang/rust/blob/1.92.0/library/std/src/sys/pal/unix/mod.rs#L235.
pub(crate) fn decode_error_kind(errno: RawOsError) -> io::ErrorKind {
#[allow(clippy::enum_glob_use)]
use io::ErrorKind::*;
match errno as c_int {
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
)))]
errno::E2BIG => ArgumentListTooLong,
// errno::EADDRINUSE => AddrInUse,
// errno::EADDRNOTAVAIL => AddrNotAvailable,
errno::EBUSY => ResourceBusy,
// errno::ECONNABORTED => ConnectionAborted,
// errno::ECONNREFUSED => ConnectionRefused,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ECONNRESET => ConnectionReset,
// errno::EDEADLK => Deadlock,
// errno::EDQUOT => QuotaExceeded,
errno::EEXIST => AlreadyExists,
errno::EFBIG => FileTooLarge,
// errno::EHOSTUNREACH => HostUnreachable,
errno::EINTR => Interrupted,
errno::EINVAL => InvalidInput,
errno::EISDIR => IsADirectory,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ELOOP => __FilesystemLoop,
errno::ENOENT => NotFound,
errno::ENOMEM => OutOfMemory,
errno::ENOSPC => StorageFull,
// errno::ENOSYS => Unsupported,
errno::EMLINK => TooManyLinks,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ENAMETOOLONG => InvalidFilename,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ENETDOWN => NetworkDown,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ENETUNREACH => NetworkUnreachable,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ENOTCONN => NotConnected,
errno::ENOTDIR => NotADirectory,
// errno::ENOTEMPTY => DirectoryNotEmpty,
errno::EPIPE => BrokenPipe,
errno::EROFS => ReadOnlyFilesystem,
errno::ESPIPE => NotSeekable,
// errno::ESTALE => StaleNetworkFileHandle,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ETIMEDOUT => TimedOut,
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
errno::ETXTBSY => ExecutableFileBusy,
errno::EXDEV => CrossesDevices,
// errno::EINPROGRESS => __InProgress,
// errno::EOPNOTSUPP => Unsupported,
errno::EACCES | errno::EPERM => PermissionDenied,
// These two constants can have the same value on some systems,
// but different values on others, so we can't use a match
// clause
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))]
x if x == errno::EAGAIN || x == errno::EWOULDBLOCK => WouldBlock,
_ => Other,
}
}
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Values are defined in the appendix of Reference Manual.
// TODO(sys): should we expose this as public API of `sys` module?
#![allow(dead_code)]
use core::ffi::c_int;
pub(crate) const EPERM: c_int = 1; // link, unlink
pub(crate) const ENOENT: c_int = 2; // link, unlink, open
pub(crate) const EINTR: c_int = 4; // open, close, read, write
pub(crate) const EIO: c_int = 5; // pwrite, open, close, fstat, pread, read, write
pub(crate) const ENXIO: c_int = 6; // open, pread, pwrite, read, write
pub(crate) const EBADF: c_int = 9; // pwrite, pread, close, lseek, fstat, read, write
pub(crate) const EAGAIN: c_int = 11; // pread, pwrite, read, write
pub(crate) const EWOULDBLOCK: c_int = 11; // pread, pwrite
pub(crate) const ENOMEM: c_int = 12; // open, pread, read
pub(crate) const EACCES: c_int = 13; // pwrite, link, unlink, open, write
pub(crate) const EBUSY: c_int = 16; // unlink,
pub(crate) const EEXIST: c_int = 17; // link, open,
pub(crate) const EXDEV: c_int = 18; // link
pub(crate) const ENOTDIR: c_int = 20; // link, unlink, open
pub(crate) const EISDIR: c_int = 21; // open, pread, read
pub(crate) const EINVAL: c_int = 22; // lseek, pread, pwrite, open, read
pub(crate) const ENFILE: c_int = 23; // open
pub(crate) const EMFILE: c_int = 24; // open
pub(crate) const ETXTBSY: c_int = 26; // unlink, open
pub(crate) const EFBIG: c_int = 27; // pwrite, read, write
pub(crate) const ENOSPC: c_int = 28; // pwrite, link, open, write
pub(crate) const ESPIPE: c_int = 29; // lseek, pread, pwrite, read
pub(crate) const EROFS: c_int = 30; // link, unlink, open
pub(crate) const EMLINK: c_int = 31; // link,
pub(crate) const EPIPE: c_int = 32; // pwrite, write, write
pub(crate) const ERANGE: c_int = 34; // pwrite, write
pub(crate) const ENOSR: c_int = 63; // open
pub(crate) const EBADMSG: c_int = 77; // pread, read
pub(crate) const ENAMETOOLONG: c_int = 91; // link, unlink, open
pub(crate) const ELOOP: c_int = 92; // open, link, unlink
pub(crate) const ECONNRESET: c_int = 104; // pread, pwrite, read
pub(crate) const ENOBUFS: c_int = 105; // pread, pwrite, read, write
pub(crate) const ENETUNREACH: c_int = 114; // pwrite, write
pub(crate) const ENETDOWN: c_int = 115; // pwrite, write
pub(crate) const ETIMEDOUT: c_int = 116; // pread, read
pub(crate) const ENOTCONN: c_int = 128; // pread, read
pub(crate) const EOVERFLOW: c_int = 139; // open, lseek, fstat, pread, read
+77
View File
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::ffi::CStr;
use super::{
O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, SeekWhence::SEEK_SET,
mips_fstat, mips_lseek, mips_open,
};
pub(crate) use super::{mips_fstat as metadata, mips_unlink as unlink, uhi_stat as Metadata};
use crate::{
fd::{BorrowedFd, OwnedFd},
fs, io,
};
impl Metadata {
#[inline]
pub(crate) fn size(&self) -> u64 {
self.st_size
}
}
#[allow(clippy::cast_possible_wrap)]
pub(crate) fn open(path: &CStr, options: &fs::OpenOptions) -> io::Result<OwnedFd> {
match (options.write, options.append) {
(true, false) => {}
(false, false) => {
if options.truncate || options.create {
return Err(io::Error::EINVAL);
}
}
(_, true) => {
if options.truncate {
return Err(io::Error::EINVAL);
}
}
}
let access_mode = match (options.read, options.write, options.append) {
(true, false, false) => O_RDONLY,
(false, true, false) => O_WRONLY,
(true, true, false) => O_RDWR,
(false, _, true) => O_WRONLY | O_APPEND,
(true, _, true) => O_RDWR | O_APPEND,
(false, false, false) => return Err(io::Error::EINVAL),
};
let creation_mode = match (options.create, options.truncate, options.create_new) {
(false, false, false) => 0,
(true, false, false) => O_CREAT,
(false, true, false) => O_TRUNC,
(true, true, false) => O_CREAT | O_TRUNC,
(_, _, true) => O_CREAT | O_EXCL,
};
mips_open(path, access_mode | creation_mode, options.mode as i32)
}
// TODO(mips): UHI doesn't provide Large-file support (LFS).
#[allow(clippy::cast_possible_wrap)]
pub(crate) fn seek(fd: BorrowedFd<'_>, pos: io::SeekFrom) -> io::Result<u64> {
let (whence, offset) = match pos {
io::SeekFrom::Start(pos) => (SEEK_SET, pos as i64),
io::SeekFrom::End(offset) => {
// (SEEK_END, offset) doesn't reject invalid offset
let len = mips_fstat(fd)?.size();
let pos = (len as i64).saturating_add(offset);
if pos.is_negative() {
return Err(io::Error::EINVAL);
}
(SEEK_SET, pos)
} // io::SeekFrom::Current(offset) => (SEEK_CUR, offset),
};
// mips_lseek will fail even without this guard, but errno will not be set.
let offset = isize::try_from(offset).map_err(|_| io::Error::EINVAL)?;
Ok(unsafe { mips_lseek(fd, offset, whence)? as u64 })
}
pub(crate) fn rename(_from: &CStr, _to: &CStr) -> io::Result<()> {
Err(io::ErrorKind::Unsupported.into())
}
+335
View File
@@ -0,0 +1,335 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Low-level access to MIPS Unified Hosting Interface (UHI).
//!
//! Refs:
//! - Unified Hosting Interface. MD01069 Reference Manual
//! - <https://github.com/qemu/qemu/blob/v10.2.0/target/mips/tcg/system/mips-semi.c>
#![allow(clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)] // TODO
pub(crate) mod errno;
#[cfg(feature = "fs")]
pub(crate) mod fs;
#[cfg(feature = "stdio")]
pub(crate) mod stdio;
pub mod syscall;
use core::{ffi::CStr, mem, mem::MaybeUninit};
use self::syscall::{
OperationCode, ParamRegR, ParamRegW, RetReg, syscall0, syscall1_noreturn_readonly,
syscall1_readonly, syscall2, syscall2_readonly, syscall3, syscall3_readonly, syscall4,
syscall4_readonly,
};
use crate::{
fd::{BorrowedFd, OwnedFd, RawFd},
io,
utils::slice_assume_init_mut,
};
#[allow(missing_docs)]
mod consts {
pub const O_RDONLY: i32 = 0x0;
pub const O_WRONLY: i32 = 0x1;
pub const O_RDWR: i32 = 0x2;
pub const O_APPEND: i32 = 0x8;
pub const O_CREAT: i32 = 0x200;
pub const O_TRUNC: i32 = 0x400;
pub const O_EXCL: i32 = 0x800;
pub const S_IXOTH: i32 = 0o1;
pub const S_IWOTH: i32 = 0o2;
pub const S_IROTH: i32 = 0o4;
pub const S_IRWXO: i32 = 0o7;
pub const S_IXGRP: i32 = 0o10;
pub const S_IWGRP: i32 = 0o20;
pub const S_IRGRP: i32 = 0o40;
pub const S_IRWXG: i32 = 0o70;
pub const S_IXUSR: i32 = 0o100;
pub const S_IWUSR: i32 = 0o200;
pub const S_IRUSR: i32 = 0o400;
pub const S_IRWXU: i32 = 0o700;
}
pub use self::consts::*;
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(i32)]
#[non_exhaustive]
pub enum SeekWhence {
// Unified Hosting Interface. MD01069 Reference Manual says SEEK_* are defined as follows,
// but QEMU (as of 10.2) uses Linux's SEEK_* number.
// SEEK_SET = 0x0001,
// SEEK_CUR = 0x0002,
// SEEK_END = 0x0004,
SEEK_SET = 0,
SEEK_CUR = 1,
SEEK_END = 2,
}
#[allow(missing_docs)]
#[allow(clippy::exhaustive_structs)] // TODO(semver)
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct uhi_stat {
pub st_dev: i16,
pub st_ino: u16,
pub st_mode: u32,
pub st_nlink: u16,
pub st_uid: u16,
pub st_gid: u16,
pub st_rdev: i16,
pub st_size: u64,
pub st_atime: u64,
pub st_spare1: u64,
pub st_mtime: u64,
pub st_spare2: u64,
pub st_ctime: u64,
pub st_spare3: u64,
pub st_blksize: u64,
pub st_blocks: u64,
pub st_spare4: [u64; 2],
}
#[cold]
fn from_errno(res: RetReg) -> io::Error {
io::Error::from_raw_os_error(res.errno())
}
/// UHI_exit
// TODO(semver): change return type to !?
pub fn mips_exit(code: i32) {
#[allow(clippy::cast_sign_loss)]
unsafe {
syscall1_readonly(OperationCode::UHI_EXIT, ParamRegR::unsigned(code as isize as usize));
}
}
pub(crate) fn exit(code: i32) -> ! {
#[allow(clippy::cast_sign_loss)]
unsafe {
syscall1_noreturn_readonly(
OperationCode::UHI_EXIT,
ParamRegR::unsigned(code as isize as usize),
)
}
}
/// UHI_open
pub fn mips_open(path: &CStr, flags: i32, mode: i32) -> io::Result<OwnedFd> {
#[allow(clippy::cast_sign_loss)]
let (res, errno) = unsafe {
syscall3_readonly(
OperationCode::UHI_OPEN,
ParamRegR::c_str(path),
ParamRegR::unsigned(flags as usize),
ParamRegR::unsigned(mode as usize),
)
};
match res.raw_fd() {
Some(fd) => Ok(unsafe { OwnedFd::from_raw_fd(fd) }),
None => Err(from_errno(errno)),
}
}
/// UHI_close
/// (Equivalent to [`sys::close`](crate::sys::close))
pub unsafe fn mips_close(fd: RawFd) -> io::Result<()> {
let (res, errno) =
unsafe { syscall1_readonly(OperationCode::UHI_CLOSE, ParamRegR::raw_fd(fd)) };
if res.unsigned() == 0 {
Ok(())
} else {
debug_assert_eq!(res.signed(), -1);
Err(from_errno(errno))
}
}
pub(crate) use self::mips_close as close;
/// UHI_read
/// (Equivalent to [`sys::read`](crate::sys::read))
pub fn mips_read(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result<usize> {
let len = buf.len();
// SAFETY: transmuting initialized `&mut [u8]` to `&mut [MaybeUninit<u8>]` is safe unless uninitialized byte will be written to resulting slice.
let buf =
unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<MaybeUninit<u8>>(), len) };
Ok(read_uninit(fd, buf)?.0.len())
}
pub(crate) use self::mips_read as read;
pub(crate) fn read_uninit<'a>(
fd: BorrowedFd<'_>,
buf: &'a mut [MaybeUninit<u8>],
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
let len = buf.len();
let (res, errno) = unsafe {
syscall3(
OperationCode::UHI_READ,
ParamRegW::fd(fd),
ParamRegW::buf(buf),
ParamRegW::unsigned(len),
)
};
if res.signed() == -1 {
Err(from_errno(errno))
} else {
debug_assert!(res.unsigned() <= buf.len());
let (filled, rest) = buf.split_at_mut(res.unsigned());
Ok((unsafe { slice_assume_init_mut(filled) }, rest))
}
}
/// UHI_write
/// (Equivalent to [`sys::write`](crate::sys::write))
pub fn mips_write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
let (res, errno) = unsafe {
syscall3_readonly(
OperationCode::UHI_WRITE,
ParamRegR::fd(fd),
ParamRegR::buf(buf),
ParamRegR::unsigned(buf.len()),
)
};
if res.signed() == -1 {
Err(from_errno(errno))
} else {
debug_assert!(res.unsigned() <= buf.len());
Ok(res.unsigned())
}
}
pub(crate) use self::mips_write as write;
/// UHI_lseek
pub unsafe fn mips_lseek(
fd: BorrowedFd<'_>,
offset: isize,
whence: SeekWhence,
) -> io::Result<usize> {
let (res, errno) = unsafe {
syscall3_readonly(
OperationCode::UHI_LSEEK,
ParamRegR::fd(fd),
ParamRegR::signed(offset),
ParamRegR::unsigned(whence as usize),
)
};
if res.signed() == -1 { Err(from_errno(errno)) } else { Ok(res.unsigned()) }
}
/// UHI_unlink
/// (Equivalent to [`fs::remove_file`](crate::fs::remove_file))
pub fn mips_unlink(path: &CStr) -> io::Result<()> {
let (res, errno) =
unsafe { syscall1_readonly(OperationCode::UHI_UNLINK, ParamRegR::c_str(path)) };
if res.unsigned() == 0 { Ok(()) } else { Err(from_errno(errno)) }
}
/// UHI_fstat
pub fn mips_fstat(fd: BorrowedFd<'_>) -> io::Result<uhi_stat> {
let mut buf: uhi_stat = unsafe { mem::zeroed() };
let (res, errno) =
unsafe { syscall2(OperationCode::UHI_FSTAT, ParamRegW::fd(fd), ParamRegW::ref_(&mut buf)) };
if res.unsigned() == 0 { Ok(buf) } else { Err(from_errno(errno)) }
}
/// UHI_argc
pub fn mips_argc() -> usize {
let (res, _errno) = unsafe { syscall0(OperationCode::UHI_ARGC) };
debug_assert!(!res.signed().is_negative(), "{}", res.signed());
res.unsigned()
}
/// UHI_argnlen
pub fn mips_argnlen(n: usize) -> io::Result<usize> {
let (res, errno) =
unsafe { syscall1_readonly(OperationCode::UHI_ARGNLEN, ParamRegR::unsigned(n)) };
if res.signed() == -1 { Err(from_errno(errno)) } else { Ok(res.unsigned()) }
}
/// UHI_argn
pub unsafe fn mips_argn(n: usize, buf: *mut u8) -> io::Result<()> {
let (res, errno) =
unsafe { syscall2(OperationCode::UHI_ARGN, ParamRegW::unsigned(n), ParamRegW::ptr(buf)) };
if res.unsigned() == 0 {
Ok(())
} else {
debug_assert_eq!(res.signed(), -1);
Err(from_errno(errno))
}
}
/// UHI_plog
pub fn mips_plog(msg: &CStr) -> io::Result<usize> {
let (res, errno) = unsafe { syscall1_readonly(OperationCode::UHI_PLOG, ParamRegR::c_str(msg)) };
if res.signed() == -1 {
Err(from_errno(errno))
} else {
debug_assert_eq!(res.unsigned(), msg.to_bytes().len());
Ok(res.unsigned())
}
}
// TODO(mips): UHI_ASSERT
/// UHI_pread
pub fn mips_pread(fd: BorrowedFd<'_>, buf: &mut [u8], offset: usize) -> io::Result<usize> {
let len = buf.len();
// SAFETY: transmuting initialized `&mut [u8]` to `&mut [MaybeUninit<u8>]` is safe unless uninitialized byte will be written to resulting slice.
let buf =
unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<MaybeUninit<u8>>(), len) };
Ok(mips_pread_uninit(fd, buf, offset)?.0.len())
}
fn mips_pread_uninit<'a>(
fd: BorrowedFd<'_>,
buf: &'a mut [MaybeUninit<u8>],
offset: usize,
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
let len = buf.len();
let (res, errno) = unsafe {
syscall4(
OperationCode::UHI_PREAD,
ParamRegW::fd(fd),
ParamRegW::buf(buf),
ParamRegW::unsigned(len),
ParamRegW::unsigned(offset),
)
};
if res.signed() == -1 {
Err(from_errno(errno))
} else {
debug_assert!(res.unsigned() <= buf.len());
let (filled, rest) = buf.split_at_mut(res.unsigned());
Ok((unsafe { slice_assume_init_mut(filled) }, rest))
}
}
/// UHI_pwrite
pub fn mips_pwrite(fd: BorrowedFd<'_>, buf: &[u8], offset: usize) -> io::Result<usize> {
let (res, errno) = unsafe {
syscall4_readonly(
OperationCode::UHI_PWRITE,
ParamRegR::fd(fd),
ParamRegR::buf(buf),
ParamRegR::unsigned(buf.len()),
ParamRegR::unsigned(offset),
)
};
if res.signed() == -1 {
Err(from_errno(errno))
} else {
debug_assert!(res.unsigned() <= buf.len());
Ok(res.unsigned())
}
}
/// UHI_link
pub fn mips_link(old: &CStr, new: &CStr) -> io::Result<()> {
let (res, errno) = unsafe {
syscall2_readonly(OperationCode::UHI_LINK, ParamRegR::c_str(old), ParamRegR::c_str(new))
};
if res.unsigned() == 0 {
Ok(())
} else {
debug_assert_eq!(res.signed(), -1);
Err(from_errno(errno))
}
}
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::ffi::c_uint;
use super::mips_fstat;
use crate::{
fd::{BorrowedFd, OwnedFd, RawFd},
io,
};
const STDIN_FILENO: RawFd = 0; // /dev/stdin
const STDOUT_FILENO: RawFd = 1; // /dev/stdout
const STDERR_FILENO: RawFd = 2; // /dev/stderr
pub(crate) type StdioFd = BorrowedFd<'static>;
pub(crate) fn stdin() -> io::Result<StdioFd> {
Ok(unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) })
}
pub(crate) fn stdout() -> io::Result<StdioFd> {
Ok(unsafe { BorrowedFd::borrow_raw(STDOUT_FILENO) })
}
pub(crate) fn stderr() -> io::Result<StdioFd> {
Ok(unsafe { BorrowedFd::borrow_raw(STDERR_FILENO) })
}
#[inline]
#[allow(clippy::cast_sign_loss)]
pub(crate) fn should_close(fd: &OwnedFd) -> bool {
// In UHI, stdio streams are open by default, and shouldn't closed.
fd.as_raw_fd() as c_uint > STDERR_FILENO as c_uint
}
pub(crate) fn is_terminal(fd: BorrowedFd<'_>) -> bool {
const S_IFCHR: u32 = 0x2000;
matches!(mips_fstat(fd), Ok(stat) if stat.st_mode & S_IFCHR != 0)
}
+265
View File
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::arch::asm;
use super::{OperationCode, ParamRegR, ParamRegW, RetReg};
macro_rules! trap {
() => {
"sdbbp 1"
};
}
/// Raw semihosting call without parameters.
#[inline]
pub unsafe fn syscall0(op: OperationCode) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
out("$4") _,
out("$5") _,
in("$25") op.0,
options(nostack, readonly),
);
}
(RetReg(r1), RetReg(r2))
}
/// Raw semihosting call with 1 parameter that will be read + modified by the host.
#[inline]
pub unsafe fn syscall1(op: OperationCode, arg1: ParamRegW<'_>) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
out("$5") _,
in("$25") op.0,
options(nostack),
);
}
(RetReg(r1), RetReg(r2))
}
/// Raw semihosting call with 1 parameter that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall1_readonly(op: OperationCode, arg1: ParamRegR<'_>) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
out("$5") _,
in("$25") op.0,
options(nostack, readonly),
);
}
(RetReg(r1), RetReg(r2))
}
#[inline]
pub(crate) unsafe fn syscall1_noreturn_readonly(op: OperationCode, arg1: ParamRegR<'_>) -> ! {
unsafe {
asm!(
".set push",
".set noat",
trap!(),
// An infinite loop to prevent the noreturn contract from being violated when a
// semihosting call doesn't work for some reason.
"2:",
"b 2b",
".set pop",
in("$2") 1_usize,
in("$4") arg1.0,
in("$25") op.0,
options(nostack, noreturn, preserves_flags, readonly),
)
}
}
/// Raw semihosting call with 2 parameters that will be read + modified by the host.
#[inline]
pub unsafe fn syscall2(
op: OperationCode,
arg1: ParamRegW<'_>,
arg2: ParamRegW<'_>,
) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
inout("$5") arg2.0 => _,
in("$25") op.0,
options(nostack),
);
}
(RetReg(r1), RetReg(r2))
}
/// Raw semihosting call with 2 parameters that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall2_readonly(
op: OperationCode,
arg1: ParamRegR<'_>,
arg2: ParamRegR<'_>,
) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
inout("$5") arg2.0 => _,
in("$25") op.0,
options(nostack, readonly),
);
}
(RetReg(r1), RetReg(r2))
}
/// Raw semihosting call with 3 parameters that will be read + modified by the host.
#[inline]
pub unsafe fn syscall3(
op: OperationCode,
arg1: ParamRegW<'_>,
arg2: ParamRegW<'_>,
arg3: ParamRegW<'_>,
) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
inout("$5") arg2.0 => _,
in("$6") arg3.0,
in("$25") op.0,
options(nostack),
);
(RetReg(r1), RetReg(r2))
}
}
/// Raw semihosting call with 3 parameters that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall3_readonly(
op: OperationCode,
arg1: ParamRegR<'_>,
arg2: ParamRegR<'_>,
arg3: ParamRegR<'_>,
) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
inout("$5") arg2.0 => _,
in("$6") arg3.0,
in("$25") op.0,
options(nostack, readonly),
);
}
(RetReg(r1), RetReg(r2))
}
/// Raw semihosting call with 4 parameters that will be read + modified by the host.
#[inline]
pub unsafe fn syscall4(
op: OperationCode,
arg1: ParamRegW<'_>,
arg2: ParamRegW<'_>,
arg3: ParamRegW<'_>,
arg4: ParamRegW<'_>,
) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
inout("$5") arg2.0 => _,
in("$6") arg3.0,
in("$7") arg4.0,
in("$25") op.0,
options(nostack),
);
}
(RetReg(r1), RetReg(r2))
}
/// Raw semihosting call with 4 parameters that will be read (but not modified) by the host.
#[inline]
pub unsafe fn syscall4_readonly(
op: OperationCode,
arg1: ParamRegR<'_>,
arg2: ParamRegR<'_>,
arg3: ParamRegR<'_>,
arg4: ParamRegR<'_>,
) -> (RetReg, RetReg) {
let r1;
let r2;
unsafe {
asm!(
".set push",
".set noat",
trap!(),
".set pop",
inout("$2") 1_usize => r1,
out("$3") r2,
inout("$4") arg1.0 => _,
inout("$5") arg2.0 => _,
in("$6") arg3.0,
in("$7") arg4.0,
in("$25") op.0,
options(nostack, readonly),
);
}
(RetReg(r1), RetReg(r2))
}
+49
View File
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Raw semihosting call.
#[allow(clippy::needless_pass_by_value)]
#[cfg_attr(
any(
doc,
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
),
path = "mips.rs"
)]
mod arch;
pub(crate) use self::arch::syscall1_noreturn_readonly;
pub use self::arch::{
syscall0, syscall1, syscall1_readonly, syscall2, syscall2_readonly, syscall3,
syscall3_readonly, syscall4, syscall4_readonly,
};
pub use crate::sys::reg::{ParamRegR, ParamRegW, RetReg};
/// Semihosting operation code.
#[derive(Debug, Clone, Copy)]
pub struct OperationCode(usize);
#[allow(missing_docs)]
impl OperationCode {
pub const UHI_EXIT: Self = Self(1);
pub const UHI_OPEN: Self = Self(2);
pub const UHI_CLOSE: Self = Self(3);
pub const UHI_READ: Self = Self(4);
pub const UHI_WRITE: Self = Self(5);
pub const UHI_LSEEK: Self = Self(6);
pub const UHI_UNLINK: Self = Self(7);
pub const UHI_FSTAT: Self = Self(8);
pub const UHI_ARGC: Self = Self(9);
pub const UHI_ARGNLEN: Self = Self(10);
pub const UHI_ARGN: Self = Self(11);
// pub const UHI_RAMRANGE: Self = Self(12); // QEMU (as of 10.2) doesn't support this
pub const UHI_PLOG: Self = Self(13);
// pub const UHI_ASSERT: Self = Self(14); // TODO(mips)
// pub const UHI_EXCEPTION: Self = Self(15); // QEMU (as of 10.2) doesn't support this
pub const UHI_PREAD: Self = Self(19); // QEMU (as of 10.2) doesn't support this
pub const UHI_PWRITE: Self = Self(20); // QEMU (as of 10.2) doesn't support this
pub const UHI_LINK: Self = Self(22); // QEMU (as of 10.2) doesn't support this
// pub const UHI_BOOT_FAIL: Self = Self(23); // QEMU (as of 10.2) doesn't support this
}
+189
View File
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Low-level access to platform-specific semihosting interfaces.
//!
//! - At this (`semihosting::sys`) module level, wrappers around common semihosting interfaces
//! across multiple semihosting interfaces are provided. Its functionality usually corresponds to the POSIX function of the same name, but is memory-safe and I/O-safe.
//! - Note that we do not provide correspondents for POSIX functions that already have high-level APIs doing exactly the same thing:
//! [`exit`](crate::process::exit), [`unlink`](crate::fs::remove_file), [`rename`](crate::fs::rename), [`isatty`](crate::io::IsTerminal::is_terminal)
//! - At the following platform-specific module level, thin wrappers around more platform-specific operations,
//! including raw semihosting calls are provided. These usually just call the corresponding
//! semihosting calls, but is memory-safe and I/O-safe.
//! - `arm_compat`: AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting)
//! - `mips`: MIPS32, MIPS64
#![allow(
non_camel_case_types,
non_upper_case_globals,
clippy::unnecessary_wraps,
clippy::upper_case_acronyms
)]
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
use self::arm_compat as arch;
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
)))
)]
pub mod arm_compat;
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
use self::mips as arch;
#[cfg(any(
all(doc, docsrs),
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
)))
)]
pub mod mips;
#[cfg(feature = "args")]
pub(crate) mod env;
mod errno;
#[cfg(feature = "random")]
pub(crate) mod random;
mod reg;
#[cfg(feature = "time")]
pub(crate) mod time;
use core::mem::MaybeUninit;
#[cfg(feature = "stdio")]
pub(crate) use self::arch::stdio;
#[cfg(feature = "fs")]
pub(crate) use self::arch::{errno::EINVAL, fs};
pub(crate) use self::{
arch::exit,
errno::{decode_error_kind, is_interrupted},
};
use crate::{
fd::{BorrowedFd, RawFd},
io,
};
/// Closes the specified file descriptor.
///
/// Note that [`OwnedFd`](crate::fd::OwnedFd) closes its file descriptor automatically on drop,
/// so this is unnecessary for most users.
///
/// # Safety
///
/// See [I/O Safety documentation](https://doc.rust-lang.org/std/io/index.html#io-safety).
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_CLOSE] |
/// | MIPS32, MIPS64 | UHI_close |
///
/// [SYS_CLOSE]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-close-0x02
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "SYS_CLOSE")] // arm_compat
#[doc(alias = "UHI_close")] // mips
pub unsafe fn close(fd: RawFd) -> io::Result<()> {
// SAFETY: the caller must uphold the safety contract.
unsafe { arch::close(fd) }
}
/// Reads to the specified buffer, returning how many bytes were read.
///
/// See also [`read_uninit`].
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_READ] |
/// | MIPS32, MIPS64 | UHI_read |
///
/// [SYS_READ]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-read-0x06
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "SYS_READ")] // arm_compat
#[doc(alias = "UHI_read")] // mips
pub fn read(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result<usize> {
arch::read(fd, buf)
}
/// Reads to the specified buffer, returning the content that was read and the
/// remaining part of the buffer.
///
/// See also [`read`].
///
/// # Platform-specific behavior
///
/// See [`read`'s documentation](read#platform-specific-behavior).
#[doc(alias = "SYS_READ")] // arm_compat
#[doc(alias = "UHI_read")] // mips
pub fn read_uninit<'a>(
fd: BorrowedFd<'_>,
buf: &'a mut [MaybeUninit<u8>],
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
arch::read_uninit(fd, buf)
}
/// Writes from the specified buffer, returning how many bytes were written.
///
/// # Platform-specific behavior
///
/// The following semihosting calls are currently being used:
///
/// | Platform | Semihosting call |
/// | ------------------------------------------------------------- | ---------------- |
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_WRITE] |
/// | MIPS32, MIPS64 | UHI_write |
///
/// [SYS_WRITE]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-write-0x05
///
/// **Disclaimer:** These semihosting calls might change over time.
#[doc(alias = "SYS_WRITE")] // arm_compat
#[doc(alias = "UHI_write")] // mips
pub fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
arch::write(fd, buf)
}
+121
View File
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Refs: https://github.com/rust-lang/rust/blob/1.92.0/library/std/src/sys/random/unix_legacy.rs
use core::mem::{self, MaybeUninit};
use crate::{
fd::{BorrowedFd, OwnedFd},
fs, io, sys,
utils::slice_assume_init_mut,
};
#[cfg_attr(any(target_has_atomic = "32", feature = "portable-atomic"), cold)]
fn init() -> io::Result<OwnedFd> {
Ok(fs::File::open(c!("/dev/urandom"))?.into())
}
cfg_sel!({
#[cfg(any(target_has_atomic = "32", feature = "portable-atomic"))]
{
use self::once::OnceOwnedFd;
static DEVICE: OnceOwnedFd = OnceOwnedFd::none();
pub(crate) fn fill_bytes(bytes: &mut [MaybeUninit<u8>]) -> io::Result<&mut [u8]> {
let fd = DEVICE.get_or_try_init(init)?;
read_exact_uninit(fd, bytes)
}
}
#[cfg(else)]
{
use crate::fd::AsFd as _;
pub(crate) fn fill_bytes(bytes: &mut [MaybeUninit<u8>]) -> io::Result<&mut [u8]> {
let fd: OwnedFd = init()?;
read_exact_uninit(fd.as_fd(), bytes)
}
}
});
fn read_exact_uninit<'a>(
fd: BorrowedFd<'_>,
buf: &'a mut [MaybeUninit<u8>],
) -> io::Result<&'a mut [u8]> {
fn inner(fd: BorrowedFd<'_>, mut buf: &mut [MaybeUninit<u8>]) -> io::Result<()> {
while !buf.is_empty() {
match sys::read_uninit(fd, mem::take(&mut buf)) {
Ok((&mut [], rest)) => {
buf = rest;
break;
}
Ok((_, rest)) => {
buf = rest;
}
Err(ref e) if e.is_interrupted() => {}
Err(e) => return Err(e),
}
}
if buf.is_empty() { Ok(()) } else { Err(io::Error::READ_EXACT_EOF) }
}
inner(fd, buf)?;
// SAFETY: we've filled all bytes
Ok(unsafe { slice_assume_init_mut(buf) })
}
#[cfg(any(target_has_atomic = "32", feature = "portable-atomic"))]
mod once {
use crate::{
atomic::{AtomicI32, Ordering},
fd::{BorrowedFd, OwnedFd},
io,
};
const INIT: i32 = -1;
#[repr(transparent)]
pub(super) struct OnceOwnedFd(AtomicI32);
impl OnceOwnedFd {
pub(super) const fn none() -> Self {
Self(AtomicI32::new(INIT))
}
#[inline]
fn get(&self) -> Option<BorrowedFd<'_>> {
let fd = self.0.load(Ordering::Acquire);
// SAFETY: we set non -1 value only from OwnedFd and close it only from Drop.
if fd == INIT { None } else { Some(unsafe { BorrowedFd::borrow_raw(fd) }) }
}
#[inline]
pub(super) fn get_or_try_init(
&self,
f: impl FnOnce() -> io::Result<OwnedFd>,
) -> io::Result<BorrowedFd<'_>> {
if let Some(fd) = self.get() {
return Ok(fd);
}
self.try_init(f)
}
#[cold]
fn try_init(&self, f: impl FnOnce() -> io::Result<OwnedFd>) -> io::Result<BorrowedFd<'_>> {
let fd = f()?;
if let Some(fd) = self.get() {
return Ok(fd);
}
let fd = fd.into_raw_fd();
match self.0.compare_exchange(INIT, fd, Ordering::Release, Ordering::Acquire) {
// SAFETY: we set non -1 value only from OwnedFd and close it only from Drop.
Ok(_) => Ok(unsafe { BorrowedFd::borrow_raw(fd) }),
Err(new_fd) => {
// SAFETY: fd is from OwnedFd and will never referred from others since CAS failed.
drop(unsafe { OwnedFd::from_raw_fd(fd) });
// SAFETY: we set non -1 value only from OwnedFd and close it only from Drop.
Ok(unsafe { BorrowedFd::borrow_raw(new_fd) })
}
}
}
}
impl Drop for OnceOwnedFd {
fn drop(&mut self) {
let fd = *self.0.get_mut();
if fd != INIT {
// SAFETY: we set non -1 value only from OwnedFd and close it only from Drop.
drop(unsafe { OwnedFd::from_raw_fd(fd) });
}
}
}
}
+222
View File
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![allow(missing_docs)]
use core::{
ffi::{CStr, c_int, c_void},
marker::PhantomData,
};
#[cfg(semihosting_no_strict_provenance)]
use crate::utils::ptr::PtrExt as _;
use crate::{
fd::{BorrowedFd, RawFd},
io::RawOsError,
};
/// PARAMETER REGISTER (read-write)
#[allow(missing_debug_implementations)]
#[repr(transparent)]
pub struct ParamRegW<'a>(pub(crate) *mut c_void, PhantomData<&'a mut ()>);
impl<'a> ParamRegW<'a> {
#[inline]
pub fn fd(fd: BorrowedFd<'a>) -> Self {
Self::raw_fd(fd.as_raw_fd())
}
#[inline]
pub fn raw_fd(fd: RawFd) -> Self {
Self::signed(fd as isize)
}
#[inline]
pub(crate) fn unsigned(n: usize) -> Self {
Self(crate::utils::ptr::without_provenance_mut(n), PhantomData)
}
#[allow(clippy::cast_sign_loss)]
#[inline]
pub(crate) fn signed(n: isize) -> Self {
Self::unsigned(n as usize)
}
#[inline]
pub fn usize(n: usize) -> Self {
// TODO(semver): use without_provenance_mut
Self(crate::utils::ptr::with_exposed_provenance_mut(n), PhantomData)
}
#[allow(clippy::cast_sign_loss)]
#[inline]
pub fn isize(n: isize) -> Self {
Self::usize(n as usize)
}
#[inline]
pub fn ptr<T>(ptr: *mut T) -> Self {
Self(ptr.cast::<c_void>(), PhantomData)
}
#[inline]
pub fn ref_<T>(r: &'a mut T) -> Self {
Self::ptr(r)
}
#[inline]
pub fn buf<T>(buf: &'a mut [T]) -> Self {
Self::ptr(buf.as_mut_ptr())
}
}
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
impl<'a> ParamRegW<'a> {
#[inline]
pub fn block(b: &'a mut [ParamRegW<'_>]) -> Self {
Self::ptr(b.as_mut_ptr())
}
#[inline]
pub(crate) fn to_ret(&self) -> RetReg {
RetReg(self.0)
}
}
/// PARAMETER REGISTER (read-only)
#[allow(missing_debug_implementations)]
#[repr(transparent)]
pub struct ParamRegR<'a>(pub(crate) *const c_void, PhantomData<&'a ()>);
impl<'a> ParamRegR<'a> {
#[inline]
pub fn fd(fd: BorrowedFd<'a>) -> Self {
Self::raw_fd(fd.as_raw_fd())
}
#[inline]
pub fn raw_fd(fd: RawFd) -> Self {
Self::signed(fd as isize)
}
#[inline]
pub(crate) fn unsigned(n: usize) -> Self {
Self(crate::utils::ptr::without_provenance_mut(n), PhantomData)
}
#[allow(clippy::cast_sign_loss)]
#[inline]
pub(crate) fn signed(n: isize) -> Self {
Self::unsigned(n as usize)
}
#[inline]
pub fn usize(n: usize) -> Self {
// TODO(semver): without_provenance_mut
Self(crate::utils::ptr::with_exposed_provenance(n), PhantomData)
}
#[allow(clippy::cast_sign_loss)]
#[inline]
pub fn isize(n: isize) -> Self {
Self::usize(n as usize)
}
#[inline]
pub fn ptr<T>(ptr: *const T) -> Self {
Self(ptr.cast::<c_void>(), PhantomData)
}
#[inline]
pub fn ref_<T>(r: &'a T) -> Self {
Self::ptr(r)
}
#[inline]
pub fn buf<T>(buf: &'a [T]) -> Self {
Self::ptr(buf.as_ptr())
}
#[inline]
pub fn c_str(s: &'a CStr) -> Self {
Self::ptr(s.as_ptr())
}
}
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
impl<'a> ParamRegR<'a> {
#[inline]
pub fn c_str_len(s: &CStr) -> Self {
Self::unsigned(s.to_bytes().len())
}
#[inline]
pub fn block(b: &'a [ParamRegR<'_>]) -> Self {
Self::ptr(b.as_ptr())
}
}
/// RETURN REGISTER
#[derive(Clone, Copy)]
#[allow(missing_debug_implementations)]
#[repr(transparent)]
pub struct RetReg(pub(crate) *mut c_void);
impl RetReg {
#[inline(always)]
pub(crate) fn unsigned(self) -> usize {
self.0.addr()
}
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
#[inline(always)]
pub(crate) fn signed(self) -> isize {
self.unsigned() as isize
}
#[inline]
pub fn usize(self) -> usize {
self.0 as usize
}
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
#[inline]
fn isize(self) -> isize {
self.usize() as isize
}
#[allow(clippy::cast_possible_truncation)]
#[inline]
pub fn int(self) -> c_int {
self.isize() as c_int
}
#[inline]
pub fn raw_fd(self) -> Option<RawFd> {
#[allow(clippy::cast_possible_truncation)]
let fd = self.signed() as RawFd;
if fd == -1 {
None
} else {
debug_assert!(!fd.is_negative(), "{}", fd);
debug_assert_eq!(fd as isize, self.signed());
Some(fd)
}
}
#[inline]
pub fn errno(self) -> RawOsError {
#[allow(clippy::cast_possible_truncation)]
let err = self.signed() as RawOsError;
debug_assert!(!err.is_negative(), "{}", err);
debug_assert_eq!(err as isize, self.signed());
err
}
}
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
impl RetReg {
#[inline]
pub fn u8(self) -> u8 {
#[allow(clippy::cast_possible_truncation)]
let b = self.unsigned() as u8;
debug_assert_eq!(b as usize, self.unsigned());
b
}
#[inline(always)]
pub(crate) fn ptr(self) -> *mut c_void {
self.0
}
}
+219
View File
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss)]
// Based on https://github.com/rust-lang/rust/blob/1.92.0/library/std/src/sys/pal/unix/time.rs.
use core::{fmt, time::Duration};
use crate::io;
const NSEC_PER_SEC: u64 = 1_000_000_000;
pub(crate) const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
struct Nanoseconds(u32);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct SystemTime {
t: Timespec,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Timespec {
tv_sec: i64,
tv_nsec: Nanoseconds,
}
impl SystemTime {
pub(crate) fn sub_time(&self, other: &Self) -> Result<Duration, Duration> {
self.t.sub_timespec(&other.t)
}
pub(crate) fn checked_add_duration(&self, other: &Duration) -> Option<Self> {
Some(Self { t: self.t.checked_add_duration(other)? })
}
pub(crate) fn checked_sub_duration(&self, other: &Duration) -> Option<Self> {
Some(Self { t: self.t.checked_sub_duration(other)? })
}
}
impl fmt::Debug for SystemTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemTime")
.field("tv_sec", &self.t.tv_sec)
.field("tv_nsec", &self.t.tv_nsec.0)
.finish()
}
}
impl Timespec {
const fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Self {
Self { tv_sec, tv_nsec: Nanoseconds(tv_nsec as u32) }
}
const fn zero() -> Timespec {
Self::new_unchecked(0, 0)
}
fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
// When a >= b, the difference fits in u64.
fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
debug_assert!(a >= b);
a.wrapping_sub(b) as u64
}
if self >= other {
let (secs, nsec) = if self.tv_nsec.0 >= other.tv_nsec.0 {
(sub_ge_to_unsigned(self.tv_sec, other.tv_sec), self.tv_nsec.0 - other.tv_nsec.0)
} else {
// Following sequence of assertions explain why `self.tv_sec - 1` does not underflow.
debug_assert!(self.tv_nsec < other.tv_nsec);
debug_assert!(self.tv_sec > other.tv_sec);
debug_assert!(self.tv_sec > i64::MIN);
(
sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec),
self.tv_nsec.0 + (NSEC_PER_SEC as u32) - other.tv_nsec.0,
)
};
Ok(Duration::new(secs, nsec))
} else {
match other.sub_timespec(self) {
Ok(d) => Err(d),
Err(d) => Ok(d),
}
}
}
pub(crate) fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
// i*::checked_add_unsigned requires Rust 1.66
#[inline]
fn checked_add_unsigned(this: i64, rhs: u64) -> Option<i64> {
let (a, b) = {
let rhs = rhs as i64;
let (res, overflowed) = this.overflowing_add(rhs);
(res, overflowed ^ (rhs < 0))
};
if b { None } else { Some(a) }
}
let mut secs = checked_add_unsigned(self.tv_sec, other.as_secs())?;
// Nano calculations can't overflow because nanos are <1B which fit
// in a u32.
let mut nsec = other.subsec_nanos() + self.tv_nsec.0;
if nsec >= NSEC_PER_SEC as u32 {
nsec -= NSEC_PER_SEC as u32;
secs = secs.checked_add(1)?;
}
Some(Timespec::new_unchecked(secs, nsec.into()))
}
pub(crate) fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
// i*::checked_sub_unsigned requires Rust 1.66
#[inline]
fn checked_sub_unsigned(this: i64, rhs: u64) -> Option<i64> {
let (a, b) = {
let rhs = rhs as i64;
let (res, overflowed) = this.overflowing_sub(rhs);
(res, overflowed ^ (rhs < 0))
};
if b { None } else { Some(a) }
}
let mut secs = checked_sub_unsigned(self.tv_sec, other.as_secs())?;
// Similar to above, nanos can't overflow.
let mut nsec = self.tv_nsec.0 as i32 - other.subsec_nanos() as i32;
if nsec < 0 {
nsec += NSEC_PER_SEC as i32;
secs = secs.checked_sub(1)?;
}
Some(Timespec::new_unchecked(secs, nsec.into()))
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct Instant {
t: Timespec,
}
impl Instant {
pub(crate) fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> {
self.t.sub_timespec(&other.t).ok()
}
pub(crate) fn checked_add_duration(&self, other: &Duration) -> Option<Instant> {
Some(Instant { t: self.t.checked_add_duration(other)? })
}
pub(crate) fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> {
Some(Instant { t: self.t.checked_sub_duration(other)? })
}
}
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Instant")
.field("tv_sec", &self.t.tv_sec)
.field("tv_nsec", &self.t.tv_nsec.0)
.finish()
}
}
cfg_sel!({
#[cfg(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch32",
target_arch = "loongarch64",
all(target_arch = "xtensa", feature = "openocd-semihosting"),
))]
{
use crate::sys::arm_compat::{sys_clock, sys_time};
const CENTISECONDS_PER_SEC: u64 = 100;
const NANOS_PER_CENTI: u32 = 10_000_000;
impl SystemTime {
pub(crate) fn now() -> io::Result<Self> {
// SYS_TIME doesn't have Y2038 problem (although it still has Y2106 problem): https://github.com/ARM-software/abi-aa/commit/d281283bf3dcec4d4ebf9e5646020d77904904e1
let tv_sec = sys_time()? as u64 as i64;
// SYS_TIME returns seconds, so tv_nsec is always zero.
let tv_nsec = Nanoseconds(0);
Ok(Self { t: Timespec { tv_sec, tv_nsec } })
}
}
impl Instant {
pub(crate) fn now() -> io::Result<Self> {
// SYS_CLOCK returns centiseconds (hundredths of a second).
// Conversion is based on Duration::from_millis.
let centiseconds = sys_clock()? as u64;
let secs = centiseconds / CENTISECONDS_PER_SEC;
let subsec_centiseconds = (centiseconds % CENTISECONDS_PER_SEC) as u32;
// SAFETY: (x % 10_000_000) * 100 < 1_000_000_000
// => x % 10_000_000 < 10_000_000
let subsec_nanos = Nanoseconds(subsec_centiseconds * NANOS_PER_CENTI);
Ok(Self { t: Timespec { tv_sec: secs as i64, tv_nsec: subsec_nanos } })
}
}
}
#[cfg(else)]
{
impl SystemTime {
pub(crate) fn now() -> io::Result<Self> {
Err(io::ErrorKind::Unsupported.into())
}
}
impl Instant {
pub(crate) fn now() -> io::Result<Self> {
Err(io::ErrorKind::Unsupported.into())
}
}
}
});
+143
View File
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use core::mem::MaybeUninit;
// rustfmt-compatible cfg_select/cfg_if alternative
// Note: This macro is cfg_sel!({ }), not cfg_sel! { }.
// An extra brace is used in input to make contents rustfmt-able.
macro_rules! cfg_sel {
({#[cfg(else)] { $($output:tt)* }}) => {
$($output)*
};
({
#[cfg($cfg:meta)]
{ $($output:tt)* }
$($( $rest:tt )+)?
}) => {
#[cfg($cfg)]
cfg_sel! {{#[cfg(else)] { $($output)* }}}
$(
#[cfg(not($cfg))]
cfg_sel! {{ $($rest)+ }}
)?
};
}
// <[_]>::assume_init_ref requires Rust 1.93.
/// # Safety
///
/// Calling this when the content is not yet fully initialized causes undefined
/// behavior: it is up to the caller to guarantee that every `MaybeUninit<T>` in
/// the slice really is in an initialized state.
#[allow(dead_code)]
#[inline(always)]
pub(crate) const unsafe fn slice_assume_init_ref<T>(s: &[MaybeUninit<T>]) -> &[T] {
// SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that
// `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`.
// The pointer obtained is valid since it refers to memory owned by `slice` which is a
// reference and thus guaranteed to be valid for reads.
unsafe { &*(s as *const [MaybeUninit<T>] as *const [T]) }
}
// <[_]>::assume_init_mut requires Rust 1.93.
/// # Safety
///
/// Calling this when the content is not yet fully initialized causes undefined
/// behavior: it is up to the caller to guarantee that every `MaybeUninit<T>` in the
/// slice really is in an initialized state. For instance, `.assume_init_mut()` cannot
/// be used to initialize a `MaybeUninit` slice.
#[inline(always)]
pub(crate) unsafe fn slice_assume_init_mut<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
// SAFETY: similar to safety notes for `slice_get_ref`, but we have a
// mutable reference which is also guaranteed to be valid for writes.
unsafe { &mut *(s as *mut [MaybeUninit<T>] as *mut [T]) }
}
// This module provides core::ptr strict_provenance/exposed_provenance polyfill for pre-1.84 rustc.
pub(crate) mod ptr {
cfg_sel!({
#[cfg(not(semihosting_no_strict_provenance))]
{
pub(crate) use core::ptr::{
with_exposed_provenance, with_exposed_provenance_mut, without_provenance_mut,
};
}
#[cfg(else)]
{
#[inline(always)]
#[must_use]
pub(crate) const fn without_provenance_mut<T>(addr: usize) -> *mut T {
// An int-to-pointer transmute currently has exactly the intended semantics: it creates a
// pointer without provenance. Note that this is *not* a stable guarantee about transmute
// semantics, it relies on sysroot crates having special status.
// SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that
// pointer).
#[cfg(miri)]
unsafe {
core::mem::transmute(addr)
}
// Using transmute doesn't work with CHERI: https://github.com/kent-weak-memory/rust/blob/0c0ca909de877f889629057e1ddf139527446d75/library/core/src/ptr/mod.rs#L607
#[cfg(not(miri))]
{
addr as *mut T
}
}
#[inline(always)]
#[must_use]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub(crate) fn with_exposed_provenance<T>(addr: usize) -> *const T {
addr as *const T
}
#[inline(always)]
#[must_use]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub(crate) fn with_exposed_provenance_mut<T>(addr: usize) -> *mut T {
addr as *mut T
}
pub(crate) trait PtrExt<T: ?Sized>: Copy {
#[must_use]
fn addr(self) -> usize;
}
impl<T: ?Sized> PtrExt<T> for *const T {
#[inline(always)]
#[must_use]
fn addr(self) -> usize {
// A pointer-to-integer transmute currently has exactly the right semantics: it returns the
// address without exposing the provenance. Note that this is *not* a stable guarantee about
// transmute semantics, it relies on sysroot crates having special status.
// SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the
// provenance).
#[cfg(miri)]
unsafe {
core::mem::transmute(self.cast::<()>())
}
// Using transmute doesn't work with CHERI: https://github.com/kent-weak-memory/rust/blob/0c0ca909de877f889629057e1ddf139527446d75/library/core/src/ptr/mut_ptr.rs#L210
#[cfg(not(miri))]
{
self.cast::<()>() as usize
}
}
}
impl<T: ?Sized> PtrExt<T> for *mut T {
#[inline(always)]
#[must_use]
fn addr(self) -> usize {
// A pointer-to-integer transmute currently has exactly the right semantics: it returns the
// address without exposing the provenance. Note that this is *not* a stable guarantee about
// transmute semantics, it relies on sysroot crates having special status.
// SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the
// provenance).
#[cfg(miri)]
unsafe {
core::mem::transmute(self.cast::<()>())
}
// Using transmute doesn't work with CHERI: https://github.com/kent-weak-memory/rust/blob/0c0ca909de877f889629057e1ddf139527446d75/library/core/src/ptr/mut_ptr.rs#L210
#[cfg(not(miri))]
{
self.cast::<()>() as usize
}
}
}
}
});
}
+102
View File
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
use std::{env, iter, process::Command, str};
pub(crate) fn rustc_version() -> Option<Version> {
let rustc = env::var_os("RUSTC")?;
let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|v| !v.is_empty());
// Do not apply RUSTC_WORKSPACE_WRAPPER: https://github.com/cuviper/autocfg/issues/58#issuecomment-2067625980
let mut rustc = rustc_wrapper.into_iter().chain(iter::once(rustc));
let mut cmd = Command::new(rustc.next().unwrap());
cmd.args(rustc);
// Use verbose version output because the packagers add extra strings to the normal version output.
// Do not use long flags (--version --verbose) because clippy-deriver doesn't handle them properly.
// -vV is also matched with that cargo internally uses: https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/rustc.rs#L65
let output = cmd.arg("-vV").output().ok()?;
let verbose_version = str::from_utf8(&output.stdout).ok()?;
Version::parse(verbose_version)
}
pub(crate) struct Version {
pub(crate) minor: u32,
pub(crate) nightly: bool,
commit_date: Date,
}
impl Version {
// The known latest stable version. If we unable to determine
// the rustc version, we assume this is the current version.
// It is no problem if this is older than the actual latest stable.
pub(crate) const LATEST: Self = Self::stable(94);
const fn stable(minor: u32) -> Self {
Self { minor, nightly: false, commit_date: Date::UNKNOWN }
}
pub(crate) fn probe(&self, minor: u32, year: u16, month: u8, day: u8) -> bool {
if self.nightly {
self.minor > minor
|| self.minor == minor && self.commit_date >= Date::new(year, month, day)
} else {
self.minor >= minor
}
}
pub(crate) fn parse(verbose_version: &str) -> Option<Self> {
let mut release = verbose_version
.lines()
.find(|line| line.starts_with("release: "))
.map(|line| &line["release: ".len()..])?
.splitn(2, '-');
let version = release.next().unwrap();
let channel = release.next().unwrap_or_default();
let mut digits = version.splitn(3, '.');
let major = digits.next()?;
if major != "1" {
return None;
}
let minor = digits.next()?.parse::<u32>().ok()?;
let _patch = digits.next().unwrap_or("0").parse::<u32>().ok()?;
let nightly = match env::var_os("RUSTC_BOOTSTRAP") {
// When -1 is passed rustc works like stable, e.g., cfg(target_feature = "unstable_target_feature") will never be set. https://github.com/rust-lang/rust/pull/132993
Some(v) if v == "-1" => false,
_ => channel == "nightly" || channel == "dev",
};
// we don't refer commit date on stable/beta.
if nightly {
let commit_date = (|| {
let mut commit_date = verbose_version
.lines()
.find(|line| line.starts_with("commit-date: "))
.map(|line| &line["commit-date: ".len()..])?
.splitn(3, '-');
let year = commit_date.next()?.parse::<u16>().ok()?;
let month = commit_date.next()?.parse::<u8>().ok()?;
let day = commit_date.next()?.parse::<u8>().ok()?;
if month > 12 || day > 31 {
return None;
}
Some(Date::new(year, month, day))
})();
Some(Self { minor, nightly, commit_date: commit_date.unwrap_or(Date::UNKNOWN) })
} else {
Some(Self::stable(minor))
}
}
}
#[derive(PartialEq, PartialOrd)]
struct Date {
year: u16,
month: u8,
day: u8,
}
impl Date {
const UNKNOWN: Self = Self::new(0, 0, 0);
const fn new(year: u16, month: u8, day: u8) -> Self {
Self { year, month, day }
}
}