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:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -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
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "075a26b55575aff93d46eb99ff4939a101fdda62"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
Vendored
+191
@@ -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
@@ -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",
|
||||
]
|
||||
Vendored
+221
@@ -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"))',
|
||||
]
|
||||
Vendored
+177
@@ -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
|
||||
Vendored
+23
@@ -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.
|
||||
Vendored
+251
@@ -0,0 +1,251 @@
|
||||
# semihosting
|
||||
|
||||
[](https://crates.io/crates/semihosting)
|
||||
[](https://docs.rs/semihosting)
|
||||
[](#license)
|
||||
[](https://www.rust-lang.org)
|
||||
[](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.
|
||||
Vendored
+153
@@ -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}\"");
|
||||
}
|
||||
}
|
||||
Vendored
+126
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
Vendored
+223
@@ -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)
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
Vendored
+277
@@ -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()
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -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",
|
||||
];
|
||||
Vendored
+327
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+499
@@ -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(())
|
||||
}
|
||||
}
|
||||
Vendored
+242
@@ -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),
|
||||
}
|
||||
Vendored
+139
@@ -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);
|
||||
Vendored
+391
@@ -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;
|
||||
}
|
||||
Vendored
+70
@@ -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)),+,)
|
||||
};
|
||||
}
|
||||
Vendored
+54
@@ -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)
|
||||
}
|
||||
Vendored
+132
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)) }
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
Vendored
+171
@@ -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!()
|
||||
}
|
||||
}
|
||||
});
|
||||
Vendored
+151
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
Vendored
+189
@@ -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)
|
||||
}
|
||||
Vendored
+121
@@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+222
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
+219
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Vendored
+143
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Vendored
+102
@@ -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 }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user