From 87b48c3665eb96480e1164cba2fdd41b91767d23 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Tue, 29 Dec 2020 17:53:46 +0800 Subject: [PATCH] initial commit Signed-off-by: Sean Cross --- .gitattributes | 18 + .gitignore | 1 + Cargo.lock | 133 ++ Cargo.toml | 10 + macros/Cargo.lock | 98 ++ macros/Cargo.toml | 29 + macros/src/lib.rs | 137 ++ src/main.rs | 47 + xous-rs/Cargo.toml | 28 + xous-rs/assemble.ps1 | 18 + xous-rs/assemble.sh | 22 + xous-rs/bin/riscv32i-unknown-none-elf.a | Bin 0 -> 2250 bytes xous-rs/bin/riscv32imac-unknown-none-elf.a | Bin 0 -> 2266 bytes xous-rs/bin/riscv32imac-unknown-xous-elf.a | Bin 0 -> 2278 bytes xous-rs/bin/riscv32imc-unknown-none-elf.a | Bin 0 -> 2266 bytes xous-rs/bin/riscv64gc-unknown-none-elf.a | Bin 0 -> 3342 bytes xous-rs/bin/riscv64imac-unknown-none-elf.a | Bin 0 -> 3342 bytes xous-rs/build.rs | 35 + xous-rs/src/arch/hosted.rs | 683 ++++++++++ xous-rs/src/arch/hosted/mem.rs | 41 + xous-rs/src/arch/mod.rs | 9 + xous-rs/src/arch/native.rs | 218 ++++ xous-rs/src/arch/native/mem.rs | 32 + xous-rs/src/asm.S | 20 + xous-rs/src/carton.rs | 98 ++ xous-rs/src/definitions.rs | 649 ++++++++++ xous-rs/src/ipc.rs | 269 ++++ xous-rs/src/lib.rs | 163 +++ xous-rs/src/messages/mod.rs | 0 xous-rs/src/string.rs | 108 ++ xous-rs/src/syscall.rs | 1352 ++++++++++++++++++++ 31 files changed, 4218 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 macros/Cargo.lock create mode 100644 macros/Cargo.toml create mode 100644 macros/src/lib.rs create mode 100644 src/main.rs create mode 100644 xous-rs/Cargo.toml create mode 100644 xous-rs/assemble.ps1 create mode 100644 xous-rs/assemble.sh create mode 100644 xous-rs/bin/riscv32i-unknown-none-elf.a create mode 100644 xous-rs/bin/riscv32imac-unknown-none-elf.a create mode 100644 xous-rs/bin/riscv32imac-unknown-xous-elf.a create mode 100644 xous-rs/bin/riscv32imc-unknown-none-elf.a create mode 100644 xous-rs/bin/riscv64gc-unknown-none-elf.a create mode 100644 xous-rs/bin/riscv64imac-unknown-none-elf.a create mode 100644 xous-rs/build.rs create mode 100644 xous-rs/src/arch/hosted.rs create mode 100644 xous-rs/src/arch/hosted/mem.rs create mode 100644 xous-rs/src/arch/mod.rs create mode 100644 xous-rs/src/arch/native.rs create mode 100644 xous-rs/src/arch/native/mem.rs create mode 100644 xous-rs/src/asm.S create mode 100644 xous-rs/src/carton.rs create mode 100644 xous-rs/src/definitions.rs create mode 100644 xous-rs/src/ipc.rs create mode 100644 xous-rs/src/lib.rs create mode 100644 xous-rs/src/messages/mod.rs create mode 100644 xous-rs/src/string.rs create mode 100644 xous-rs/src/syscall.rs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d1725ed --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +*.py text eol=lf +*.dfu binary +*.png binary +*.jpg binary +*.bin binary +*.elf binary +*.h text eol=lf +*.c text eol=lf +*.s text eol=lf +*.S text eol=lf +README.* text eol=lf +LICENSE text eol=lf +Makefile text eol=lf +*.mk text eol=lf +*.sh text eol=lf +*.ps1 text eol=crlf +.gitignore text eol=lf +.gitattributes text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3350711 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,133 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "ipc-test" +version = "0.1.0" +dependencies = [ + "xous", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha", + "rand_core", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + +[[package]] +name = "syn" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "xous" +version = "0.1.0" +dependencies = [ + "bitflags", + "hex", + "lazy_static", + "xous-macros", +] + +[[package]] +name = "xous-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rand", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4c52a1b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ipc-test" +version = "0.1.0" +authors = ["Sean Cross "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +xous = { path = "xous-rs" } diff --git a/macros/Cargo.lock b/macros/Cargo.lock new file mode 100644 index 0000000..0b6e2b9 --- /dev/null +++ b/macros/Cargo.lock @@ -0,0 +1,98 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha", + "rand_core", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + +[[package]] +name = "syn" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "xous-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rand", + "syn", +] diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..389d17c --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,29 @@ +[package] +authors = [ + "Sean Cross " +] +categories = ["embedded", "no-std"] +description = "Attributes re-exported in `xous`" +documentation = "https://docs.rs/xous" +keywords = ["xous", "runtime", "startup"] +license = "MIT OR Apache-2.0" +name = "xous-macros" +repository = "https://github.com/betrusted/xous-core" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["extra-traits", "full"] + +[dependencies.rand] +version = "0.7.3" +default-features = false +features = ["small_rng"] diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..1138db2 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,137 @@ +// #![deny(warnings)] + +extern crate proc_macro; +extern crate rand; +#[macro_use] +extern crate quote; +extern crate core; +extern crate proc_macro2; +#[macro_use] +extern crate syn; + +use proc_macro2::Span; +use rand::Rng; +use rand::SeedableRng; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; +use syn::{parse, spanned::Spanned, Ident, ItemFn, ReturnType, Type, Visibility}; + +static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + +use proc_macro::TokenStream; + +/// Attribute to declare the entry point of the program +/// +/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you +/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no +/// private modules between the item and the root of the crate); if the item is in the root of the +/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases. +/// +/// The specified function will be called by the reset handler *after* RAM has been initialized. +/// If present, the FPU will also be enabled before the function is called. +/// +/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function) +/// +/// # Properties +/// +/// The entry point will be called by the reset handler. The program can't reference to the entry +/// point, much less invoke it. +/// +/// # Examples +/// +/// - Simple entry point +/// +/// ``` no_run +/// # #![no_main] +/// # use riscv_rt_macros::entry; +/// #[entry] +/// fn main() -> ! { +/// loop { +/// /* .. */ +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn xous_main(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as ItemFn); + + // check the function signature + let valid_signature = f.sig.constness.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), + }; + + if !valid_signature { + return parse::Error::new( + f.span(), + "`#[xous_main]` function must have signature `[unsafe] fn() -> !`", + ) + .to_compile_error() + .into(); + } + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let unsafety = f.sig.unsafety; + let hash = random_ident(); + let stmts = f.block.stmts; + + let r = quote!( + #[export_name = "xous_entry"] + #(#attrs)* + pub #unsafety fn #hash() -> ! { + xous::init(); + #(#stmts)* + } + + xous::maybe_main!(); + ); + r.into() +} + +// Creates a random identifier +fn random_ident() -> Ident { + let secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let count: u64 = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u64; + let mut seed: [u8; 16] = [0; 16]; + + for (i, v) in seed.iter_mut().take(8).enumerate() { + *v = ((secs >> (i * 8)) & 0xFF) as u8 + } + + for (i, v) in seed.iter_mut().skip(8).enumerate() { + *v = ((count >> (i * 8)) & 0xFF) as u8 + } + + let mut rng = rand::rngs::SmallRng::from_seed(seed); + Ident::new( + &(0..16) + .map(|i| { + if i == 0 || rng.gen() { + (b'a' + rng.gen::() % 25) as char + } else { + (b'0' + rng.gen::() % 10) as char + } + }) + .collect::(), + Span::call_site(), + ) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4f47084 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,47 @@ +#[repr(C)] +#[derive(Default)] +struct SendableThing { + one: usize, + two: u32, + three: u8, + four: u16, +} + +// #[repr(C)] +// struct UnSendableThing { +// badness: *const u8, +// } + +#[xous::xous_main] +fn main() -> ! { + // let mut unsendable = xous::ipc::Sendable::new(UnSendableThing { + // badness: 4 as *const u8, + // }); + // Allocate an uninitialized SendableThing. Note we must explicitly call out what it is + // we're constructing, since it's ambiguous. This is the Turbofish operator (i.e. ::) + let _st1_uninit = xous::ipc::UninitializedSendable::::uninit().unwrap(); + // {{{something here involving uninitialized values}}} + let _st1 = unsafe { _st1_uninit.assume_init() }; + + // Pass a default value to the constructor + let mut _st2 = xous::ipc::Sendable::new(SendableThing { + one: 1, + two: 10, + three: 3, + four: 4, + }).unwrap(); + + // Note that you can mostly ignore the `SendableThing` wrapper and + // directly manipulate fields. + _st2.two = 2; + _st2.four += 1; + + // Since this implements `Default`, we can just call the `default()` constructor. + // This also uses Turbofish. + let _st3 = xous::ipc::Sendable::::default(); + + // It's also possible to simply be explicit about what type we want. + let _st4: xous::ipc::Sendable = xous::ipc::Sendable::default(); + + loop {} +} diff --git a/xous-rs/Cargo.toml b/xous-rs/Cargo.toml new file mode 100644 index 0000000..e558142 --- /dev/null +++ b/xous-rs/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "xous" +version = "0.1.0" +authors = ["Sean Cross "] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "System call interface for Xous" + +[dependencies] +bitflags = "1" +xous-macros = { path = "../macros", version = "0.1.0" } +log = { version = "0.4", optional = true } + +[features] +# If this is set, then the "Drop" feature of MemoryMessage structs +# will not be implemented. This should only be set by the kernel. +forget-memory-messages = [] + +# `logging` causes this program to connect to a log server on startup +# so you can run log commands such as `info!()`. +logging = ["log"] + +default = [] + +[target.'cfg(any(windows,unix))'.dependencies] +lazy_static = "1.4" +hex = "0.4" +# debug-here = "*" diff --git a/xous-rs/assemble.ps1 b/xous-rs/assemble.ps1 new file mode 100644 index 0000000..5c9a72a --- /dev/null +++ b/xous-rs/assemble.ps1 @@ -0,0 +1,18 @@ +# remove existing blobs because otherwise this will append object files to the old blobs +New-Item -Type Directory -Name bin -Force | Out-Null +Remove-Item -Force bin/*.a | Out-Null + +$crate = "xous" + +riscv64-unknown-elf-gcc -ggdb3 -c -mabi=ilp32 -march=rv32imac src/asm.S -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv32imac-unknown-none-elf.a bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv32imc-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -ggdb3 -c -mabi=ilp32 -march=rv32i src/asm.S -DSKIP_MULTICORE -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv32i-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -ggdb3 -c -mabi=lp64 -march=rv64imac src/asm.S -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv64imac-unknown-none-elf.a bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv64gc-unknown-none-elf.a bin/$crate.o + +Remove-Item bin/$crate.o diff --git a/xous-rs/assemble.sh b/xous-rs/assemble.sh new file mode 100644 index 0000000..9ee9497 --- /dev/null +++ b/xous-rs/assemble.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euxo pipefail + +crate=xous-kernel + +# remove existing blobs because otherwise this will append object files to the old blobs +mkdir -p bin +rm -f bin/*.a + +riscv64-unknown-elf-gcc -ggdb3 -c -mabi=ilp32 -march=rv32imac src/asm.S -o bin/$crate.o +ar crs bin/riscv32imac-unknown-none-elf.a bin/$crate.o +ar crs bin/riscv32imc-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -ggdb3 -c -mabi=ilp32 -march=rv32i src/asm.S -DSKIP_MULTICORE -o bin/$crate.o +ar crs bin/riscv32i-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -ggdb3 -c -mabi=lp64 -march=rv64imac src/asm.S -o bin/$crate.o +ar crs bin/riscv64imac-unknown-none-elf.a bin/$crate.o +ar crs bin/riscv64gc-unknown-none-elf.a bin/$crate.o + +rm bin/$crate.o diff --git a/xous-rs/bin/riscv32i-unknown-none-elf.a b/xous-rs/bin/riscv32i-unknown-none-elf.a new file mode 100644 index 0000000000000000000000000000000000000000..0af7945fd572728dab9a824b26443603bbb54805 GIT binary patch literal 2250 zcmbVNO>fjz6ul1VluZFF-61htgn6*;ZbN zP!Mfa_5Q8WY_~f8%D z%~qV?lEvF&JhKPkx9c$yH9F|aY_hYy80H_Dxo@VWy=&Uc7_W7-b)t2EOFf3BBS*?Q zz@*REIe+$1@ABqq+Pi_>?|qkM8|kpelQ^B_^5xa*{>5v49G#8j;`znJ{Tk82#tXUp9DtkwWz^mo0Ta1s~57 zXdVtbsreFq+I)uc!y>gTycSvpLj)wI90wlo}Uqz%Vl&}A{+F#3IzX$9fg)}L$89&0ax!v#NG7cs`K!r zEth@OF$e7=8n4NJ>1LsEV*ty1+XQMZ#<_3~pQ^t=;)4kCO3=^7Grxr9#sJDIw2(l} z#rPiT`5u6L4IlGy4_v;hz-|nn{Eep9gme^3YMA~u2KfYP`edXBUa$KaqzfPG^7**B zw}4rfFPg)>!$GA@{<$c>vaDBR_CwC literal 0 HcmV?d00001 diff --git a/xous-rs/bin/riscv32imac-unknown-none-elf.a b/xous-rs/bin/riscv32imac-unknown-none-elf.a new file mode 100644 index 0000000000000000000000000000000000000000..e9e31da1ba57734b4bfc46065e32825d2422027c GIT binary patch literal 2266 zcmbVMO^*~+6upmPPzGpvhVdgpOwknzOr)l#7-xWh5%41>1Q$4oiJdgnJ>}4`X9lZk zCJ+}A;!^f34I#uB;=-TcFEIQB*RFJ9;>L5{yKlOx6Jziu@4j>IJ@4N4eezOP3~#?X zyBIcQ>7Dawy1aURH4VeA4b^oRF0Xk0tyvM-hgPul?{4pu{qkNp%*JDBSp8zBtVBpi zwkP%et!RYo<0YSx*yg2t(EU)?M{FMy}HTx0G1o&byS15-AyniCgqc~klGQ}(M< z_8U_+SLnp$<7w3G*qfT4o8zQ8`v5a-dR)FbzZpMd`IGfHds6R^FJ${bUY@ciJHN|N=g$|M<-r6ff@&XV`umd>eWdWEpt!Zv6iy za#DE#dP>iAdzv{Yr_p##{)@K+jvoVP^X(F-_}JU9d9B}5|F=lIfFQ2~{cJq*t7v`< zpgc#52~>QH@1dUW5sXjaV?OSI&vygZj{%gw(Da(n+>2#3On)1LLIM?iGIo9Sy1&EV zH_p0zKECc9VAf@f(uI*aXf%WS2ZLwn#pmO7uk%jeZ+YlR=soWr^H8-d literal 0 HcmV?d00001 diff --git a/xous-rs/bin/riscv32imac-unknown-xous-elf.a b/xous-rs/bin/riscv32imac-unknown-xous-elf.a new file mode 100644 index 0000000000000000000000000000000000000000..73802b359e359b1d61a3ac925b3a3b20905f3b45 GIT binary patch literal 2278 zcmbVMO^Xy)6ur-K{1~ZaRSEy znb!NaO08ydWd&8*t>&_=wLbsNXE#1K#-NZDBQIrYiTpMvg}q#am!vL7B?EqPE%^SK z1kZkz;Q8+o{P048AODo#r@tild5;NxSu{@`bNZh6(8j#39f2+iA-&1czDS&nqFc7w z9G5HJLF1W&2)~)eNYrRR7ujTQc_GZdE9S9Tkovv_vuM29$=d1KF)Xu?_n>vOda4~J ze!Yk8Fu;vU->JDvwmb?K(C_c{VB#`n$GymnS> zyyi^hFV5I+&)DzI*j%9%n~x_`wnJxXer}GH>g+wtwCb_>%KU2lP{E(B$J%X|UJmUd z0#6Ri*wdY|ao(({c>frGxU`poH)7=YlwQ~p-DvvN*ITvpEuY6YqvbyUZs^4%4%Pn& z{d#!Bp>kH~-wy>L4wZ8qxS>lV4wdsI{kk;bP&sS#>*f)M%DGFwZXI!`oS6Q@P!Qr! zIh(+ZBlL<8hsqfNH}rB5hsyby{-aP3;!rtUhSAH$6y^jJq>hGnTnP8u=mmX;_?Y$c zJsUj?pZG27FLE*gWHwKrc{uEh=9egYILhN5`aHG$mP2{+AeU$(&SQysSte1M4C81c z>1}Uzhl6oaaSX=yCLkBVxYtXQok}=P%M})Z=7}cd|ci8z^uy{r3vGTg@tB}?GYNbg7iYhaGrGrdU7c2%fJ73aQuPB kr^B;n-8vfQ;LZ$CwqWoqow$6w?q%K*{+6eXgx>M~29Ha+fB*mh literal 0 HcmV?d00001 diff --git a/xous-rs/bin/riscv32imc-unknown-none-elf.a b/xous-rs/bin/riscv32imc-unknown-none-elf.a new file mode 100644 index 0000000000000000000000000000000000000000..e9e31da1ba57734b4bfc46065e32825d2422027c GIT binary patch literal 2266 zcmbVMO^*~+6upmPPzGpvhVdgpOwknzOr)l#7-xWh5%41>1Q$4oiJdgnJ>}4`X9lZk zCJ+}A;!^f34I#uB;=-TcFEIQB*RFJ9;>L5{yKlOx6Jziu@4j>IJ@4N4eezOP3~#?X zyBIcQ>7Dawy1aURH4VeA4b^oRF0Xk0tyvM-hgPul?{4pu{qkNp%*JDBSp8zBtVBpi zwkP%et!RYo<0YSx*yg2t(EU)?M{FMy}HTx0G1o&byS15-AyniCgqc~klGQ}(M< z_8U_+SLnp$<7w3G*qfT4o8zQ8`v5a-dR)FbzZpMd`IGfHds6R^FJ${bUY@ciJHN|N=g$|M<-r6ff@&XV`umd>eWdWEpt!Zv6iy za#DE#dP>iAdzv{Yr_p##{)@K+jvoVP^X(F-_}JU9d9B}5|F=lIfFQ2~{cJq*t7v`< zpgc#52~>QH@1dUW5sXjaV?OSI&vygZj{%gw(Da(n+>2#3On)1LLIM?iGIo9Sy1&EV zH_p0zKECc9VAf@f(uI*aXf%WS2ZLwn#pmO7uk%jeZ+YlR=soWr^H8-d literal 0 HcmV?d00001 diff --git a/xous-rs/bin/riscv64gc-unknown-none-elf.a b/xous-rs/bin/riscv64gc-unknown-none-elf.a new file mode 100644 index 0000000000000000000000000000000000000000..aa1433070a5f582b7e624ba7de4dad884ce9ccfd GIT binary patch literal 3342 zcmb_e&5smS5U<{bbw6a7T~LW6rg2b_Kr=h6!^W@dqQV8ZY)mwnWTt1fVJFOf(A~3H z-~i#y#lL_MV~8;xJb5&pJR1K5{scXGv3}E4yVY-cF2qXuy?Vd;y{dZkK4#8*8YR8W z&*v@$MOpf2rO{ZvvT~&n1oe^0wn4DmRDNfUh^F{U@U{1MhG{#!pZ21`fC{5_GE6ff z5W=0qc#|S6)$7eBN4dPzT*+f~<{o`@XgeIh2+@*ywoQogn?KW=@ovk|wx;oWN8jg#$o(8A7LOj5dj>+4``J!piN8?<(g zJth6Lw@2PPtR7AFn#=vhZoR#QCPLHWraYcS{nDMpJ{(lo(i!&)V+`_=jTcVwBhC!P zrzhYz+E$cbu^{V;FHXSUoPfVQ0Y@dRsQ-5@$hzY1Pr$J`uK47x_Rx>ZRsXJ^9G`dT zlFvMq*KMBMJmJ76xvY*ipWNNb2|nSbjEhg>`GDoq>tke8peor~^t;21jsJt3 zW*?}b)=gcmilTK^9QCtuh6kAM{<3XalF`An@&ONK_B_ zpO{h=(B2u~E;lN{z^e<<{TrnB2pmX@zq0&yXgeIh2+@*ywoQogn?KW=@ovk|wx;oWN8jg#$o(8A7LOj5dj>+4``J!piN8?<(g zJth6Lw@2PPtR7AFn#=vhZoR#QCPLHWraYcS{nDMpJ{(lo(i!&)V+`_=jTcVwBhC!P zrzhYz+E$cbu^{V;FHXSUoPfVQ0Y@dRsQ-5@$hzY1Pr$J`uK47x_Rx>ZRsXJ^9G`dT zlFvMq*KMBMJmJ76xvY*ipWNNb2|nSbjEhg>`GDoq>tke8peor~^t;21jsJt3 zW*?}b)=gcmilTK^9QCtuh6kAM{<3XalF`An@&ONK_B_ zpO{h=(B2u~E;lN{z^e<<{TrnB2pmX@zq0&y ProcessKey { + ProcessKey(key) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ThreadInit {} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct ProcessInit { + pub key: ProcessKey, +} + +pub struct ProcessArgsAsThread { + main: F, + name: String, +} + +impl ProcessArgsAsThread +where + F: FnOnce(), +{ + pub fn new(name: &str, main: F) -> ProcessArgsAsThread { + ProcessArgsAsThread { + main, + name: name.to_owned(), + } + } +} +pub struct ProcessHandleAsThread(std::thread::JoinHandle<()>); + +/// If no connection exists, create a new connection to the server. This means +/// our parent PID will be PID1. Otherwise, reuse the same connection. +pub fn create_process_pre_as_thread( + _args: &ProcessArgsAsThread, +) -> core::result::Result +where + F: FnOnce(), +{ + ensure_connection()?; + + // Ensure there is a connection, because after this function returns + // we'll make a syscall with CreateProcess(). This should only need + // to happen for PID1. + Ok(ProcessInit { + key: PROCESS_KEY + .with(|pk| *pk.borrow()) + .unwrap_or_else(default_process_key), + }) +} + +pub fn create_process_post_as_thread( + args: ProcessArgsAsThread, + init: ProcessInit, + pid: PID, +) -> core::result::Result +where + F: FnOnce() + Send + 'static, +{ + let server_address = xous_address(); + + let f = args.main; + let thread_main = std::thread::Builder::new() + .name(args.name) + .spawn(move || { + set_xous_address(server_address); + THREAD_ID.with(|tid| *tid.borrow_mut() = 1); + PROCESS_ID.with(|p| *p.borrow_mut() = pid); + XOUS_SERVER_CONNECTION.with(|xsc| { + let mut xsc = xsc.borrow_mut(); + match xous_connect_impl(server_address, &init.key) { + Ok(a) => { + *xsc = Some(a); + Ok(()) + } + Err(_) => Err(crate::Error::InternalError), + } + })?; + + crate::create_thread(f) + }) + .map_err(|_| crate::Error::InternalError)? + .join() + .unwrap() + .unwrap(); + + Ok(ProcessHandleAsThread(thread_main.0)) +} + +pub fn wait_process_as_thread(joiner: ProcessHandleAsThread) -> crate::SysCallResult { + joiner.0.join().map(|_| Result::Ok).map_err(|_x| { + // panic!("wait error: {:?}", x); + crate::Error::InternalError + }) +} + +pub struct ProcessArgs { + command: String, + name: String, +} + +impl ProcessArgs { + pub fn new(name: &str, command: String) -> ProcessArgs { + ProcessArgs { + command, + name: name.to_owned(), + } + } +} + +#[derive(Debug)] +pub struct ProcessHandle(std::process::Child); + +/// If no connection exists, create a new connection to the server. This means +/// our parent PID will be PID1. Otherwise, reuse the same connection. +pub fn create_process_pre(_args: &ProcessArgs) -> core::result::Result { + ensure_connection()?; + + // Ensure there is a connection, because after this function returns + // we'll make a syscall with CreateProcess(). This should only need + // to happen for PID1. + Ok(ProcessInit { + key: PROCESS_KEY + .with(|pk| *pk.borrow()) + .unwrap_or_else(default_process_key), + }) +} + +pub fn create_process_post( + args: ProcessArgs, + init: ProcessInit, + pid: PID, +) -> core::result::Result { + use std::process::Command; + let server_env = format!("{}", xous_address()); + let pid_env = format!("{}", pid); + let process_name_env = args.name.to_string(); + let process_key_env = hex::encode(&init.key.0); + let (shell, args) = if cfg!(windows) { + ("cmd", ["/C", &args.command]) + } else if cfg!(unix) { + ("sh", ["-c", &args.command]) + } else { + panic!("unrecognized platform -- don't know how to shell out"); + }; + + // println!("Launching process..."); + Command::new(shell) + .args(&args) + .env("XOUS_SERVER", server_env) + .env("XOUS_PID", pid_env) + .env("XOUS_PROCESS_NAME", process_name_env) + .env("XOUS_PROCESS_KEY", process_key_env) + .spawn() + .map(ProcessHandle) + .map_err(|_| { + // eprintln!("couldn't start command: {}", e); + crate::Error::InternalError + }) +} + +pub fn wait_process(mut joiner: ProcessHandle) -> crate::SysCallResult { + joiner + .0 + .wait() + .or(Err(crate::Error::InternalError)) + .and_then(|e| { + if e.success() { + Ok(crate::Result::Ok) + } else { + Err(crate::Error::UnknownError) + } + }) +} + +pub struct WaitHandle(std::thread::JoinHandle); + +#[derive(Clone)] +struct ServerConnection { + send: Arc>, + recv: Arc>, + mailbox: Arc>>, +} + +pub fn thread_to_args(call: usize, _init: &ThreadInit) -> [usize; 8] { + [call, 0, 0, 0, 0, 0, 0, 0] +} + +pub fn process_to_args(call: usize, init: &ProcessInit) -> [usize; 8] { + [ + call, + u32::from_le_bytes(init.key.0[0..4].try_into().unwrap()) as _, + u32::from_le_bytes(init.key.0[4..8].try_into().unwrap()) as _, + u32::from_le_bytes(init.key.0[8..12].try_into().unwrap()) as _, + u32::from_le_bytes(init.key.0[12..16].try_into().unwrap()) as _, + 0, + 0, + 0, + ] +} + +pub fn args_to_thread( + _a1: usize, + _a2: usize, + _a3: usize, + _a4: usize, + _a5: usize, + _a6: usize, + _a7: usize, +) -> core::result::Result { + Ok(ThreadInit {}) +} + +pub fn args_to_process( + a1: usize, + a2: usize, + a3: usize, + a4: usize, + _a5: usize, + _a6: usize, + _a7: usize, +) -> core::result::Result { + let mut v = vec![]; + v.extend_from_slice(&(a1 as u32).to_le_bytes()); + v.extend_from_slice(&(a2 as u32).to_le_bytes()); + v.extend_from_slice(&(a3 as u32).to_le_bytes()); + v.extend_from_slice(&(a4 as u32).to_le_bytes()); + let mut key = [0u8; 16]; + key.copy_from_slice(&v); + Ok(ProcessInit { + key: ProcessKey(key), + }) +} + +thread_local!(static NETWORK_CONNECT_ADDRESS: RefCell> = RefCell::new(None)); +thread_local!(static XOUS_SERVER_CONNECTION: RefCell> = RefCell::new(None)); +thread_local!(static THREAD_ID: RefCell = RefCell::new(1)); +thread_local!(static PROCESS_ID: RefCell = RefCell::new(PID::new(1).unwrap())); +thread_local!(static PROCESS_KEY: RefCell> = RefCell::new(None)); +thread_local!(static CALL_FOR_THREAD: RefCell>>> = RefCell::new(Arc::new(Mutex::new(HashMap::new())))); + +fn default_xous_address() -> SocketAddr { + std::env::var("XOUS_SERVER") + .map(|s| { + s.to_socket_addrs() + .expect("invalid server address") + .next() + .expect("unable to resolve server address") + }) + .unwrap_or_else(|_| SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0)) +} + +fn default_process_key() -> ProcessKey { + std::env::var("XOUS_PROCESS_KEY") + .map(|s| { + let mut base = ProcessKey([0u8; 16]); + hex::decode_to_slice(s, &mut base.0).unwrap(); + base + }) + .unwrap_or(ProcessKey([0u8; 16])) +} + +pub fn set_process_key(new_key: &[u8; 16]) { + PROCESS_KEY.with(|pk| *pk.borrow_mut() = Some(ProcessKey(*new_key))); +} + +/// Set the network address for this particular thread. +pub fn set_xous_address(new_address: SocketAddr) { + NETWORK_CONNECT_ADDRESS.with(|nca| { + let mut address = nca.borrow_mut(); + *address = Some(new_address); + XOUS_SERVER_CONNECTION.with(|xsc| *xsc.borrow_mut() = None); + }); +} + +/// Get the network address for this particular thread. +fn xous_address() -> SocketAddr { + NETWORK_CONNECT_ADDRESS + .with(|nca| *nca.borrow()) + .unwrap_or_else(default_xous_address) +} + +pub fn create_thread_simple_pre( + _f: &fn(T) -> U, + _arg: &T, +) -> core::result::Result +where + T: Send + 'static, + U: Send + 'static, +{ + Ok(ThreadInit {}) +} + +pub fn create_thread_simple_post( + f: fn(T) -> U, + arg: T, + thread_id: TID, +) -> core::result::Result, crate::Error> +where + T: Send + 'static, + U: Send + 'static, +{ + create_thread_post(move || f(arg), thread_id) +} + +pub fn create_thread_pre(_f: &F) -> core::result::Result +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + Ok(ThreadInit {}) +} + +pub fn create_thread_post( + f: F, + thread_id: TID, +) -> core::result::Result, crate::Error> +where + F: FnOnce() -> U, + F: Send + 'static, + U: Send + 'static, +{ + let server_address = xous_address(); + let server_connection = + XOUS_SERVER_CONNECTION.with(|xsc| xsc.borrow().as_ref().unwrap().clone()); + let process_id = PROCESS_ID.with(|pid| *pid.borrow()); + let call_for_thread = CALL_FOR_THREAD.with(|cft| cft.borrow().clone()); + Ok(std::thread::Builder::new() + .spawn(move || { + set_xous_address(server_address); + THREAD_ID.with(|tid| *tid.borrow_mut() = thread_id); + PROCESS_ID.with(|pid| *pid.borrow_mut() = process_id); + XOUS_SERVER_CONNECTION.with(|xsc| *xsc.borrow_mut() = Some(server_connection)); + CALL_FOR_THREAD.with(|cft| *cft.borrow_mut() = call_for_thread); + f() + }) + .map(WaitHandle) + .map_err(|_| crate::Error::InternalError)?) +} + +pub fn wait_thread(joiner: WaitHandle) -> crate::SysCallResult { + joiner + .0 + .join() + .map(|_| Result::Ok) + .map_err(|_| crate::Error::InternalError) +} + +pub fn ensure_connection() -> core::result::Result<(), crate::Error> { + XOUS_SERVER_CONNECTION.with(|xsc| { + let mut xsc = xsc.borrow_mut(); + if xsc.is_none() { + NETWORK_CONNECT_ADDRESS.with(|nca| { + let addr = nca.borrow().unwrap_or_else(default_xous_address); + let pid1_key = PROCESS_KEY + .with(|pk| *pk.borrow()) + .unwrap_or_else(default_process_key); + match xous_connect_impl(addr, &pid1_key) { + Ok(a) => { + *xsc = Some(a); + Ok(()) + } + Err(_) => Err(crate::Error::InternalError), + } + }) + } else { + Ok(()) + } + }) +} + +fn xous_connect_impl( + addr: SocketAddr, + key: &ProcessKey, +) -> core::result::Result { + // eprintln!("Opening connection to Xous server @ {} with key {:?}...", addr, key); + assert_ne!(&key.0, &[0u8; 16]); + match TcpStream::connect(addr) { + Ok(mut conn) => { + conn.write_all(&key.0).unwrap(); // Send key to authenticate us as PID 1 + conn.flush().unwrap(); + Ok(ServerConnection { + send: Arc::new(Mutex::new(conn.try_clone().unwrap())), + recv: Arc::new(Mutex::new(conn)), + mailbox: Arc::new(Mutex::new(HashMap::new())), + }) + } + Err(_e) => { + // eprintln!("Unable to connect to Xous server: {}", _e); + // eprintln!( + // "Ensure Xous is running, or specify this process as an argument to the kernel" + // ); + Err(()) + } + } +} + +#[allow(clippy::too_many_arguments)] +#[no_mangle] +pub fn _xous_syscall( + nr: usize, + a1: usize, + a2: usize, + a3: usize, + a4: usize, + a5: usize, + a6: usize, + a7: usize, + ret: &mut Result, +) { + XOUS_SERVER_CONNECTION.with(|xsc| { + THREAD_ID.with(|tid| { + let call = crate::SysCall::from_args(nr, a1, a2, a3, a4, a5, a6, a7).unwrap(); + { + CALL_FOR_THREAD.with(|cft| { + let cft_rc = cft.borrow(); + let mut cft_mtx = cft_rc.lock().unwrap(); + let tid = *tid.borrow(); + assert!(cft_mtx.get(&tid).is_none()); + cft_mtx.insert(tid, call) + }); + } + let call = crate::SysCall::from_args(nr, a1, a2, a3, a4, a5, a6, a7).unwrap(); + + let mut xsc_borrowed = xsc.borrow_mut(); + let xsc_asmut = xsc_borrowed.as_mut().expect("not connected to server (did you forget to create a thread with xous::create_thread()?)"); + loop { + _xous_syscall_to( + nr, + a1, + a2, + a3, + a4, + a5, + a6, + a7, + &call, + &mut xsc_asmut.send.lock().unwrap(), + ); + _xous_syscall_result(ret, *tid.borrow(), xsc_asmut); + if *ret != Result::WouldBlock { + return; + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + }) + }); +} + +fn _xous_syscall_result(ret: &mut Result, thread_id: TID, server_connection: &ServerConnection) { + // Check to see if this thread id has an entry in the mailbox already. + // This will block until the hashmap is free. + { + let mut mailbox = server_connection.mailbox.lock().unwrap(); + if let Some(entry) = mailbox.get(&thread_id) { + if &Result::BlockedProcess != entry { + *ret = mailbox.remove(&thread_id).unwrap(); + return; + } + } + } + + // Receive the packet back + loop { + // Now that we have the Stream mutex, temporarily take the Mailbox mutex to see if + // this thread ID is there. If it is, there's no need to read via the network. + // Note that the mailbox mutex is released if it isn't found. + { + let mut mailbox = server_connection.mailbox.lock().unwrap(); + if let Some(entry) = mailbox.get(&thread_id) { + if &Result::BlockedProcess != entry { + *ret = mailbox.remove(&thread_id).unwrap(); + return; + } + } + } + + let mut stream = match server_connection.recv.try_lock() { + Ok(lk) => lk, + Err(std::sync::TryLockError::WouldBlock) => { + std::thread::sleep(std::time::Duration::from_millis(10)); + continue; + } + Err(e) => panic!("Receive error: {}", e), + }; + + // One more check, in case something came in while we waited for the receiver above. + { + let mut mailbox = server_connection.mailbox.lock().unwrap(); + if let Some(entry) = mailbox.get(&thread_id) { + if &Result::BlockedProcess != entry { + *ret = mailbox.remove(&thread_id).unwrap(); + return; + } + } + } + + // This thread_id doesn't exist in the mailbox, so read additional data. + let mut pkt = [0usize; 8]; + let mut raw_bytes = [0u8; size_of::() * 9]; + if let Err(e) = stream.read_exact(&mut raw_bytes) { + eprintln!("Server shut down: {}", e); + std::process::exit(0); + } + + let mut raw_bytes_chunks = raw_bytes.chunks(size_of::()); + + // Read the Thread ID, which comes across first, followed by the 8 words of + // the message data. + let msg_thread_id = + usize::from_le_bytes(raw_bytes_chunks.next().unwrap().try_into().unwrap()); + for (pkt_word, word) in pkt.iter_mut().zip(raw_bytes_chunks) { + *pkt_word = usize::from_le_bytes(word.try_into().unwrap()); + } + + let mut response = Result::from_args(pkt); + + // If we got a `WouldBlock`, then we need to retry the whole call + // again. Return and retry. + if response == Result::WouldBlock { + // If the incoming message was for this thread, return it directly. + if msg_thread_id == thread_id { + *ret = response; + return; + } + + // Otherwise, add it to the mailbox and try again. + let mut mailbox = server_connection.mailbox.lock().unwrap(); + mailbox.insert(msg_thread_id, response); + continue; + } + + if response == Result::BlockedProcess { + // println!(" Waiting again"); + continue; + } + + // Determine if this thread will have a memory packet following it. + let call = CALL_FOR_THREAD.with(|cft| { + let cft_borrowed = cft.borrow(); + let mut cft_mtx = cft_borrowed.lock().unwrap(); + cft_mtx + .remove(&msg_thread_id) + .expect("thread didn't declare whether it has data") + }); + + // If the client is passing us memory, remap the array to our own space. + if let Result::Message(msg) = &mut response { + match &mut msg.body { + crate::Message::Move(ref mut memory_message) + | crate::Message::Borrow(ref mut memory_message) + | crate::Message::MutableBorrow(ref mut memory_message) => { + let data = vec![0u8; memory_message.buf.len()]; + let mut data = std::mem::ManuallyDrop::new(data); + if let Err(e) = stream.read_exact(&mut data) { + eprintln!("Server shut down: {}", e); + std::process::exit(0); + } + let len = data.len(); + let addr = data.as_mut_ptr(); + memory_message.buf.addr = crate::MemoryAddress::new(addr as _).unwrap(); + memory_message.buf.size = crate::MemorySize::new(len).unwrap(); + } + _ => (), + } + } + + // If the original call contained memory, then ensure the memory we get back is correct. + if let Some(mem) = call.memory() { + if call.is_borrow() || call.is_mutableborrow() { + // Read the buffer back from the remote host. + use core::slice; + let mut data = unsafe { slice::from_raw_parts_mut(mem.as_mut_ptr(), mem.len()) }; + + // If it's a Borrow, verify the contents haven't changed. + let previous_data = if call.is_borrow() { + Some(data.to_vec()) + } else { + None + }; + + if let Err(e) = stream.read_exact(&mut data) { + eprintln!("Server shut down: {}", e); + std::process::exit(0); + } + + // If it is an immutable borrow, verify the contents haven't changed somehow + if let Some(previous_data) = previous_data { + assert_eq!(data, previous_data.as_slice()); + } + } + + if call.is_move() { + // In a hosted environment, the message contents are leaked when + // it gets converted into a MemoryMessage. Now that the call is + // complete, free the memory. + mem::unmap_memory_post(mem).unwrap(); + } + + // If we're returning memory to the Server, then reconstitute the buffer we just passed, + // and Drop it so it can be freed. + if call.is_return_memory() { + let rebuilt = + unsafe { Vec::from_raw_parts(mem.as_mut_ptr(), mem.len(), mem.len()) }; + drop(rebuilt); + } + } + + // Now that we have the Stream mutex, temporarily take the Mailbox mutex to see if + // this thread ID is there. If it is, there's no need to read via the network. + // Note that the mailbox mutex is released if it isn't found. + { + // If the incoming message was for this thread, return it directly. + if msg_thread_id == thread_id { + *ret = response; + return; + } + + // Otherwise, add it to the mailbox and try again. + let mut mailbox = server_connection.mailbox.lock().unwrap(); + mailbox.insert(msg_thread_id, response); + } + } +} + +#[allow(clippy::too_many_arguments)] +#[no_mangle] +fn _xous_syscall_to( + nr: usize, + a1: usize, + a2: usize, + a3: usize, + a4: usize, + a5: usize, + a6: usize, + a7: usize, + call: &crate::SysCall, + xsc: &mut TcpStream, +) { + // println!( + // "Making Syscall: {:?}", + // crate::SysCall::from_args(nr, a1, a2, a3, a4, a5, a6, a7).unwrap() + // ); + + // Send the packet to the server + let mut pkt = vec![]; + THREAD_ID.with(|tid| pkt.extend_from_slice(&tid.borrow().to_le_bytes())); + for word in &[nr, a1, a2, a3, a4, a5, a6, a7] { + pkt.extend_from_slice(&word.to_le_bytes()); + } + + // Also send memory, if it's present. + if let Some(memory) = call.memory() { + use core::slice; + let data: &[u8] = + unsafe { slice::from_raw_parts(memory.as_ptr(), memory.len()) }; + pkt.extend_from_slice(data); + } + + if let Err(e) = xsc.write_all(&pkt) { + eprintln!("Server shut down: {}", e); + std::process::exit(0); + } + xsc.flush().unwrap(); +} diff --git a/xous-rs/src/arch/hosted/mem.rs b/xous-rs/src/arch/hosted/mem.rs new file mode 100644 index 0000000..bdb5a85 --- /dev/null +++ b/xous-rs/src/arch/hosted/mem.rs @@ -0,0 +1,41 @@ +use crate::{Error, MemoryAddress, MemoryFlags, MemoryRange}; + +extern crate alloc; +use alloc::alloc::{alloc, dealloc, Layout}; + +pub fn map_memory_pre( + _phys: &Option, + _virt: &Option, + _size: usize, + _flags: MemoryFlags, +) -> core::result::Result<(), Error> { + Ok(()) +} + +pub fn map_memory_post( + _phys: Option, + _virt: Option, + _size: usize, + _flags: MemoryFlags, + mut range: MemoryRange, +) -> core::result::Result { + let layout = Layout::from_size_align(range.len(), 4096).unwrap(); + let new_mem = MemoryAddress::new(unsafe { alloc(layout) } as usize).ok_or(Error::BadAddress)?; + range.addr = new_mem; + Ok(range) +} + +pub fn unmap_memory_pre( + _range: &MemoryRange +) -> core::result::Result<(), Error> { + Ok(()) +} + +pub fn unmap_memory_post( + range: MemoryRange +) -> core::result::Result<(), Error> { + let layout = Layout::from_size_align(range.len(), 4096).unwrap(); + let ptr = range.as_mut_ptr(); + unsafe { dealloc(ptr, layout) }; + Ok(()) +} diff --git a/xous-rs/src/arch/mod.rs b/xous-rs/src/arch/mod.rs new file mode 100644 index 0000000..481135b --- /dev/null +++ b/xous-rs/src/arch/mod.rs @@ -0,0 +1,9 @@ +#[cfg(target_os = "none")] +pub mod native; +#[cfg(target_os = "none")] +pub use native::*; + +#[cfg(not(target_os = "none"))] +pub mod hosted; +#[cfg(not(target_os = "none"))] +pub use hosted::*; diff --git a/xous-rs/src/arch/native.rs b/xous-rs/src/arch/native.rs new file mode 100644 index 0000000..b9cebc2 --- /dev/null +++ b/xous-rs/src/arch/native.rs @@ -0,0 +1,218 @@ +use crate::{MemoryAddress, MemoryRange, PID, TID}; +use core::convert::TryInto; + +mod mem; +pub use mem::*; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ProcessArgs { + name: [u8; 16], +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ThreadInit { + pub call: fn(data: usize) -> usize, + pub stack: MemoryRange, + pub arg: Option, + pub name: [u8; 12], +} + +impl ThreadInit { + pub fn new( + call: fn(data: usize) -> usize, + stack: MemoryRange, + arg: Option, + name: [u8; 12], + ) -> Self { + ThreadInit { + call, + stack, + arg, + name, + } + } +} + +impl Default for ThreadInit { + fn default() -> Self { + ThreadInit { + call: unsafe { core::mem::transmute:: usize>(4) }, + stack: MemoryRange::new(4, 4).unwrap(), + arg: None, + name: [0; 12], + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct ProcessKey([u8; 8]); +impl ProcessKey { + pub fn new(key: [u8; 8]) -> ProcessKey { + ProcessKey(key) + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct ProcessInit { + pub key: ProcessKey, +} + +pub struct WaitHandle(core::marker::PhantomData); +pub struct ProcessHandle(()); + +pub fn thread_to_args(call: usize, init: &ThreadInit) -> [usize; 8] { + [ + call as usize, + init.call as usize, + init.stack.as_ptr() as _, + init.stack.len(), + init.arg.map(|x| x.get()).unwrap_or_default(), + 0, + 0, + 0, + ] +} + +/// This code is executed inside the kernel. It takes the list of args +/// that were passed via registers and converts them into a `ThreadInit` +/// struct with enough information to start the new thread. +pub fn args_to_thread( + a1: usize, + a2: usize, + a3: usize, + a4: usize, + _a5: usize, + _a6: usize, + _a7: usize, +) -> core::result::Result { + let call = unsafe { core::mem::transmute(a1) }; + let stack = MemoryRange::new(a2, a3).map_err(|_| crate::Error::InvalidSyscall)?; + let arg = MemoryAddress::new(a4); + Ok(ThreadInit { + call, + stack, + arg, + name: [0; 12], + }) +} + +pub fn create_thread_pre(_f: &F) -> core::result::Result +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + todo!() + // Ok(ThreadInit {}) +} + +pub fn create_thread_post( + _f: F, + _thread_id: TID, +) -> core::result::Result, crate::Error> +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + todo!() + // let server_address = xous_address(); + // let server_connection = + // XOUS_SERVER_CONNECTION.with(|xsc| xsc.borrow().as_ref().unwrap().clone()); + // let process_id = PROCESS_ID.with(|pid| *pid.borrow()); + // Ok(std::thread::Builder::new() + // .spawn(move || { + // set_xous_address(server_address); + // THREAD_ID.with(|tid| *tid.borrow_mut() = thread_id); + // PROCESS_ID.with(|pid| *pid.borrow_mut() = process_id); + // XOUS_SERVER_CONNECTION.with(|xsc| *xsc.borrow_mut() = Some(server_connection)); + // f() + // }) + // .map(WaitHandle) + // .map_err(|_| crate::Error::InternalError)?) +} + +pub fn wait_thread(_joiner: WaitHandle) -> crate::SysCallResult { + todo!() + // joiner + // .0 + // .join() + // .map(|_| Result::Ok) + // .map_err(|_| crate::Error::InternalError) +} + +pub fn process_to_args(call: usize, init: &ProcessInit) -> [usize; 8] { + [ + call, + u32::from_le_bytes(init.key.0[0..4].try_into().unwrap()) as _, + u32::from_le_bytes(init.key.0[4..8].try_into().unwrap()) as _, + u32::from_le_bytes(init.key.0[8..12].try_into().unwrap()) as _, + u32::from_le_bytes(init.key.0[12..16].try_into().unwrap()) as _, + 0, + 0, + 0, + ] +} + +pub fn args_to_process( + _a1: usize, + _a2: usize, + _a3: usize, + _a4: usize, + _a5: usize, + _a6: usize, + _a7: usize, +) -> core::result::Result { + todo!() +} + +pub fn create_thread_simple_pre( + f: &fn(T) -> U, + arg: &T, +) -> core::result::Result +where + T: Send + 'static, + U: Send + 'static, +{ + let stack = crate::map_memory( + None, + None, + 131_072, + crate::MemoryFlags::R | crate::MemoryFlags::W | crate::MemoryFlags::RESERVE, + )?; + let start = unsafe { core::mem::transmute(*f) }; + let arg = unsafe { core::mem::transmute(arg) }; + Ok(ThreadInit::new(start, stack, Some(arg), [0; 12])) +} + +pub fn create_thread_simple_post( + _f: fn(T) -> U, + _arg: T, + _thread_id: TID, +) -> core::result::Result, crate::Error> +where + T: Send + 'static, + U: Send + 'static, +{ + Ok(WaitHandle(core::marker::PhantomData)) +} + +/// If no connection exists, create a new connection to the server. This means +/// our parent PID will be PID1. Otherwise, reuse the same connection. +pub fn create_process_pre(_args: &ProcessArgs) -> core::result::Result { + todo!() +} + +pub fn create_process_post( + _args: ProcessArgs, + _init: ProcessInit, + _pid: PID, +) -> core::result::Result { + todo!() +} + +pub fn wait_process(_joiner: ProcessHandle) -> crate::SysCallResult { + loop { + crate::wait_event(); + } +} diff --git a/xous-rs/src/arch/native/mem.rs b/xous-rs/src/arch/native/mem.rs new file mode 100644 index 0000000..f1b0a1d --- /dev/null +++ b/xous-rs/src/arch/native/mem.rs @@ -0,0 +1,32 @@ +use crate::{Error, MemoryAddress, MemoryFlags, MemoryRange}; + +pub fn map_memory_pre( + _phys: &Option, + _virt: &Option, + _size: usize, + _flags: MemoryFlags, +) -> core::result::Result<(), Error> { + Ok(()) +} + +pub fn map_memory_post( + _phys: Option, + _virt: Option, + _size: usize, + _flags: MemoryFlags, + range: MemoryRange, +) -> core::result::Result { + Ok(range) +} + +pub fn unmap_memory_pre( + _range: &MemoryRange +) -> core::result::Result<(), Error> { + Ok(()) +} + +pub fn unmap_memory_post( + _range: MemoryRange +) -> core::result::Result<(), Error> { + Ok(()) +} diff --git a/xous-rs/src/asm.S b/xous-rs/src/asm.S new file mode 100644 index 0000000..9843c99 --- /dev/null +++ b/xous-rs/src/asm.S @@ -0,0 +1,20 @@ +#define STORE sw +#define LOAD lw +#define LOG_REGBYTES 2 +#define REGBYTES (1 << LOG_REGBYTES) + +.global _xous_syscall +.global _xous_syscall_rust +_xous_syscall_rust: +_xous_syscall: + ecall + lw t0, 0(sp) + sw a0, 0*REGBYTES(t0) + sw a1, 1*REGBYTES(t0) + sw a2, 2*REGBYTES(t0) + sw a3, 3*REGBYTES(t0) + sw a4, 4*REGBYTES(t0) + sw a5, 5*REGBYTES(t0) + sw a6, 6*REGBYTES(t0) + sw a7, 7*REGBYTES(t0) + ret diff --git a/xous-rs/src/carton.rs b/xous-rs/src/carton.rs new file mode 100644 index 0000000..c81a0d8 --- /dev/null +++ b/xous-rs/src/carton.rs @@ -0,0 +1,98 @@ +//! A Carton is an object that wraps another object for shipping across the kernel +//! boundary. Structs that are stored in Cartons can be sent as messages. + +use crate::{Error, MemoryMessage, MemoryRange, MemorySize, Message, CID}; + +#[derive(Debug)] +pub struct Carton<'a> { + range: MemoryRange, + valid: MemoryRange, + slice: &'a [u8], + should_drop: bool, +} + +impl<'a> Carton<'a> { + pub fn from_bytes(bytes: &[u8]) -> Self { + let src_mem = bytes.as_ptr(); + + // Ensure our byte size is a multiple of 4096 + let remainder = bytes.len() & 4095; + let size = bytes.len() + (4096 - remainder); + + let new_mem = crate::map_memory( + None, + None, + size, + crate::MemoryFlags::R | crate::MemoryFlags::W, + ) + .unwrap(); + + // NOTE: Remaining bytes are not zeroed. We assume the kernel has done this for us. + unsafe { + core::ptr::copy(src_mem, new_mem.as_mut_ptr(), bytes.len()); + }; + let mut valid = new_mem; + valid.size = MemorySize::new(bytes.len()).unwrap(); + Carton { + range: new_mem, + slice: unsafe { core::slice::from_raw_parts_mut(new_mem.as_mut_ptr(), bytes.len()) }, + valid, + should_drop: true, + } + } + + pub fn into_message(mut self, id: usize) -> MemoryMessage { + // Leak the memory buffer, since it will be taken care of + // when the MemoryMessage is dropped. + self.should_drop = false; + MemoryMessage { + id, + buf: self.valid, + offset: None, + valid: None, + } + } + + /// Perform an immutable lend of this Carton to the specified server. + /// This function will block until the server returns. + pub fn lend(&self, connection: CID, id: usize) -> Result { + let msg = MemoryMessage { + id, + buf: self.valid, + offset: None, + valid: None, + }; + crate::send_message(connection, Message::Borrow(msg)) + } + + /// Perform a mutable lend of this Carton to the server. + pub fn lend_mut(&mut self, connection: CID, id: usize) -> Result { + let msg = MemoryMessage { + id, + buf: self.valid, + offset: None, + valid: None, + }; + crate::send_message(connection, Message::MutableBorrow(msg)) + } +} + +impl<'a> AsRef for Carton<'a> { + fn as_ref(&self) -> &MemoryRange { + &self.valid + } +} + +impl<'a> AsRef<[u8]> for Carton<'a> { + fn as_ref(&self) -> &[u8] { + &self.slice + } +} + +impl<'a> Drop for Carton<'a> { + fn drop(&mut self) { + if self.should_drop { + crate::unmap_memory(self.range).unwrap(); + } + } +} diff --git a/xous-rs/src/definitions.rs b/xous-rs/src/definitions.rs new file mode 100644 index 0000000..456b87c --- /dev/null +++ b/xous-rs/src/definitions.rs @@ -0,0 +1,649 @@ +use core::convert::TryInto; +use core::num::{NonZeroU8, NonZeroUsize}; + +pub type MemoryAddress = NonZeroUsize; +pub type MemorySize = NonZeroUsize; +pub type StackPointer = usize; +pub type MessageId = usize; + +pub type PID = NonZeroU8; +pub type Connection = usize; + +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone)] +pub struct MessageSender { + data: usize, +} + +impl MessageSender { + pub fn to_usize(&self) -> usize { + self.data + } + + pub fn from_usize(data: usize) -> Self { + MessageSender { data } + } + + pub fn pid(&self) -> Option { + let pid_u8 = ((self.data >> 24) & 0xff) as u8; + PID::new(pid_u8) + } +} + +impl core::default::Default for MessageSender { + fn default() -> Self { + MessageSender { data: 0 } + } +} + +impl core::fmt::Display for MessageSender { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "data: {}", self.data) + } +} + +/// Server ID +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct SID((u32, u32, u32, u32)); +impl SID { + pub fn from_bytes(b: &[u8]) -> Option { + if b.len() > 16 { + None + } else { + let mut sid = (0, 0, 0, 0); + let mut byte_iter = b.chunks_exact(4); + if let Some(val) = byte_iter.next() { + sid.0 = u32::from_le_bytes(val.try_into().ok()?); + } + if let Some(val) = byte_iter.next() { + sid.1 = u32::from_le_bytes(val.try_into().ok()?); + } + if let Some(val) = byte_iter.next() { + sid.2 = u32::from_le_bytes(val.try_into().ok()?); + } + if let Some(val) = byte_iter.next() { + sid.3 = u32::from_le_bytes(val.try_into().ok()?); + } + Some(SID(sid)) + } + } + pub fn from_u32(a0: u32, a1: u32, a2: u32, a3: u32) -> SID { + SID((a0, a1, a2, a3)) + } + pub fn to_u32(&self) -> (u32, u32, u32, u32) { + ((self.0).0, (self.0).1, (self.0).2, (self.0).3) + } +} + +impl core::str::FromStr for SID { + type Err = (); + + fn from_str(s: &str) -> core::result::Result { + Self::from_bytes(s.as_bytes()).ok_or(()) + } +} + +/// Connection ID +pub type CID = usize; + +/// Context ID +pub type TID = usize; + +/// Equivalent to a RISC-V Hart ID +pub type CpuID = usize; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub struct MemoryRange { + pub addr: MemoryAddress, + pub size: MemorySize, +} + +bitflags! { + /// Flags to be passed to the MapMemory struct. + /// Note that it is an error to have memory be + /// writable and not readable. + pub struct MemoryFlags: usize { + /// Free this memory + const FREE = 0b0000_0000; + + /// Immediately allocate this memory. Otherwise it will + /// be demand-paged. This is implicitly set when `phys` + /// is not 0. + const RESERVE = 0b0000_0001; + + /// Allow the CPU to read from this page. + const R = 0b0000_0010; + + /// Allow the CPU to write to this page. + const W = 0b0000_0100; + + /// Allow the CPU to execute from this page. + const X = 0b0000_1000; + } +} + +pub fn pid_from_usize(src: usize) -> core::result::Result { + if src > u8::MAX as _ { + return Err(Error::InvalidPID); + } + Ok(PID::new(src as u8).ok_or(Error::InvalidPID)?) +} + +#[repr(usize)] +#[derive(Debug, PartialEq)] +pub enum Error { + NoError = 0, + BadAlignment = 1, + BadAddress = 2, + OutOfMemory = 3, + MemoryInUse = 4, + InterruptNotFound = 5, + InterruptInUse = 6, + InvalidString = 7, + ServerExists = 8, + ServerNotFound = 9, + ProcessNotFound = 10, + ProcessNotChild = 11, + ProcessTerminated = 12, + Timeout = 13, + InternalError = 14, + ServerQueueFull = 15, + ThreadNotAvailable = 16, + UnhandledSyscall = 17, + InvalidSyscall = 18, + ShareViolation = 19, + InvalidThread = 20, + InvalidPID = 21, + UnknownError = 22, +} + +impl Error { + pub fn from_usize(arg: usize) -> Self { + use crate::Error::*; + match arg { + 0 => NoError, + 1 => BadAlignment, + 2 => BadAddress, + 3 => OutOfMemory, + 4 => MemoryInUse, + 5 => InterruptNotFound, + 6 => InterruptInUse, + 7 => InvalidString, + 8 => ServerExists, + 9 => ServerNotFound, + 10 => ProcessNotFound, + 11 => ProcessNotChild, + 12 => ProcessTerminated, + 13 => Timeout, + 14 => InternalError, + 15 => ServerQueueFull, + 16 => ThreadNotAvailable, + 17 => UnhandledSyscall, + 18 => InvalidSyscall, + 19 => ShareViolation, + 20 => InvalidThread, + 21 => InvalidPID, + _ => UnknownError, + } + } + pub fn to_usize(&self) -> usize { + use crate::Error::*; + match *self { + NoError => 0, + BadAlignment => 1, + BadAddress => 2, + OutOfMemory => 3, + MemoryInUse => 4, + InterruptNotFound => 5, + InterruptInUse => 6, + InvalidString => 7, + ServerExists => 8, + ServerNotFound => 9, + ProcessNotFound => 10, + ProcessNotChild => 11, + ProcessTerminated => 12, + Timeout => 13, + InternalError => 14, + ServerQueueFull => 15, + ThreadNotAvailable => 16, + UnhandledSyscall => 17, + InvalidSyscall => 18, + ShareViolation => 19, + InvalidThread => 20, + InvalidPID => 21, + UnknownError => usize::MAX, + } + } +} + +#[repr(C)] +pub struct Context { + stack: StackPointer, + pid: PID, +} + +#[repr(C)] +#[derive(Debug, PartialEq)] +/// A struct describing memory that is passed between processes. +/// The `buf` value will get translated as necessary. +pub struct MemoryMessage { + /// A user-assignable message ID. + pub id: MessageId, + + /// The offset of the buffer. This address will get transformed when the + /// message is moved between processes. + pub buf: MemoryRange, + + /// The offset within the buffer where the interesting stuff starts. + pub offset: Option, + + /// How many bytes in the buffer are valid + pub valid: Option, +} + +impl MemoryMessage { + pub fn from_usize( + id: usize, + addr: usize, + size: usize, + offset: usize, + valid: usize, + ) -> Option { + let addr = match MemoryAddress::new(addr) { + None => return None, + Some(s) => s, + }; + let size = match MemorySize::new(size) { + None => return None, + Some(s) => s, + }; + let buf = MemoryRange { addr, size }; + let offset = MemoryAddress::new(offset); + let valid = MemorySize::new(valid); + + Some(MemoryMessage { + id, + buf, + offset, + valid, + }) + } + pub fn to_usize(&self) -> [usize; 5] { + [ + self.id, + self.buf.addr.get(), + self.buf.size.get(), + self.offset.map(|e| e.get()).unwrap_or(0), + self.valid.map(|e| e.get()).unwrap_or(0), + ] + } +} + +#[repr(C)] +#[derive(Debug, PartialEq, Clone, Copy)] +/// A simple scalar message. This is similar to a `move` message. +pub struct ScalarMessage { + pub id: MessageId, + pub arg1: usize, + pub arg2: usize, + pub arg3: usize, + pub arg4: usize, +} + +impl ScalarMessage { + pub fn from_usize( + id: usize, + arg1: usize, + arg2: usize, + arg3: usize, + arg4: usize, + ) -> ScalarMessage { + ScalarMessage { + id, + arg1, + arg2, + arg3, + arg4, + } + } + pub fn to_usize(&self) -> [usize; 5] { + [self.id, self.arg1, self.arg2, self.arg3, self.arg4] + } +} + +#[repr(usize)] +#[derive(Debug, PartialEq)] +pub enum Message { + MutableBorrow(MemoryMessage), + Borrow(MemoryMessage), + Move(MemoryMessage), + Scalar(ScalarMessage), + BlockingScalar(ScalarMessage), +} + +impl Message { + /// Determine whether the specified Message will block + pub fn is_blocking(&self) -> bool { + match *self { + Message::MutableBorrow(_) | Message::Borrow(_) | Message::BlockingScalar(_) => true, + Message::Move(_) | Message::Scalar(_) => false, + } + } + + /// Determine whether the specified message has data attached + pub fn has_memory(&self) -> bool { + match *self { + Message::MutableBorrow(_) | Message::Borrow(_) | Message::Move(_) => true, + Message::BlockingScalar(_) | Message::Scalar(_) => false, + } + } + + pub fn memory(&self) -> Option<&MemoryRange> { + match self { + Message::MutableBorrow(mem) | Message::Borrow(mem) | Message::Move(mem) => { + Some(&mem.buf) + } + Message::BlockingScalar(_) | Message::Scalar(_) => None, + } + } + + pub fn message_type(&self) -> usize { + match *self { + Message::MutableBorrow(_) => 1, + Message::Borrow(_) => 2, + Message::Move(_) => 3, + Message::Scalar(_) => 4, + Message::BlockingScalar(_) => 5, + } + } +} + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub struct MessageEnvelope { + pub sender: MessageSender, + pub body: Message, +} + +impl MessageEnvelope { + pub fn to_usize(&self) -> [usize; 7] { + let ret = match &self.body { + Message::MutableBorrow(m) => (0, m.to_usize()), + Message::Borrow(m) => (1, m.to_usize()), + Message::Move(m) => (2, m.to_usize()), + Message::Scalar(m) => (3, m.to_usize()), + Message::BlockingScalar(m) => (4, m.to_usize()), + }; + [ + self.sender.to_usize(), + ret.0, + ret.1[0], + ret.1[1], + ret.1[2], + ret.1[3], + ret.1[4], + ] + } +} + +#[cfg(not(feature = "forget-memory-messages"))] +/// When a MessageEnvelope goes out of scope, return the memory. It must either +/// go to the kernel (in the case of a Move), or back to the borrowed process +/// (in the case of a Borrow). Ignore Scalar messages. +impl Drop for MessageEnvelope { + fn drop(&mut self) { + match &self.body { + Message::Borrow(x) | Message::MutableBorrow(x) => { + crate::syscall::return_memory(self.sender, x.buf).expect("couldn't return memory") + } + Message::Move(msg) => { + crate::syscall::unmap_memory(msg.buf).expect("couldn't free memory message") + } + _ => (), + } + } +} + +impl MemoryRange { + pub fn new(addr: usize, size: usize) -> core::result::Result { + assert!( + addr != 0, + "tried to construct a memory range with a null pointer" + ); + assert!(size != 0, "tried to construct a zero-length memory range"); + Ok(MemoryRange { + addr: MemoryAddress::new(addr).ok_or(Error::BadAddress)?, + size: MemorySize::new(size).ok_or(Error::BadAddress)?, + }) + } + + pub fn from_parts(addr: MemoryAddress, size: MemorySize) -> MemoryRange { + MemoryRange { addr, size } + } + + pub fn len(&self) -> usize { + self.size.get() + } + + pub fn is_empty(&self) -> bool { + self.size.get() > 0 + } + + pub fn as_ptr(&self) -> *const u8 { + self.addr.get() as *const u8 + } + + pub fn as_mut_ptr(&self) -> *mut u8 { + self.addr.get() as *mut u8 + } +} + +/// Which memory region the operation should affect. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum MemoryType { + /// The address where addresses go when no `virt` is specified. + Default = 1, + + /// Addresses will begin here when `IncreaseHeap` is called. + Heap = 2, + + /// When messages are passed to a process, they will go here. + Messages = 3, + + /// Unlike other memory types, this defines the "end" of the region. + Stack = 4, +} + +impl From for MemoryType { + fn from(arg: usize) -> Self { + match arg { + 2 => MemoryType::Heap, + 3 => MemoryType::Messages, + 4 => MemoryType::Stack, + _ => MemoryType::Default, + } + } +} + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub enum Result { + Ok, + Error(Error), + MemoryAddress(MemoryAddress), + MemoryRange(MemoryRange), + ReadyThreads( + usize, /* count */ + usize, + /* pid0 */ usize, /* context0 */ + usize, + /* pid1 */ usize, /* context1 */ + usize, + /* pid2 */ usize, /* context2 */ + ), + ResumeProcess, + ServerID(SID), + ConnectionID(CID), + NewServerID(SID, CID), + Message(MessageEnvelope), + ThreadID(TID), + ProcessID(PID), + + /// The requested system call is unimplemented + Unimplemented, + + /// The process is blocked and should perform the read() again. This is only + /// ever seen in `Hosted` mode, because when running natively the kernel + /// simply never schedules the process. + BlockedProcess, + + /// A scalar with one value + Scalar1(usize), + + /// A scalar with two values + Scalar2(usize, usize), + + /// The syscall should be attempted again. This is returned when calling + /// functions such as `try_connect()` and `try_send()` that may block. + WouldBlock, + + /// The message was successful but no value was returned. + None, + + UnknownResult(usize, usize, usize, usize, usize, usize, usize), +} + +impl Result { + pub fn to_args(&self) -> [usize; 8] { + match self { + Result::Ok => [0, 0, 0, 0, 0, 0, 0, 0], + Result::Error(e) => [1, e.to_usize(), 0, 0, 0, 0, 0, 0], + Result::MemoryAddress(s) => [2, s.get(), 0, 0, 0, 0, 0, 0], + Result::MemoryRange(r) => [3, r.addr.get(), r.size.get(), 0, 0, 0, 0, 0], + Result::ReadyThreads(count, pid0, ctx0, pid1, ctx1, pid2, ctx2) => { + [4, *count, *pid0, *ctx0, *pid1, *ctx1, *pid2, *ctx2] + } + Result::ResumeProcess => [5, 0, 0, 0, 0, 0, 0, 0], + Result::ServerID(sid) => { + let s = sid.to_u32(); + [6, s.0 as _, s.1 as _, s.2 as _, s.3 as _, 0, 0, 0] + } + Result::ConnectionID(cid) => [7, *cid, 0, 0, 0, 0, 0, 0], + Result::Message(me) => { + let me_enc = me.to_usize(); + [ + 8, me_enc[0], me_enc[1], me_enc[2], me_enc[3], me_enc[4], me_enc[5], me_enc[6], + ] + } + Result::ThreadID(ctx) => [9, *ctx as usize, 0, 0, 0, 0, 0, 0], + Result::ProcessID(pid) => [10, pid.get() as _, 0, 0, 0, 0, 0, 0], + Result::Unimplemented => [11, 0, 0, 0, 0, 0, 0, 0], + Result::BlockedProcess => [12, 0, 0, 0, 0, 0, 0, 0], + Result::Scalar1(a) => [13, *a, 0, 0, 0, 0, 0, 0], + Result::Scalar2(a, b) => [14, *a, *b, 0, 0, 0, 0, 0], + Result::NewServerID(sid, cid) => { + let s = sid.to_u32(); + [15, s.0 as _, s.1 as _, s.2 as _, s.3 as _, *cid, 0, 0] + } + Result::WouldBlock => [16, 0, 0, 0, 0, 0, 0, 0], + Result::None => [17, 0, 0, 0, 0, 0, 0, 0], + Result::UnknownResult(arg1, arg2, arg3, arg4, arg5, arg6, arg7) => { + [usize::MAX, *arg1, *arg2, *arg3, *arg4, *arg5, *arg6, *arg7] + } + } + } + + pub fn from_args(src: [usize; 8]) -> Self { + match src[0] { + 0 => Result::Ok, + 1 => Result::Error(Error::from_usize(src[1])), + 2 => match MemoryAddress::new(src[1]) { + None => Result::Error(Error::InternalError), + Some(s) => Result::MemoryAddress(s), + }, + 3 => { + let addr = match MemoryAddress::new(src[1]) { + None => return Result::Error(Error::InternalError), + Some(s) => s, + }; + let size = match MemorySize::new(src[2]) { + None => return Result::Error(Error::InternalError), + Some(s) => s, + }; + + Result::MemoryRange(MemoryRange { addr, size }) + } + 4 => Result::ReadyThreads(src[1], src[2], src[3], src[4], src[5], src[6], src[7]), + 5 => Result::ResumeProcess, + 6 => Result::ServerID(SID::from_u32( + src[1] as _, + src[2] as _, + src[3] as _, + src[4] as _, + )), + 7 => Result::ConnectionID(src[1] as CID), + 8 => { + let sender = src[1]; + let message = match src[2] { + 0 => match MemoryMessage::from_usize(src[3], src[4], src[5], src[6], src[7]) { + None => return Result::Error(Error::InternalError), + Some(s) => Message::MutableBorrow(s), + }, + 1 => match MemoryMessage::from_usize(src[3], src[4], src[5], src[6], src[7]) { + None => return Result::Error(Error::InternalError), + Some(s) => Message::Borrow(s), + }, + 2 => match MemoryMessage::from_usize(src[3], src[4], src[5], src[6], src[7]) { + None => return Result::Error(Error::InternalError), + Some(s) => Message::Move(s), + }, + 3 => Message::Scalar(ScalarMessage::from_usize( + src[3], src[4], src[5], src[6], src[7], + )), + 4 => Message::BlockingScalar(ScalarMessage::from_usize( + src[3], src[4], src[5], src[6], src[7], + )), + _ => return Result::Error(Error::InternalError), + }; + Result::Message(MessageEnvelope { + sender: MessageSender::from_usize(sender), + body: message, + }) + } + 9 => Result::ThreadID(src[1] as TID), + 10 => Result::ProcessID(PID::new(src[1] as _).unwrap()), + 11 => Result::Unimplemented, + 12 => Result::BlockedProcess, + 13 => Result::Scalar1(src[1]), + 14 => Result::Scalar2(src[1], src[2]), + 15 => Result::NewServerID( + SID::from_u32(src[1] as _, src[2] as _, src[3] as _, src[4] as _), + src[5] as _, + ), + 16 => Result::WouldBlock, + 17 => Result::None, + _ => Result::UnknownResult(src[0], src[1], src[2], src[3], src[4], src[5], src[6]), + } + } + + /// If the Result has memory attached to it, return the memory + pub fn memory(&self) -> Option { + match self { + Result::Message(msg) => match &msg.body { + Message::Move(memory_message) + | Message::Borrow(memory_message) + | Message::MutableBorrow(memory_message) => Some(memory_message.buf), + _ => None, + }, + _ => None, + } + } +} + +impl From for Result { + fn from(e: Error) -> Self { + Result::Error(e) + } +} + +pub type SysCallRequest = core::result::Result; +pub type SysCallResult = core::result::Result; diff --git a/xous-rs/src/ipc.rs b/xous-rs/src/ipc.rs new file mode 100644 index 0000000..1c7f943 --- /dev/null +++ b/xous-rs/src/ipc.rs @@ -0,0 +1,269 @@ +// use core::mem::MaybeUninit; +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::marker::{PhantomData, Unpin}; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; + +#[derive(PartialEq, Debug)] +enum MemoryState { + /// This struct is available for lending and sending + Available, + + /// This memory has been mutably lent out and will be returned + MutablyLent, + + /// This memory was moved, and should be forgotten + Moved, +} + +// #[repr(C)] +pub struct UninitializedSendable { + contents: NonNull, +} + +pub struct Sendable { + contents: *mut T, + total_size: usize, + memory_state: MemoryState, + // NOTE: this marker has no consequences for variance, but is necessary + // for dropck to understand that we logically own a `T`. + // + // For details, see: + // https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data + _marker: PhantomData, +} + +impl UninitializedSendable { + fn type_size() -> usize { + let type_size = core::mem::size_of::(); + let remainder = type_size & 4095; + type_size + (4096 - remainder) + } + + pub fn new(val: T) -> Result, crate::Error> + where + T: Unpin, + { + let uninitialized = Self::uninit()?; + let type_size = core::mem::size_of::(); + unsafe { + let src_slice = core::slice::from_raw_parts(&val as *const _ as *const u8, type_size); + let dest_slice = core::slice::from_raw_parts_mut( + uninitialized.contents.as_ptr() as *mut u8, + type_size, + ); + for (src, dest) in src_slice.iter().zip(dest_slice) { + *dest = *src; + } + } + Ok(uninitialized) + } + + pub fn uninit() -> Result, crate::Error> { + let padded_size = Self::type_size(); + + // Ensure this object takes up exactly a multiple of a page. This + // ensures it can be sent to another process. + let new_mem = crate::map_memory( + None, + None, + padded_size, + crate::MemoryFlags::R | crate::MemoryFlags::W, + )?; + + let contents = unsafe { NonNull::new_unchecked(new_mem.as_mut_ptr() as *mut T) }; + Ok(UninitializedSendable { contents }) + } + + pub unsafe fn assume_init(self) -> Sendable { + Sendable { + contents: self.contents.as_ptr(), + total_size: Self::type_size(), + memory_state: MemoryState::Available, + _marker: PhantomData, + } + } +} + +impl Default for Sendable { + /// Creates a `Box`, with the `Default` value for T. + fn default() -> Sendable { + Sendable::new(Default::default()).unwrap() + } +} + +// impl Default for Sendable<[T]> { +// fn default() -> Sendable<[T]> { +// Sendable::<[T; 0]>::new([]) +// } +// } + +/// `Unique` pointers are `Send` if `T` is `Send` because the data they +/// reference is unaliased. Note that this aliasing invariant is +/// unenforced by the type system; the abstraction using the +/// `Unique` must enforce it. +unsafe impl Send for Sendable {} + +/// `Unique` pointers are `Sync` if `T` is `Sync` because the data they +/// reference is unaliased. Note that this aliasing invariant is +/// unenforced by the type system; the abstraction using the +/// `Unique` must enforce it. +unsafe impl Sync for Sendable {} + +impl Sendable { + pub fn new(val: T) -> Result, crate::Error> + where + T: Unpin, + { + Ok(unsafe { UninitializedSendable::new(val)?.assume_init() }) + } + + /// Perform an immutable lend of this Carton to the specified server. + /// This function will block until the server returns. + pub fn lend(&self, connection: crate::CID, id: usize) -> Result { + let buf = crate::MemoryRange::new(self.contents as usize, self.total_size)?; + let msg = crate::MemoryMessage { + id, + buf, + offset: None, + valid: crate::MemorySize::new(core::mem::size_of::()), + }; + crate::send_message(connection, crate::Message::Borrow(msg)) + } + + /// Perform a mutable lend of this Carton to the server. + pub fn lend_mut( + &mut self, + connection: crate::CID, + id: usize, + ) -> Result { + let buf = crate::MemoryRange::new(self.contents as usize, self.total_size)?; + let msg = crate::MemoryMessage { + id, + buf, + offset: None, + valid: crate::MemorySize::new(core::mem::size_of::()), + }; + self.memory_state = MemoryState::MutablyLent; + let result = crate::send_message(connection, crate::Message::MutableBorrow(msg)); + self.memory_state = MemoryState::Available; + result + } + + /// Perform a mutable lend of this Carton to the server. + pub fn send( + mut self, + connection: crate::CID, + id: usize, + ) -> Result { + let buf = crate::MemoryRange::new(self.contents as usize, self.total_size)?; + let msg = crate::MemoryMessage { + id, + buf, + offset: None, + valid: crate::MemorySize::new(core::mem::size_of::()), + }; + let result = crate::send_message(connection, crate::Message::Move(msg))?; + + // Mark this state as Moved, which prevents it from being Dropped. + self.memory_state = MemoryState::Moved; + Ok(result) + } +} + +impl Deref for Sendable { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.contents } + } +} + +impl DerefMut for Sendable { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.contents } + } +} + +// Display formatting +impl fmt::Display for Sendable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl fmt::Debug for Sendable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Pointer for Sendable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // It's not possible to extract the inner Uniq directly from the Box, + // instead we cast it to a *const which aliases the Unique + let ptr: *const T = &**self; + fmt::Pointer::fmt(&ptr, f) + } +} + +impl PartialEq for Sendable { + #[inline] + fn eq(&self, other: &Sendable) -> bool { + PartialEq::eq(&**self, &**other) + } + #[inline] + fn ne(&self, other: &Sendable) -> bool { + PartialEq::ne(&**self, &**other) + } +} + +impl PartialOrd for Sendable { + #[inline] + fn partial_cmp(&self, other: &Sendable) -> Option { + PartialOrd::partial_cmp(&**self, &**other) + } + #[inline] + fn lt(&self, other: &Sendable) -> bool { + PartialOrd::lt(&**self, &**other) + } + #[inline] + fn le(&self, other: &Sendable) -> bool { + PartialOrd::le(&**self, &**other) + } + #[inline] + fn ge(&self, other: &Sendable) -> bool { + PartialOrd::ge(&**self, &**other) + } + #[inline] + fn gt(&self, other: &Sendable) -> bool { + PartialOrd::gt(&**self, &**other) + } +} + +impl Ord for Sendable { + #[inline] + fn cmp(&self, other: &Sendable) -> Ordering { + Ord::cmp(&**self, &**other) + } +} + +impl Eq for Sendable {} + +impl Hash for Sendable { + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +impl Drop for Sendable { + fn drop(&mut self) { + if self.memory_state != MemoryState::Available { + panic!("invalid memory state: {:?}", self.memory_state); + } + let range = crate::MemoryRange::new(self.contents as usize, self.total_size).unwrap(); + crate::unmap_memory(range).unwrap(); + } +} diff --git a/xous-rs/src/lib.rs b/xous-rs/src/lib.rs new file mode 100644 index 0000000..dc900a3 --- /dev/null +++ b/xous-rs/src/lib.rs @@ -0,0 +1,163 @@ +#![cfg_attr(target_os = "none", no_std)] + +#[macro_use] +extern crate bitflags; + +extern crate xous_macros as macros; + +pub use macros::xous_main; + +pub mod arch; + +pub mod carton; +pub mod definitions; +pub mod ipc; +mod messages; +pub mod string; +pub mod syscall; + +pub use arch::{ProcessArgs, ProcessInit, ProcessKey, ThreadInit}; +pub use definitions::*; +pub use messages::*; +pub use string::*; +pub use syscall::*; + +#[cfg(not(target_os = "none"))] +pub use arch::ProcessArgsAsThread; + +#[cfg(target_os = "none")] +pub fn init() {} + +#[cfg(not(target_os = "none"))] +pub fn init() { + use std::panic; + panic::set_hook(Box::new(|arg| { + println!("PANIC!"); + println!("Details: {:?}", arg); + // debug_here::debug_here!(); + })); +} + +/// Convert a four-letter string into a 32-bit int. +#[macro_export] +macro_rules! make_name { + ($fcc:expr) => {{ + let mut c: [u8; 4] = Default::default(); + c.copy_from_slice($fcc.as_bytes()); + u32::from_le_bytes(c) as usize + }}; +} + +#[cfg(not(target_os = "none"))] +#[macro_export] +macro_rules! maybe_main { + () => { + extern "Rust" { + fn xous_entry() -> !; + } + + fn main() { + xous::arch::ensure_connection().unwrap(); + unsafe { xous_entry() }; + } + }; +} + +#[cfg(target_os = "none")] +#[macro_export] +macro_rules! maybe_main { + () => { + use core::panic::PanicInfo; + + #[panic_handler] + fn handle_panic(arg: &PanicInfo) -> ! { + use core::fmt::Write; + use xous::{ + terminate_process, try_connect, try_send_message, wait_event, Message, + ScalarMessage, CID, SID, + }; + + // Try to connect to the log server. If this fails, we won't be able to print + // anything anyway. + // We use `try_connect()` here because we want this to work even during an interrupt handler. + // If we've already connected to the log server, then the kernel will reuse the + // connection number. + if let Ok(conn) = try_connect(SID::from_bytes(b"xous-log-server ").unwrap()) { + struct PanicWriter { + conn: CID, + } + impl PanicWriter { + // Group `usize` bytes into a `usize` and return it, beginning + // from `offset` * sizeof(usize) bytes from the start. For example, + // `group_or_null([1,2,3,4,5,6,7,8], 1)` on a 32-bit system will + // return a usize with 5678 packed into it. + fn group_or_null(data: &[u8], offset: usize) -> usize { + let start = offset * core::mem::size_of::(); + let mut out_array = [0u8; core::mem::size_of::()]; + for i in 0..core::mem::size_of::() { + out_array[i] = if i + start < data.len() { + data[start + i] + } else { + 0 + }; + } + usize::from_le_bytes(out_array) + } + } + impl core::fmt::Write for PanicWriter { + fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> { + for c in s.as_bytes().chunks(core::mem::size_of::() * 4) { + // Text is grouped into 4x `usize` words. The id is 1100 plus + // the number of characters in this message. + let mut panic_msg = ScalarMessage { + id: 1100 + c.len(), + arg1: Self::group_or_null(&c, 0), + arg2: Self::group_or_null(&c, 1), + arg3: Self::group_or_null(&c, 2), + arg4: Self::group_or_null(&c, 3), + }; + try_send_message(self.conn, Message::Scalar(panic_msg)).ok(); + } + Ok(()) + } + } + + let mut pw = PanicWriter { conn }; + // Send the "We're panicking" message (1000). + let panic_start_msg = ScalarMessage { + id: 1000, + arg1: 0, + arg2: 0, + arg3: 0, + arg4: 0, + }; + try_send_message(conn, Message::Scalar(panic_start_msg)).ok(); + + // Send the contents of the panic. + writeln!(&mut pw, "{}", arg).ok(); + + // Send the "We're done panicking now it's time to quit" message (1200) + let panic_start_msg = ScalarMessage { + id: 1200, + arg1: 0, + arg2: 0, + arg3: 0, + arg4: 0, + }; + try_send_message(conn, Message::Scalar(panic_start_msg)).ok(); + } + wait_event(); + terminate_process(); + loop {} + } + + extern "Rust" { + fn xous_entry() -> !; + } + + #[export_name = "_start"] + pub extern "C" fn _start() { + unsafe { xous_entry() }; + } + }; +} diff --git a/xous-rs/src/messages/mod.rs b/xous-rs/src/messages/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/xous-rs/src/string.rs b/xous-rs/src/string.rs new file mode 100644 index 0000000..003036c --- /dev/null +++ b/xous-rs/src/string.rs @@ -0,0 +1,108 @@ +use crate::{ + map_memory, send_message, Error, MemoryFlags, MemoryMessage, MemoryRange, MemorySize, Message, + Result, CID, +}; + +pub struct String<'a> { + raw_slice: &'a mut [u8], + s: &'a str, + len: usize, +} + +impl<'a> String<'a> { + pub fn new(max: usize) -> String<'a> { + let mem = map_memory(None, None, max, MemoryFlags::R | MemoryFlags::W).unwrap(); + let p = mem.as_mut_ptr(); + for i in 0..max { + unsafe { p.add(i).write_volatile(0) }; + } + String { + raw_slice: unsafe { core::slice::from_raw_parts_mut(mem.as_mut_ptr(), max) }, + s: unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts(mem.as_ptr(), 0)) + }, + len: 0, + } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Convert a `MemoryMessage` into a `String` + pub fn from_message(message: &'a mut MemoryMessage) -> core::result::Result, core::str::Utf8Error> { + let raw_slice = unsafe { core::slice::from_raw_parts_mut(message.buf.as_mut_ptr(), message.buf.len()) }; + let starting_length = message.valid.map(|x| x.get()).unwrap_or(0); + Ok(String { + raw_slice, + s: core::str::from_utf8(unsafe { core::slice::from_raw_parts(message.buf.as_ptr(), starting_length) })?, + len: 0, + }) + } + + /// Perform an immutable lend of this String to the specified server. + /// This function will block until the server returns. + pub fn lend(&self, connection: CID, id: crate::MessageId) -> core::result::Result { + let memory_range = + MemoryRange::new(self.raw_slice.as_ptr() as _, self.raw_slice.len()).unwrap(); + let msg = MemoryMessage { + id, + buf: memory_range, + offset: None, + valid: MemorySize::new(self.len).map(Some).unwrap_or(None), + }; + send_message(connection, Message::Borrow(msg)) + } + + /// Move this string from the client into the server. + pub fn send(self, connection: CID, id: crate::MessageId) -> core::result::Result { + let memory_range = + MemoryRange::new(self.raw_slice.as_ptr() as _, self.raw_slice.len()).unwrap(); + let msg = MemoryMessage { + id, + buf: memory_range, + offset: None, + valid: MemorySize::new(self.len).map(Some).unwrap_or(None), + }; + send_message(connection, Message::Move(msg)) + } + + /// Clear the contents of this String and set the length to 0 + pub fn clear(&mut self) { + self.len = 0; + self.s = unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts( + self.raw_slice.as_ptr(), + self.len, + )) + }; + } +} + +impl<'a> core::fmt::Display for String<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.s) + } +} + +impl<'a> core::fmt::Write for String<'a> { + fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> { + for c in s.bytes() { + if self.len < self.raw_slice.len() { + self.raw_slice[self.len] = c; + self.len += 1; + } + } + self.s = unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts( + self.raw_slice.as_ptr(), + self.len, + )) + }; + Ok(()) + } +} diff --git a/xous-rs/src/syscall.rs b/xous-rs/src/syscall.rs new file mode 100644 index 0000000..a30df89 --- /dev/null +++ b/xous-rs/src/syscall.rs @@ -0,0 +1,1352 @@ +use crate::{ + pid_from_usize, CpuID, Error, MemoryAddress, MemoryFlags, MemoryMessage, MemoryRange, + MemorySize, MemoryType, Message, MessageEnvelope, MessageSender, ProcessArgs, ProcessInit, + Result, ScalarMessage, SysCallResult, ThreadInit, CID, PID, SID, +}; +// use num_derive::FromPrimitive; +// use num_traits::FromPrimitive; + +#[cfg(not(target_os = "none"))] +use crate::ProcessArgsAsThread; + +#[derive(Debug, PartialEq)] +pub enum SysCall { + /// Allocates pages of memory, equal to a total of `size` bytes. A physical + /// address may be specified, which can be used to allocate regions such as + /// memory-mapped I/O. + /// + /// If a virtual address is specified, then the returned pages are located + /// at that address. Otherwise, they are located at the Default offset. + /// + /// # Errors + /// + /// * **BadAlignment**: Either the physical or virtual addresses aren't + /// page-aligned, or the size isn't a multiple of the + /// page width. + /// * **OutOfMemory**: A contiguous chunk of memory couldn't be found, or + /// the system's memory size has been exceeded. + MapMemory( + Option, /* phys */ + Option, /* virt */ + MemorySize, /* region size */ + MemoryFlags, /* flags */ + ), + + /// Release the memory back to the operating system. + /// + /// # Errors + /// + UnmapMemory(MemoryRange), + + /// Sets the offset and size of a given memory region. This call may only + /// be made by processes that have not yet started, or processes that have a + /// PPID of 1. Care must be taken to ensure this region doesn't run into + /// other regions. Additionally, the base address must avoid the kernel + /// regions. + /// + /// # Errors + /// + /// * **BadAlignment**: Either the physical or virtual addresses aren't + /// page-aligned, or the size isn't a multiple of the + /// page width. + /// * **BadAddress**: The address conflicts with the kernel + SetMemRegion( + PID, /* pid */ + MemoryType, /* region type */ + MemoryAddress, /* region address */ + usize, /* region size */ + ), + + /// Add the given number of bytes to the heap. The number of bytes must be + /// divisible by the page size. The newly-allocated pages will have the + /// specified flags. To get the current heap base, call this with a size of + /// `0`. + /// + /// # Returns + /// + /// * **MemoryRange(*mut usize /* The base of the heap */, usize /* the new + /// size of the heap */) + /// + /// # Errors + /// + /// * **BadAlignment**: Either the physical or virtual addresses aren't + /// page-aligned, or the size isn't a multiple of the + /// page width. + /// * **OutOfMemory**: A contiguous chunk of memory couldn't be found, or + /// the system's memory size has been exceeded. + IncreaseHeap(usize /* number of bytes to add */, MemoryFlags), + + /// Remove the given number of bytes from the heap. + /// + /// # Returns + /// + /// * **MemoryRange(*mut usize /* The base of the heap */, usize /* the new + /// size of the heap */) + /// + /// # Errors + /// + /// * **BadAlignment**: Either the physical or virtual addresses aren't + /// page-aligned, or the size isn't a multiple of the + /// page width. + /// * **OutOfMemory**: A contiguous chunk of memory couldn't be found, or + /// the system's memory size has been exceeded. + DecreaseHeap(usize /* desired heap size */), + + /// Set the specified flags on the virtual address range. This can be used + /// to REMOVE flags on a memory region, for example to mark it as no-execute + /// after writing program data. + /// + /// # Errors + /// + /// * **ProcessNotChild**: The given PID is not a child of the current + /// process. + /// * **MemoryInUse**: The given PID has already been started, and it is not + /// legal to modify memory flags anymore. + UpdateMemoryFlags( + MemoryAddress, /* virt */ + usize, /* number of pages */ + MemoryFlags, /* new flags */ + ), + + /// Pauses execution of the current thread and returns execution to the parent + /// process. This may return at any time in the future, including immediately. + Yield, + + /// This process will now wait for an event such as an IRQ or Message. + WaitEvent, + + /// This thread will now wait for a message with the given server ID. You + /// can set up a pool by having multiple threads call `ReceiveMessage` with + /// the same SID. + ReceiveMessage(SID), + + /// If a message is available for the specified server, return that message + /// and resume execution. If no message is available, return `Result::None` + /// immediately without blocking. + TryReceiveMessage(SID), + + /// Stop running the given process and return control to the parent. This + /// will force a Yield on the process currently running on the target CPU. + /// This can be run during an Interrupt context. + /// + /// # Errors + /// + /// * **ProcessNotChild**: The given PID is not a child of the current + /// process + ReturnToParent(PID, CpuID), + + /// Claims an interrupt and unmasks it immediately. The provided function + /// will be called from within an interrupt context, but using the ordinary + /// privilege level of the process. + /// + /// # Errors + /// + /// * **InterruptNotFound**: The specified interrupt isn't valid on this + /// system + /// * **InterruptInUse**: The specified interrupt has already been claimed + ClaimInterrupt( + usize, /* IRQ number */ + MemoryAddress, /* function pointer */ + Option, /* argument */ + ), + + /// Returns the interrupt back to the operating system and masks it again. + /// This function is implicitly called when a process exits. + /// + /// # Errors + /// + /// * **InterruptNotFound**: The specified interrupt doesn't exist, or isn't + /// assigned to this process. + FreeInterrupt(usize /* IRQ number */), + + /// Resumes a process using the given context. A parent could use this + /// function to implement multi-threading inside a child process, or to + /// create a task switcher. + /// + /// To resume a process exactly where it left off, set `context_id` to `0`. + /// This would be done in a very simple system that has no threads. + /// + /// If no more contexts are available when one is required, then the child + /// automatically relinquishes its quantum. + /// + /// # Returns + /// + /// When this function returns, it provides a list of the processes and + /// contexts that are ready to be run. Three can fit as return values. + /// + /// # Examples + /// + /// If a process called `yield()`, or if its quantum expired normally, then + /// a single pair is returned: (pid, context). + /// + /// If the child process called `client_send()` and ended up blocking due to + /// the server not being ready, then this would return no pairs. This thread + /// or process should not be scheduled to run. + /// + /// If the child called `client_send()` and the server was ready, then the + /// server process would be run immediately. If the child process' quantum + /// expired while the server was running, then this function would return a + /// single pair containing the PID of the server, and the context number. + /// + /// If the child called `client_send()` and the server was ready, then the + /// server process would be run immediately. If the server then finishes, + /// execution flow is returned to the child process. If the quantum then + /// expires, this would return two pairs: the server's PID and its context + /// when it called `client_reply()`, and the child's PID with its current + /// context. + /// + /// If the server in turn called another server, and both servers ended up + /// returning to the child before the quantum expired, then there would be + /// three pairs returned. + /// + /// # Errors + /// + /// * **ProcessNotFound**: The requested process does not exist + /// * **ProcessNotChild**: The given process was not a child process, and + /// therefore couldn't be resumed. + /// * **ProcessTerminated**: The process has crashed. + SwitchTo(PID, usize /* context ID */), + + /// Get a list of contexts that can be run in the given PID. + ReadyThreads(PID), + + /// Create a new Server with a specified address + /// + /// This will return a 128-bit Server ID that can be used to send messages + /// to this server, as well as a connection ID. This connection ID will be + /// unique per process, while the server ID is available globally. + /// + /// # Returns + /// + /// The specified SID, along with the connection ID for this process to talk + /// to the server. + /// + /// # Errors + /// + /// * **OutOfMemory**: The server table was full and a new server couldn't + /// be created. + /// * **ServerExists**: The server hash is already in use. + CreateServerWithAddress(SID /* server hash */), + + /// Connect to a server. This turns a 128-bit Serever ID into a 32-bit + /// Connection ID. Blocks until the server is available. + /// + /// # Errors + /// + /// None + Connect(SID /* server id */), + + /// Try to connect to a server. This turns a 128-bit Serever ID into a 32-bit + /// Connection ID. + /// + /// # Errors + /// + /// * **ServerNotFound**: The server could not be found. + TryConnect(SID /* server id */), + + /// Send a message to a server (blocking until it's ready) + SendMessage(CID, Message), + + /// Try to send a message to a server + TrySendMessage(CID, Message), + + /// Return a Borrowed memory region to the sender + ReturnMemory(MessageSender, MemoryRange), + + /// Return a scalar to the sender + ReturnScalar1(MessageSender, usize), + + /// Return two scalars to the sender + ReturnScalar2(MessageSender, usize, usize), + + /// Spawn a new thread + CreateThread(ThreadInit), + + /// Create a new process, setting the current process as the parent ID. + /// Does not start the process immediately. + CreateProcess(ProcessInit), + + /// Terminate the current process, closing all server connections. + TerminateProcess, + + /// Shut down the entire system + Shutdown, + + /// Create a new Server + /// + /// This will return a 128-bit Server ID that can be used to send messages + /// to this server. The returned Derver ID is random. + /// + /// # Returns + /// + /// The SID, along with a Connection ID that can be used to immediately + /// communicate with this process. + /// + /// # Errors + /// + /// * **OutOfMemory**: The server table was full and a new server couldn't + /// be created. + CreateServer, + + /// Establish a connection in the given process to the given server. This + /// call can be used by a nameserver to make server connections without + /// disclosing SIDs. + ConnectForProcess(PID, SID), + + /// This syscall does not exist. It captures all possible + /// arguments so detailed analysis can be performed. + Invalid(usize, usize, usize, usize, usize, usize, usize), +} + +// #[derive(FromPrimitive)] +pub enum SysCallNumber { + MapMemory = 2, + Yield = 3, + ReturnToParent = 4, + ClaimInterrupt = 5, + FreeInterrupt = 6, + SwitchTo = 7, + ReadyThreads = 8, + WaitEvent = 9, + IncreaseHeap = 10, + DecreaseHeap = 11, + UpdateMemoryFlags = 12, + SetMemRegion = 13, + CreateServerWithAddress = 14, + ReceiveMessage = 15, + SendMessage = 16, + Connect = 17, + CreateThread = 18, + UnmapMemory = 19, + ReturnMemory = 20, + CreateProcess = 21, + TerminateProcess = 22, + Shutdown = 23, + TrySendMessage = 24, + TryConnect = 25, + ReturnScalar1 = 26, + ReturnScalar2 = 27, + TryReceiveMessage = 28, + CreateServer = 29, + ConnectForProcess = 30, + Invalid, +} + +impl SysCallNumber { + pub fn from(val: usize) -> SysCallNumber { + use SysCallNumber::*; + match val { + 2 => MapMemory, + 3 => Yield, + 4 => ReturnToParent, + 5 => ClaimInterrupt, + 6 => FreeInterrupt, + 7 => SwitchTo, + 8 => ReadyThreads, + 9 => WaitEvent, + 10 => IncreaseHeap, + 11 => DecreaseHeap, + 12 => UpdateMemoryFlags, + 13 => SetMemRegion, + 14 => CreateServerWithAddress, + 15 => ReceiveMessage, + 16 => SendMessage, + 17 => Connect, + 18 => CreateThread, + 19 => UnmapMemory, + 20 => ReturnMemory, + 21 => CreateProcess, + 22 => TerminateProcess, + 23 => Shutdown, + 24 => TrySendMessage, + 25 => TryConnect, + 26 => ReturnScalar1, + 27 => ReturnScalar2, + 28 => TryReceiveMessage, + 29 => CreateServer, + 30 => ConnectForProcess, + _ => Invalid, + } + } +} + +impl SysCall { + /// Convert the SysCall into an array of eight `usize` elements, + /// suitable for passing to the kernel. + pub fn as_args(&self) -> [usize; 8] { + use core::mem; + assert!( + mem::size_of::() == mem::size_of::() * 8, + "SysCall is not the expected size (expected {}, got {})", + mem::size_of::() * 8, + mem::size_of::() + ); + match self { + SysCall::MapMemory(a1, a2, a3, a4) => [ + SysCallNumber::MapMemory as usize, + a1.map(|x| x.get()).unwrap_or_default(), + a2.map(|x| x.get()).unwrap_or_default(), + a3.get(), + a4.bits(), + 0, + 0, + 0, + ], + SysCall::UnmapMemory(range) => [ + SysCallNumber::UnmapMemory as usize, + range.as_ptr() as usize, + range.len(), + 0, + 0, + 0, + 0, + 0, + ], + SysCall::Yield => [SysCallNumber::Yield as usize, 0, 0, 0, 0, 0, 0, 0], + SysCall::WaitEvent => [SysCallNumber::WaitEvent as usize, 0, 0, 0, 0, 0, 0, 0], + SysCall::ReceiveMessage(sid) => { + let s = sid.to_u32(); + [ + SysCallNumber::ReceiveMessage as usize, + s.0 as _, + s.1 as _, + s.2 as _, + s.3 as _, + 0, + 0, + 0, + ] + } + SysCall::TryReceiveMessage(sid) => { + let s = sid.to_u32(); + [ + SysCallNumber::TryReceiveMessage as usize, + s.0 as _, + s.1 as _, + s.2 as _, + s.3 as _, + 0, + 0, + 0, + ] + } + SysCall::ConnectForProcess(pid, sid) => { + let s = sid.to_u32(); + [ + SysCallNumber::ConnectForProcess as usize, + pid.get() as _, + s.0 as _, + s.1 as _, + s.2 as _, + s.3 as _, + 0, + 0, + ] + } + SysCall::ReturnToParent(a1, a2) => [ + SysCallNumber::ReturnToParent as usize, + a1.get() as usize, + *a2 as usize, + 0, + 0, + 0, + 0, + 0, + ], + SysCall::ClaimInterrupt(a1, a2, a3) => [ + SysCallNumber::ClaimInterrupt as usize, + *a1, + a2.get(), + a3.map(|x| x.get()).unwrap_or_default(), + 0, + 0, + 0, + 0, + ], + SysCall::FreeInterrupt(a1) => { + [SysCallNumber::FreeInterrupt as usize, *a1, 0, 0, 0, 0, 0, 0] + } + SysCall::SwitchTo(a1, a2) => [ + SysCallNumber::SwitchTo as usize, + a1.get() as usize, + *a2 as usize, + 0, + 0, + 0, + 0, + 0, + ], + SysCall::ReadyThreads(a1) => [ + SysCallNumber::ReadyThreads as usize, + a1.get() as usize, + 0, + 0, + 0, + 0, + 0, + 0, + ], + SysCall::IncreaseHeap(a1, a2) => [ + SysCallNumber::IncreaseHeap as usize, + *a1 as usize, + a2.bits(), + 0, + 0, + 0, + 0, + 0, + ], + SysCall::DecreaseHeap(a1) => [ + SysCallNumber::DecreaseHeap as usize, + *a1 as usize, + 0, + 0, + 0, + 0, + 0, + 0, + ], + SysCall::UpdateMemoryFlags(a1, a2, a3) => [ + SysCallNumber::UpdateMemoryFlags as usize, + a1.get(), + *a2 as usize, + a3.bits(), + 0, + 0, + 0, + 0, + ], + SysCall::SetMemRegion(a1, a2, a3, a4) => [ + SysCallNumber::SetMemRegion as usize, + a1.get() as usize, + *a2 as usize, + a3.get(), + *a4, + 0, + 0, + 0, + ], + + SysCall::CreateServerWithAddress(sid) => { + let s = sid.to_u32(); + [ + SysCallNumber::CreateServerWithAddress as usize, + s.0 as _, + s.1 as _, + s.2 as _, + s.3 as _, + 0, + 0, + 0, + ] + } + SysCall::CreateServer => [SysCallNumber::CreateServer as usize, 0, 0, 0, 0, 0, 0, 0], + SysCall::Connect(sid) => { + let s = sid.to_u32(); + [ + SysCallNumber::Connect as usize, + s.0 as _, + s.1 as _, + s.2 as _, + s.3 as _, + 0, + 0, + 0, + ] + } + SysCall::SendMessage(a1, ref a2) => match a2 { + Message::MutableBorrow(mm) | Message::Borrow(mm) | Message::Move(mm) => [ + SysCallNumber::SendMessage as usize, + *a1, + a2.message_type(), + mm.id as usize, + mm.buf.as_ptr() as usize, + mm.buf.len(), + mm.offset.map(|x| x.get()).unwrap_or(0) as usize, + mm.valid.map(|x| x.get()).unwrap_or(0) as usize, + ], + Message::Scalar(sc) | Message::BlockingScalar(sc) => [ + SysCallNumber::SendMessage as usize, + *a1, + a2.message_type(), + sc.id as usize, + sc.arg1, + sc.arg2, + sc.arg3, + sc.arg4, + ], + }, + SysCall::ReturnMemory(sender, buf) => [ + SysCallNumber::ReturnMemory as usize, + sender.to_usize(), + buf.as_ptr() as usize, + buf.len(), + 0, + 0, + 0, + 0, + ], + SysCall::CreateThread(init) => { + crate::arch::thread_to_args(SysCallNumber::CreateThread as usize, init) + } + SysCall::CreateProcess(init) => { + crate::arch::process_to_args(SysCallNumber::CreateProcess as usize, init) + } + SysCall::TerminateProcess => [ + SysCallNumber::TerminateProcess as usize, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + SysCall::Shutdown => [SysCallNumber::Shutdown as usize, 0, 0, 0, 0, 0, 0, 0], + SysCall::TryConnect(sid) => { + let s = sid.to_u32(); + [ + SysCallNumber::TryConnect as usize, + s.0 as _, + s.1 as _, + s.2 as _, + s.3 as _, + 0, + 0, + 0, + ] + } + SysCall::TrySendMessage(a1, ref a2) => match a2 { + Message::MutableBorrow(mm) | Message::Borrow(mm) | Message::Move(mm) => [ + SysCallNumber::TrySendMessage as usize, + *a1, + a2.message_type(), + mm.id as usize, + mm.buf.as_ptr() as usize, + mm.buf.len(), + mm.offset.map(|x| x.get()).unwrap_or(0) as usize, + mm.valid.map(|x| x.get()).unwrap_or(0) as usize, + ], + Message::Scalar(sc) | Message::BlockingScalar(sc) => [ + SysCallNumber::TrySendMessage as usize, + *a1, + a2.message_type(), + sc.id as usize, + sc.arg1, + sc.arg2, + sc.arg3, + sc.arg4, + ], + }, + SysCall::ReturnScalar1(sender, arg1) => [ + SysCallNumber::ReturnScalar1 as usize, + sender.to_usize(), + *arg1, + 0, + 0, + 0, + 0, + 0, + ], + SysCall::ReturnScalar2(sender, arg1, arg2) => [ + SysCallNumber::ReturnScalar2 as usize, + sender.to_usize(), + *arg1, + *arg2, + 0, + 0, + 0, + 0, + ], + SysCall::Invalid(a1, a2, a3, a4, a5, a6, a7) => [ + SysCallNumber::Invalid as usize, + *a1, + *a2, + *a3, + *a4, + *a5, + *a6, + *a7, + ], + } + } + + #[allow(clippy::too_many_arguments)] + pub fn from_args( + a0: usize, + a1: usize, + a2: usize, + a3: usize, + a4: usize, + a5: usize, + a6: usize, + a7: usize, + ) -> core::result::Result { + Ok(match SysCallNumber::from(a0) { + SysCallNumber::MapMemory => SysCall::MapMemory( + MemoryAddress::new(a1), + MemoryAddress::new(a2), + MemoryAddress::new(a3).ok_or(Error::InvalidSyscall)?, + MemoryFlags::from_bits(a4).ok_or(Error::InvalidSyscall)?, + ), + SysCallNumber::UnmapMemory => { + SysCall::UnmapMemory(MemoryRange::new(a1, a2).or(Err(Error::InvalidSyscall))?) + } + SysCallNumber::Yield => SysCall::Yield, + SysCallNumber::WaitEvent => SysCall::WaitEvent, + SysCallNumber::ReceiveMessage => { + SysCall::ReceiveMessage(SID::from_u32(a1 as _, a2 as _, a3 as _, a4 as _)) + } + SysCallNumber::TryReceiveMessage => { + SysCall::TryReceiveMessage(SID::from_u32(a1 as _, a2 as _, a3 as _, a4 as _)) + } + SysCallNumber::ReturnToParent => SysCall::ReturnToParent(pid_from_usize(a1)?, a2), + SysCallNumber::ClaimInterrupt => SysCall::ClaimInterrupt( + a1, + MemoryAddress::new(a2).ok_or(Error::InvalidSyscall)?, + MemoryAddress::new(a3), + ), + SysCallNumber::FreeInterrupt => SysCall::FreeInterrupt(a1), + SysCallNumber::SwitchTo => SysCall::SwitchTo(pid_from_usize(a1)?, a2 as usize), + SysCallNumber::ReadyThreads => SysCall::ReadyThreads(pid_from_usize(a1)?), + SysCallNumber::IncreaseHeap => SysCall::IncreaseHeap( + a1 as usize, + MemoryFlags::from_bits(a2).ok_or(Error::InvalidSyscall)?, + ), + SysCallNumber::DecreaseHeap => SysCall::DecreaseHeap(a1 as usize), + SysCallNumber::UpdateMemoryFlags => SysCall::UpdateMemoryFlags( + MemoryAddress::new(a1).ok_or(Error::InvalidSyscall)?, + a2 as usize, + MemoryFlags::from_bits(a3).ok_or(Error::InvalidSyscall)?, + ), + SysCallNumber::SetMemRegion => SysCall::SetMemRegion( + pid_from_usize(a1)?, + MemoryType::from(a2), + MemoryAddress::new(a3).ok_or(Error::InvalidSyscall)?, + a4, + ), + SysCallNumber::CreateServerWithAddress => { + SysCall::CreateServerWithAddress(SID::from_u32(a1 as _, a2 as _, a3 as _, a4 as _)) + } + SysCallNumber::CreateServer => SysCall::CreateServer, + SysCallNumber::Connect => { + SysCall::Connect(SID::from_u32(a1 as _, a2 as _, a3 as _, a4 as _)) + } + SysCallNumber::SendMessage => match a2 { + 1 => SysCall::SendMessage( + a1, + Message::MutableBorrow(MemoryMessage { + id: a3, + buf: MemoryRange::new(a4, a5)?, + offset: MemoryAddress::new(a6), + valid: MemorySize::new(a7), + }), + ), + 2 => SysCall::SendMessage( + a1, + Message::Borrow(MemoryMessage { + id: a3, + buf: MemoryRange::new(a4, a5)?, + offset: MemoryAddress::new(a6), + valid: MemorySize::new(a7), + }), + ), + 3 => SysCall::SendMessage( + a1, + Message::Move(MemoryMessage { + id: a3, + buf: MemoryRange::new(a4, a5)?, + offset: MemoryAddress::new(a6), + valid: MemorySize::new(a7), + }), + ), + 4 => SysCall::SendMessage( + a1, + Message::Scalar(ScalarMessage { + id: a3, + arg1: a4, + arg2: a5, + arg3: a6, + arg4: a7, + }), + ), + 5 => SysCall::SendMessage( + a1, + Message::BlockingScalar(ScalarMessage { + id: a3, + arg1: a4, + arg2: a5, + arg3: a6, + arg4: a7, + }), + ), + _ => SysCall::Invalid(a1, a2, a3, a4, a5, a6, a7), + }, + SysCallNumber::ReturnMemory => { + SysCall::ReturnMemory(MessageSender::from_usize(a1), MemoryRange::new(a2, a3)?) + } + SysCallNumber::CreateThread => { + SysCall::CreateThread(crate::arch::args_to_thread(a1, a2, a3, a4, a5, a6, a7)?) + } + SysCallNumber::CreateProcess => { + SysCall::CreateProcess(crate::arch::args_to_process(a1, a2, a3, a4, a5, a6, a7)?) + } + SysCallNumber::TerminateProcess => SysCall::TerminateProcess, + SysCallNumber::Shutdown => SysCall::Shutdown, + SysCallNumber::TryConnect => { + SysCall::TryConnect(SID::from_u32(a1 as _, a2 as _, a3 as _, a4 as _)) + } + SysCallNumber::TrySendMessage => match a2 { + 1 => SysCall::TrySendMessage( + a1, + Message::MutableBorrow(MemoryMessage { + id: a3, + buf: MemoryRange::new(a4, a5)?, + offset: MemoryAddress::new(a6), + valid: MemorySize::new(a7), + }), + ), + 2 => SysCall::TrySendMessage( + a1, + Message::Borrow(MemoryMessage { + id: a3, + buf: MemoryRange::new(a4, a5)?, + offset: MemoryAddress::new(a6), + valid: MemorySize::new(a7), + }), + ), + 3 => SysCall::TrySendMessage( + a1, + Message::Move(MemoryMessage { + id: a3, + buf: MemoryRange::new(a4, a5)?, + offset: MemoryAddress::new(a6), + valid: MemorySize::new(a7), + }), + ), + 4 => SysCall::TrySendMessage( + a1, + Message::Scalar(ScalarMessage { + id: a3, + arg1: a4, + arg2: a5, + arg3: a6, + arg4: a7, + }), + ), + 5 => SysCall::TrySendMessage( + a1, + Message::BlockingScalar(ScalarMessage { + id: a3, + arg1: a4, + arg2: a5, + arg3: a6, + arg4: a7, + }), + ), + _ => SysCall::Invalid(a1, a2, a3, a4, a5, a6, a7), + }, + SysCallNumber::ReturnScalar1 => { + SysCall::ReturnScalar1(MessageSender::from_usize(a1), a2) + } + SysCallNumber::ReturnScalar2 => { + SysCall::ReturnScalar2(MessageSender::from_usize(a1), a2, a3) + } + SysCallNumber::ConnectForProcess => SysCall::ConnectForProcess( + PID::new(a1 as _).ok_or(Error::InvalidSyscall)?, + SID::from_u32(a2 as _, a3 as _, a4 as _, a5 as _), + ), + SysCallNumber::Invalid => SysCall::Invalid(a1, a2, a3, a4, a5, a6, a7), + }) + } + + /// Returns `true` if the associated syscall is a message that has memory attached to it + pub fn has_memory(&self) -> bool { + match self { + SysCall::TrySendMessage(_, msg) | SysCall::SendMessage(_, msg) => { + matches!(msg, Message::Move(_) | Message::Borrow(_) | Message::MutableBorrow(_)) + } + SysCall::ReturnMemory(_, _) => true, + _ => false, + } + } + + /// Returns `true` if the associated syscall is a message that is a Move + pub fn is_move(&self) -> bool { + match self { + SysCall::TrySendMessage(_, msg) | SysCall::SendMessage(_, msg) => { + matches!(msg, Message::Move(_)) + } + _ => false, + } + } + + /// Returns `true` if the associated syscall is a message that is a Borrow + pub fn is_borrow(&self) -> bool { + match self { + SysCall::TrySendMessage(_, msg) | SysCall::SendMessage(_, msg) => { + matches!(msg, Message::Borrow(_)) + } + _ => false, + } + } + + /// Returns `true` if the associated syscall is a message that is a MutableBorrow + pub fn is_mutableborrow(&self) -> bool { + match self { + SysCall::TrySendMessage(_, msg) | SysCall::SendMessage(_, msg) => { + matches!(msg, Message::MutableBorrow(_)) + } + _ => false, + } + } + + /// Returns `true` if the associated syscall is returning memory + pub fn is_return_memory(&self) -> bool { + matches!(self, SysCall::ReturnMemory(_, _)) + } + + /// If the syscall has memory attached to it, return the memory + pub fn memory(&self) -> Option { + match self { + SysCall::TrySendMessage(_, msg) | SysCall::SendMessage(_, msg) => match msg { + Message::Move(memory_message) + | Message::Borrow(memory_message) + | Message::MutableBorrow(memory_message) => Some(memory_message.buf), + _ => None, + }, + SysCall::ReturnMemory(_, range) => Some(*range), + _ => None, + } + } + + /// Returns `true` if the given syscall may be called from an IRQ context + pub fn can_call_from_interrupt(&self) -> bool { + matches!(self, SysCall::TrySendMessage(_, _) + | SysCall::TryConnect(_) + | SysCall::TryReceiveMessage(_) + | SysCall::ReturnToParent(_, _) + | SysCall::ReturnScalar2(_, _, _) + | SysCall::ReturnScalar1(_, _) + | SysCall::ReturnMemory(_, _)) + } +} + +extern "Rust" { + // fn _xous_syscall_rust( + // nr: usize, + // a1: usize, + // a2: usize, + // a3: usize, + // a4: usize, + // a5: usize, + // a6: usize, + // a7: usize, + // ret: &mut Result, + // ); + fn _xous_syscall( + nr: usize, + a1: usize, + a2: usize, + a3: usize, + a4: usize, + a5: usize, + a6: usize, + a7: usize, + ret: &mut Result, + ); +} + +/// Map the given physical address to the given virtual address. +/// The `size` field must be page-aligned. +pub fn map_memory( + phys: Option, + virt: Option, + size: usize, + flags: MemoryFlags, +) -> core::result::Result { + crate::arch::map_memory_pre(&phys, &virt, size, flags)?; + let result = rsyscall(SysCall::MapMemory( + phys, + virt, + MemorySize::new(size).ok_or(Error::InvalidSyscall)?, + flags, + ))?; + if let Result::MemoryRange(range) = result { + Ok(crate::arch::map_memory_post( + phys, virt, size, flags, range, + )?) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Map the given physical address to the given virtual address. +/// The `size` field must be page-aligned. +pub fn unmap_memory(range: MemoryRange) -> core::result::Result<(), Error> { + crate::arch::unmap_memory_pre(&range)?; + let result = rsyscall(SysCall::UnmapMemory(range))?; + if let crate::Result::Ok = result { + crate::arch::unmap_memory_post(range)?; + Ok(()) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Map the given physical address to the given virtual address. +/// The `size` field must be page-aligned. +pub fn return_memory(sender: MessageSender, mem: MemoryRange) -> core::result::Result<(), Error> { + let result = rsyscall(SysCall::ReturnMemory(sender, mem))?; + if let crate::Result::Ok = result { + Ok(()) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Map the given physical address to the given virtual address. +/// The `size` field must be page-aligned. +pub fn return_scalar(sender: MessageSender, val: usize) -> core::result::Result<(), Error> { + let result = rsyscall(SysCall::ReturnScalar1(sender, val))?; + if let crate::Result::Ok = result { + Ok(()) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Map the given physical address to the given virtual address. +/// The `size` field must be page-aligned. +pub fn return_scalar2( + sender: MessageSender, + val1: usize, + val2: usize, +) -> core::result::Result<(), Error> { + let result = rsyscall(SysCall::ReturnScalar2(sender, val1, val2))?; + if let crate::Result::Ok = result { + Ok(()) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Claim a hardware interrupt for this process. +pub fn claim_interrupt( + irq_no: usize, + callback: fn(irq_no: usize, arg: *mut usize), + arg: *mut usize, +) -> core::result::Result<(), Error> { + let result = rsyscall(SysCall::ClaimInterrupt( + irq_no, + MemoryAddress::new(callback as *mut usize as usize).ok_or(Error::InvalidSyscall)?, + MemoryAddress::new(arg as *mut usize as usize), + ))?; + if let crate::Result::Ok = result { + Ok(()) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Create a new server with the given name. This enables other processes to +/// connect to this server to send messages. The name is a UTF-8 token that +/// will be mixed with other random data that is unique to each process. +/// That way, if a process crashes and is restarted, it can keep the same +/// name. However, other processes cannot spoof this process. +/// +/// # Errors +/// +/// * **OutOfMemory**: No more servers may be created +/// * **ServerExists**: A server has already registered with that name +/// * **InvalidString**: The name was not a valid UTF-8 string +pub fn create_server_with_address(name_bytes: &[u8; 16]) -> core::result::Result { + let sid = SID::from_bytes(name_bytes).ok_or(Error::InvalidString)?; + + let result = rsyscall(SysCall::CreateServerWithAddress(sid))?; + if let Result::NewServerID(sid, _cid) = result { + Ok(sid) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Create a new server with a random name. This enables other processes to +/// connect to this server to send messages. A random server ID is generated +/// by the kernel and returned to the caller. This address can then be registered +/// to a namserver. +/// +/// # Errors +/// +/// * **OutOfMemory**: No more servers may be created +pub fn create_server() -> core::result::Result { + let result = rsyscall(SysCall::CreateServer)?; + if let Result::NewServerID(sid, _cid) = result { + Ok(sid) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Connect to a server with the given SID +pub fn connect(server: SID) -> core::result::Result { + let result = rsyscall(SysCall::Connect(server))?; + if let Result::ConnectionID(cid) = result { + Ok(cid) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Connect to a server with the given SID +pub fn try_connect(server: SID) -> core::result::Result { + let result = rsyscall(SysCall::TryConnect(server))?; + if let Result::ConnectionID(cid) = result { + Ok(cid) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Suspend the current process until a message is received. This thread will +/// block until a message is received. +/// +/// # Errors +/// +pub fn receive_message(server: SID) -> core::result::Result { + let result = rsyscall(SysCall::ReceiveMessage(server)).expect("Couldn't call ReceiveMessage"); + if let Result::Message(envelope) = result { + Ok(envelope) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Retrieve a message from the message queue for the provided server. If no message +/// is available, returns `Ok(None)` without blocking +/// +/// # Errors +/// +pub fn try_receive_message(server: SID) -> core::result::Result, Error> { + let result = + rsyscall(SysCall::TryReceiveMessage(server)).expect("Couldn't call ReceiveMessage"); + if let Result::Message(envelope) = result { + Ok(Some(envelope)) + } else if result == Result::None { + Ok(None) + } else if let Result::Error(e) = result { + Err(e) + } else { + Err(Error::InternalError) + } +} + +/// Send a message to a server. Depending on the mesage type (move or borrow), it +/// will either block (borrow) or return immediately (move). +/// If the message type is `borrow`, then the memory addresses pointed to will be +/// unavailable to this process until this function returns. +/// +/// # Errors +/// +/// * **ServerNotFound**: The server does not exist so the connection is now invalid +/// * **BadAddress**: The client tried to pass a Memory message using an address it doesn't own +/// * **ServerQueueFull**: The queue in the server is full, and this call would block +/// * **Timeout**: The timeout limit has been reached +pub fn try_send_message(connection: CID, message: Message) -> core::result::Result { + let result = rsyscall(SysCall::TrySendMessage(connection, message)); + match result { + Ok(Result::Ok) => Ok(Result::Ok), + Ok(Result::Scalar1(a)) => Ok(Result::Scalar1(a)), + Ok(Result::Scalar2(a, b)) => Ok(Result::Scalar2(a, b)), + Err(e) => Err(e), + v => panic!("Unexpected return value: {:?}", v), + } +} + +/// Connect to a server on behalf of another process. This can be used by a name +/// resolution server to securely create connections without disclosing a SID. +/// +/// # Errors +/// +/// * **ServerNotFound**: The server does not exist so the connection is now invalid +/// * **BadAddress**: The client tried to pass a Memory message using an address it doesn't own +/// * **ServerQueueFull**: The queue in the server is full, and this call would block +/// * **Timeout**: The timeout limit has been reached +pub fn connect_for_process(pid: PID, sid: SID) -> core::result::Result { + let result = rsyscall(SysCall::ConnectForProcess(pid, sid)); + match result { + Ok(Result::ConnectionID(cid)) => Ok(Result::ConnectionID(cid)), + Err(e) => Err(e), + v => panic!("Unexpected return value: {:?}", v), + } +} + +/// Send a message to a server. Depending on the mesage type (move or borrow), it +/// will either block (borrow) or return immediately (move). +/// If the message type is `borrow`, then the memory addresses pointed to will be +/// unavailable to this process until this function returns. +/// +/// If the server queue is full, this will block. +/// +/// # Errors +/// +/// * **ServerNotFound**: The server does not exist so the connection is now invalid +/// * **BadAddress**: The client tried to pass a Memory message using an address it doesn't own +/// * **Timeout**: The timeout limit has been reached +pub fn send_message(connection: CID, message: Message) -> core::result::Result { + let result = rsyscall(SysCall::SendMessage(connection, message)); + match result { + Ok(Result::Ok) => Ok(Result::Ok), + Ok(Result::Scalar1(a)) => Ok(Result::Scalar1(a)), + Ok(Result::Scalar2(a, b)) => Ok(Result::Scalar2(a, b)), + Err(e) => Err(e), + v => panic!("Unexpected return value: {:?}", v), + } +} + +pub fn terminate_process() { + rsyscall(SysCall::TerminateProcess).expect("terminate_process returned an error"); +} + +/// Return execution to the kernel. This function may return at any time, +/// including immediately +pub fn yield_slice() { + rsyscall(SysCall::Yield).expect("yield_slice returned an error"); +} + +/// Return execution to the kernel and wait for a message or an interrupt. +pub fn wait_event() { + rsyscall(SysCall::WaitEvent).expect("wait_event returned an error"); +} + +pub fn create_thread_simple( + f: fn(T) -> U, + arg: T, +) -> core::result::Result, Error> +where + T: Send + 'static, + U: Send + 'static, +{ + let thread_info = crate::arch::create_thread_simple_pre(&f, &arg)?; + rsyscall(SysCall::CreateThread(thread_info)).and_then(|result| { + if let Result::ThreadID(thread_id) = result { + crate::arch::create_thread_simple_post(f, arg, thread_id) + } else { + Err(Error::InternalError) + } + }) +} + +/// Create a new thread with the given closure. +pub fn create_thread(f: F) -> core::result::Result, Error> +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + let thread_info = crate::arch::create_thread_pre(&f)?; + rsyscall(SysCall::CreateThread(thread_info)).and_then(|result| { + if let Result::ThreadID(thread_id) = result { + crate::arch::create_thread_post(f, thread_id) + } else { + Err(Error::InternalError) + } + }) +} + +/// Wait for a thread to finish +pub fn wait_thread(joiner: crate::arch::WaitHandle) -> SysCallResult { + crate::arch::wait_thread(joiner) +} + +/// Create a new process by running it in its own thread +#[cfg(not(target_os = "none"))] +pub fn create_process_as_thread( + args: ProcessArgsAsThread, +) -> core::result::Result +where + F: FnOnce() + Send + 'static, +{ + let process_init = crate::arch::create_process_pre_as_thread(&args)?; + rsyscall(SysCall::CreateProcess(process_init)).and_then(|result| { + if let Result::ProcessID(pid) = result { + crate::arch::create_process_post_as_thread(args, process_init, pid) + } else { + Err(Error::InternalError) + } + }) +} + +/// Wait for a thread to finish +#[cfg(not(target_os = "none"))] +pub fn wait_process_as_thread(joiner: crate::arch::ProcessHandleAsThread) -> SysCallResult { + crate::arch::wait_process_as_thread(joiner) +} + +pub fn create_process( + args: ProcessArgs, +) -> core::result::Result { + let process_init = crate::arch::create_process_pre(&args)?; + rsyscall(SysCall::CreateProcess(process_init)).and_then(|result| { + if let Result::ProcessID(pid) = result { + crate::arch::create_process_post(args, process_init, pid) + } else { + Err(Error::InternalError) + } + }) +} + +/// Wait for a thread to finish +pub fn wait_process(joiner: crate::arch::ProcessHandle) -> SysCallResult { + crate::arch::wait_process(joiner) +} + +pub fn rsyscall(call: SysCall) -> SysCallResult { + let mut ret = Result::Ok; + let args = call.as_args(); + unsafe { + _xous_syscall( + args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], &mut ret, + ) + }; + match ret { + Result::Error(e) => Err(e), + other => Ok(other), + } +} + +// /// This is dangerous, but fast. +// pub unsafe fn dangerous_syscall(call: SysCall) -> SyscallResult { +// use core::mem::{transmute, MaybeUninit}; +// let mut ret = MaybeUninit::uninit().assume_init(); +// let presto = transmute::<_, (usize, usize, usize, usize, usize, usize, usize, usize)>(call); +// _xous_syscall_rust( +// presto.0, presto.1, presto.2, presto.3, presto.4, presto.5, presto.6, presto.7, &mut ret, +// ); +// match ret { +// Result::Error(e) => Err(e), +// other => Ok(other), +// } +// }