diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fc46819..b5d40e9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,6 +15,7 @@ jobs: example: - hello_part - fuel_tank + - ping env: DURATION: 10s RUST_LOG: trace diff --git a/Cargo.lock b/Cargo.lock index 4be3a0f..755b703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -894,6 +894,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ping_client" +version = "0.1.0" +dependencies = [ + "a653rs", + "a653rs-linux", + "log", +] + +[[package]] +name = "ping_server" +version = "0.1.0" +dependencies = [ + "a653rs", + "a653rs-linux", + "log", +] + [[package]] name = "polling" version = "2.8.0" diff --git a/Cargo.toml b/Cargo.toml index 01a662f..1602212 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,14 @@ members = [ "hypervisor", "partition", "core", + "examples/hello_part", + "examples/fuel_tank_simulation", "examples/fuel_tank_controller", + + "examples/ping_client", + "examples/ping_server", ] [workspace.dependencies] diff --git a/examples/ping.yaml b/examples/ping.yaml new file mode 100644 index 0000000..c7d7d1e --- /dev/null +++ b/examples/ping.yaml @@ -0,0 +1,27 @@ +major_frame: 1s +partitions: + - id: 0 + name: ping_client + duration: 10ms + offset: 0ms + period: 1s + image: target/x86_64-unknown-linux-musl/release/ping_client + - id: 1 + name: ping_server + duration: 20ms + offset: 450ms + period: 1s + image: target/x86_64-unknown-linux-musl/release/ping_server +channel: + - !Sampling + name: ping_request + msg_size: 16B + source: ping_client + destination: + - ping_server + - !Sampling + name: ping_response + msg_size: 32B + source: ping_server + destination: + - ping_client diff --git a/examples/ping_client/Cargo.toml b/examples/ping_client/Cargo.toml new file mode 100644 index 0000000..b24bc46 --- /dev/null +++ b/examples/ping_client/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ping_client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +a653rs = { workspace = true, features = ["macros"] } +a653rs-linux = { path = "../../partition" } +log.workspace = true \ No newline at end of file diff --git a/examples/ping_client/src/main.rs b/examples/ping_client/src/main.rs new file mode 100644 index 0000000..1bd5733 --- /dev/null +++ b/examples/ping_client/src/main.rs @@ -0,0 +1,111 @@ +use a653rs::partition; +use a653rs::prelude::PartitionExt; +use a653rs_linux::partition::ApexLogger; +use log::LevelFilter; + +fn main() { + ApexLogger::install_panic_hook(); + ApexLogger::install_logger(LevelFilter::Trace).unwrap(); + + ping_client::Partition.run() +} + +#[partition(a653rs_linux::partition::ApexLinuxPartition)] +mod ping_client { + use core::time::Duration; + use log::{info, warn}; + + #[sampling_out(name = "ping_request", msg_size = "16B")] + struct PingRequest; + + #[sampling_in(name = "ping_response", msg_size = "32B", refresh_period = "10s")] + struct PingResponse; + + #[start(cold)] + fn cold_start(mut ctx: start::Context) { + // initialize both sampling ports + ctx.create_ping_request().unwrap(); + ctx.create_ping_response().unwrap(); + + // create and start a periodic process + ctx.create_periodic_ping_client().unwrap().start().unwrap(); + } + + // do the same as a cold_start + #[start(warm)] + fn warm_start(ctx: start::Context) { + cold_start(ctx); + } + + // this process requests a ping from the server at the beginning of each + // partition window / MiF + #[periodic( + period = "0ms", + time_capacity = "Infinite", + stack_size = "8KB", + base_priority = 1, + deadline = "Soft" + )] + fn periodic_ping_client(ctx: periodic_ping_client::Context) { + info!("started periodic_ping_client process"); + + // a periodic process does not actually return at the end of a partition window, + // it just pauses itself once it is done with the work from the current MiF + // see below at the `ctx.periodic_wait().unwrap()` call. + loop { + // first, send a request: + + // `ctx.get_time()` returns a [SystemTime], which might be `Infinite`, or just a + // normal time. Thus we have to check that indeed a normal time was returned. + let SystemTime::Normal(time) = ctx.get_time() else { + panic!("could not read time"); + }; + info!("sending a request"); + + // convert the current time to an u128 integer representing nanoseconds, and + // serialize the integer to a byte array + let time_in_nanoseconds = time.as_nanos(); + let buf = time_in_nanoseconds.to_le_bytes(); + + // finally send the byte array to the ping_request port + ctx.ping_request.unwrap().send(&buf).unwrap(); + + // then receive a response, if any: + + // allocate a buffer on the stack for receival of the response + let mut buf = [0u8; 32]; + + // sample the ping_response sampling port into `buf` + // - validity indicates whether data received was sitting in the samplin port + // for no more than the refresh_period + // - `bytes` is a subslice of `buf`, containing only the bytes actually read + // from the sampling port + let (validity, bytes) = ctx.ping_response.unwrap().receive(&mut buf).unwrap(); + + // only if the message is valid and has the expected length try to process it + if validity == Validity::Valid && bytes.len() == 32 { + // deserialize the bytes into an u128 + let request_timestamp = u128::from_le_bytes(bytes[0..16].try_into().unwrap()); + let response_timestamp = u128::from_le_bytes(bytes[16..32].try_into().unwrap()); + // the difference is the time passed since sending the request for this response + let round_trip = time_in_nanoseconds - request_timestamp; + let req_to_server = response_timestamp - request_timestamp; + let resp_to_client = time_in_nanoseconds - response_timestamp; + + // convert the integers of nanoseconds back to a [Duration]s for nicer logging + let req_sent_to_resp_recv = Duration::from_nanos(round_trip as u64); + let req_sent_to_resp_sent = Duration::from_nanos(req_to_server as u64); + let resp_sent_to_resp_recv = Duration::from_nanos(resp_to_client as u64); + + // and log the results! + info!("received valid response:\n\tround-trip {req_sent_to_resp_recv:?}\n\treq-to-server {req_sent_to_resp_sent:?}\n\tresp-to-client{resp_sent_to_resp_recv:?}"); + } else { + warn!("response seems to be incomplete: {validity:?}, {bytes:?}"); + } + + // wait until the beginning of this partitions next MiF. In scheduling terms + // this function would probably be called `yield()`. + ctx.periodic_wait().unwrap(); + } + } +} diff --git a/examples/ping_server/Cargo.toml b/examples/ping_server/Cargo.toml new file mode 100644 index 0000000..e582e9b --- /dev/null +++ b/examples/ping_server/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ping_server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +a653rs = { workspace = true, features = ["macros"] } +a653rs-linux = { path = "../../partition" } +log.workspace = true \ No newline at end of file diff --git a/examples/ping_server/src/main.rs b/examples/ping_server/src/main.rs new file mode 100644 index 0000000..0efda04 --- /dev/null +++ b/examples/ping_server/src/main.rs @@ -0,0 +1,78 @@ +use a653rs::partition; +use a653rs::prelude::PartitionExt; +use a653rs_linux::partition::ApexLogger; +use log::LevelFilter; + +fn main() { + ApexLogger::install_panic_hook(); + ApexLogger::install_logger(LevelFilter::Trace).unwrap(); + + ping_server::Partition.run() +} + +#[partition(a653rs_linux::partition::ApexLinuxPartition)] +mod ping_server { + use log::info; + + #[sampling_in(name = "ping_request", msg_size = "16B", refresh_period = "10s")] + struct PingRequest; + + #[sampling_out(name = "ping_response", msg_size = "32B")] + struct PingResponse; + + #[start(cold)] + fn cold_start(mut ctx: start::Context) { + // intialize the request destination port + ctx.create_ping_request().unwrap(); + + // intialize the response source port + ctx.create_ping_response().unwrap(); + + // launch the periodic process + ctx.create_periodic_ping_server().unwrap().start().unwrap(); + } + + #[start(warm)] + fn warm_start(ctx: start::Context) { + cold_start(ctx); + } + + // the server process is super simple; all it does is receive a request and + // respond to it + #[periodic( + period = "0ms", + time_capacity = "Infinite", + stack_size = "8KB", + base_priority = 1, + deadline = "Soft" + )] + fn periodic_ping_server(ctx: periodic_ping_server::Context) { + info!("started ping_server process"); + loop { + info!("forwarding request as response "); + + // allocate a buffer to receive into + let mut buf = [0u8; 32]; + + // receive a request, storing it to `buf` + ctx.ping_request.unwrap().receive(&mut buf).unwrap(); + + // `ctx.get_time()` returns a [SystemTime], which might be `Infinite`, or just a + // normal time. Thus we have to check that indeed a normal time was returned. + let SystemTime::Normal(time) = ctx.get_time() else { + panic!("could not read time"); + }; + + // convert the current time to an u128 integer representing nanoseconds, and + // serialize the integer to a byte array + let time_in_nanoseconds = time.as_nanos(); + buf[16..32].copy_from_slice(&time_in_nanoseconds.to_le_bytes()); + + // send the contents of `buf` back as response + ctx.ping_response.unwrap().send(&buf).unwrap(); + + // wait until the next partition window / MiF + ctx.periodic_wait().unwrap(); + } + } +} diff --git a/flake.nix b/flake.nix index 1c21c4c..97d8852 100644 --- a/flake.nix +++ b/flake.nix @@ -68,6 +68,10 @@ name = "fuel_tank"; partitions = [ "fuel_tank_simulation" "fuel_tank_controller" ]; } + { + name = "ping"; + partitions = [ "ping_server" "ping_client" ]; + } ]; cargoPackageList = ps: builtins.map (p: "--package=${p}") ps;