diff --git a/Cargo.toml b/Cargo.toml index a157724..4cdb76a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] -members = [ "mctp", "mctp-linux", "mctp-estack", "pldm", "pldm-fw", "pldm-fw-cli", "standalone"] +members = [ "pldm-fw-cli", "standalone" ] +exclude = [ "mctp-usb-embassy" ] resolver = "2" [workspace.dependencies] diff --git a/ci/runtests.sh b/ci/runtests.sh index 8b58c8a..afbe43a 100755 --- a/ci/runtests.sh +++ b/ci/runtests.sh @@ -33,6 +33,14 @@ cargo build --target thumbv7em-none-eabihf --features defmt --no-default-feature cargo build --features log ) +# not a workspace +( +cd mctp-usb-embassy +cargo build --target thumbv7em-none-eabihf --features defmt --no-default-features +cargo build --features log +cargo doc +) + cargo doc --features mctp-estack/log echo success diff --git a/mctp-usb-embassy/Cargo.lock b/mctp-usb-embassy/Cargo.lock new file mode 100644 index 0000000..790725b --- /dev/null +++ b/mctp-usb-embassy/Cargo.lock @@ -0,0 +1,547 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4818c32afec43e3cae234f324bad9a976c9aa7501022d26ff60a4017a1a006b7" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync 0.6.2", +] + +[[package]] +name = "embassy-sync" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-sync" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef1a8a1ea892f9b656de0295532ac5d8067e9830d49ec75076291fd6066b136" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-usb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e651b9b7b47b514e6e6d1940a6e2e300891a2c33641917130643602a0cb6386" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync 0.6.2", + "embassy-usb-driver", + "heapless", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" + +[[package]] +name = "embedded-crc-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "libmctp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d077261b65cfe16a3d490243354fbaec3f22e5bcad546d21394105ab836d1d0" +dependencies = [ + "bitfield", + "smbus-pec", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mctp" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", +] + +[[package]] +name = "mctp-estack" +version = "0.1.0" +dependencies = [ + "crc", + "defmt 0.3.100", + "embassy-sync 0.7.0", + "embedded-io-async", + "heapless", + "libmctp", + "log", + "mctp", + "smbus-pec", + "uuid", +] + +[[package]] +name = "mctp-usb-embassy" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embassy-futures", + "embassy-usb", + "embassy-usb-driver", + "heapless", + "log", + "mctp", + "mctp-estack", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "smbus-pec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" +dependencies = [ + "embedded-crc-macros", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] diff --git a/mctp-usb-embassy/Cargo.toml b/mctp-usb-embassy/Cargo.toml new file mode 100644 index 0000000..e13cce5 --- /dev/null +++ b/mctp-usb-embassy/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "mctp-usb-embassy" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +categories = ["network-programming", "embedded", "no-std"] +repository = "https://github.com/CodeConstruct/mctp-rs" +rust-version = "1.82" + +[dependencies] +embassy-usb = { version = "0.4" } +embassy-usb-driver = { version = "0.1" } +embassy-futures = { version = "0.1" } + +log = { version = "0.4", optional = true } +defmt = { version = "0.3", optional = true } + +mctp = { path = "../mctp", default-features = false } +mctp-estack = { path = "../mctp-estack", default-features = false } +heapless = "0.8" + +[features] +default = ["log"] +defmt = ["mctp-estack/defmt", "dep:defmt"] +log = ["mctp-estack/log", "dep:log"] +std = ["mctp/std"] diff --git a/mctp-usb-embassy/src/lib.rs b/mctp-usb-embassy/src/lib.rs new file mode 100644 index 0000000..47a5408 --- /dev/null +++ b/mctp-usb-embassy/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +/* + * Copyright (c) 2025 Code Construct + */ +#![no_std] +#![forbid(unsafe_code)] + +mod mctpusb; + +pub use mctpusb::{MctpUsbClass, Receiver, Sender}; + +/// Maximum packet for DSP0283 1.0. +pub const MCTP_USB_MAX_PACKET: usize = 512; diff --git a/mctp-usb-embassy/src/mctpusb.rs b/mctp-usb-embassy/src/mctpusb.rs new file mode 100644 index 0000000..0b7b91d --- /dev/null +++ b/mctp-usb-embassy/src/mctpusb.rs @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +/* + * Copyright (c) 2025 Code Construct + */ +//! # MCTP over USB transport for `embassy-usb`. +//! +//! Implements DMTF [DSP0283](https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf) +//! standard for a MCTP transport over USB. +#[cfg(feature = "defmt")] +#[allow(unused)] +use defmt::{debug, error, info, trace, warn}; + +#[cfg(feature = "log")] +#[allow(unused)] +use log::{debug, error, info, trace, warn}; + +#[cfg(not(any(feature = "log", feature = "defmt")))] +compile_error!("Either log or defmt feature must be enabled"); + +use core::ops::Range; + +use embassy_futures::join::join; +use embassy_usb::descriptor::{SynchronizationType, UsageType}; +use embassy_usb::Builder; +use embassy_usb_driver::{ + Driver, Endpoint, EndpointIn, EndpointOut, EndpointType, +}; +use heapless::Vec; +use mctp_estack::{ + router::PortBottom, router::PortId, usb::MctpUsbHandler, Router, +}; + +use crate::MCTP_USB_MAX_PACKET; + +pub const USB_CLASS_MCTP: u8 = 0x14; +pub const MCTP_SUBCLASS_DEVICE: u8 = 0x0; +pub const MCTP_PROTOCOL_V1: u8 = 0x1; + +/// The send half of a `MctpUsbClass`. +/// +/// Returned from [`split()`](MctpUsbClass::split) +pub struct Sender<'d, D: Driver<'d>> { + ep: D::EndpointIn, + buf: Vec, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Send a single packet. + pub async fn send(&mut self, pkt: &[u8]) -> mctp::Result<()> { + self.feed(pkt)?; + self.flush().await + } + + /// Enqueue a packet in the current USB payload. + /// + /// The payload will not be sent until `flush()` is called. + /// May return [`mctp::Error::NoSpace`] if the packet won't + /// fit in the current payload. + pub fn feed(&mut self, pkt: &[u8]) -> mctp::Result<()> { + let total = pkt.len().checked_add(4).ok_or(mctp::Error::NoSpace)?; + let avail = self.buf.capacity() - self.buf.len(); + if avail < total { + return Err(mctp::Error::NoSpace); + } + + let mut hdr = [0u8; 4]; + MctpUsbHandler::header(pkt.len(), &mut hdr)?; + let _ = self.buf.extend_from_slice(&hdr); + let _ = self.buf.extend_from_slice(pkt); + Ok(()) + } + + /// Send the current payload via USB. + /// + /// The payload must have been set with a previous `feed()`. + pub async fn flush(&mut self) -> mctp::Result<()> { + if self.buf.is_empty() { + return Err(mctp::Error::BadArgument); + } + let r = self.ep.write(&self.buf).await; + self.buf.clear(); + r.map_err(|_e| mctp::Error::TxFailure) + } + + /// Wait for a host to connect. + pub async fn wait_connection(&mut self) { + self.ep.wait_enabled().await + } + + /// Run with a `mctp::Router` stack. + pub async fn run(mut self, mut bottom: PortBottom<'_>) -> ! { + // Outer loop for reattaching USB + loop { + debug!("mctp usb send waiting"); + self.wait_connection().await; + debug!("mctp usb send attached"); + 'sending: loop { + // Wait for at least one MCTP packet enqueued + let (pkt, _dest) = bottom.outbound().await; + let r = self.feed(pkt); + + // Consume it + bottom.outbound_done(); + if r.is_err() { + // MCTP packet too large for USB + continue 'sending; + } + + 'fill: loop { + let Some((pkt, _dest)) = bottom.try_outbound() else { + // No more packets + break 'fill; + }; + + // See if it fits in the payload + match self.feed(pkt) { + // Success, consume it + Ok(()) => bottom.outbound_done(), + // Won't fit, leave it until next 'sending iteration. + Err(_) => break 'fill, + } + } + + if let Err(e) = self.flush().await { + debug!("usb send error {}", e); + break 'sending; + } + } + } + } +} + +/// The receive half of a `MctpUsbClass`. +/// +/// Returned from [`split()`](MctpUsbClass::split) +pub struct Receiver<'d, D: Driver<'d>> { + ep: D::EndpointOut, + buf: [u8; MCTP_USB_MAX_PACKET], + // valid range remaining in buf + remaining: Range, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Receive a single MCTP packet. + /// + /// Returns `None` on USB disconnected. + /// `Some(Err)` may be returned on an invalid packet. + pub async fn receive(&mut self) -> Option> { + if self.remaining.is_empty() { + // Refill + let l = match self.ep.read(&mut self.buf).await { + Ok(l) => l, + Err(_e) => { + return None; + } + }; + self.remaining = Range { start: 0, end: l }; + } + + // TODO: would be nice to loop until a valid decode, + // but lifetimes are difficult until polonius merges + let rem = &self.buf[self.remaining.clone()]; + let (pkt, rem) = match MctpUsbHandler::decode(rem) { + Ok(a) => a, + Err(e) => { + return Some(Err(e)); + } + }; + self.remaining.start = self.remaining.end - rem.len(); + Some(Ok(pkt)) + } + + /// Wait for a host to connect. + pub async fn wait_connection(&mut self) { + self.ep.wait_enabled().await + } + + /// Run with a `mctp::Router` stack. + pub async fn run(mut self, router: &Router<'_>, port: PortId) -> ! { + // Outer loop for reattaching USB + loop { + debug!("mctp usb recv waiting"); + self.wait_connection().await; + info!("mctp usb recv attached"); + + // Inner loop receives packets and provides MCTP handling + 'receiving: loop { + match self.receive().await { + Some(Ok(pkt)) => { + router.inbound(pkt, port).await; + } + Some(Err(e)) => { + debug!("mctp usb packet decode failure {}", e) + } + None => { + info!("mctp usb disconnected"); + break 'receiving; + } + } + } + } + } +} + +/// A MCTP-over-USB device. +/// +/// This requires a USB high-speed device because the specification +/// requires a 512 byte maximum packet size. +pub struct MctpUsbClass<'d, D: Driver<'d>> { + sender: Sender<'d, D>, + receiver: Receiver<'d, D>, +} + +impl<'d, D: Driver<'d>> MctpUsbClass<'d, D> { + pub fn new(builder: &mut Builder<'d, D>) -> Self { + let mut func = builder.function( + USB_CLASS_MCTP, + MCTP_SUBCLASS_DEVICE, + MCTP_PROTOCOL_V1, + ); + let mut iface = func.interface(); + // first alt iface is the default (and only) + let mut alt = iface.alt_setting( + USB_CLASS_MCTP, + MCTP_SUBCLASS_DEVICE, + MCTP_PROTOCOL_V1, + None, + ); + let interval = 1; + let ep_out = alt.alloc_endpoint_out( + EndpointType::Bulk, + MCTP_USB_MAX_PACKET as u16, + interval, + ); + let ep_in = alt.alloc_endpoint_in( + EndpointType::Bulk, + MCTP_USB_MAX_PACKET as u16, + interval, + ); + + alt.endpoint_descriptor( + ep_out.info(), + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ); + alt.endpoint_descriptor( + ep_in.info(), + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ); + + let sender = Sender { + ep: ep_in, + buf: Vec::new(), + }; + let receiver = Receiver { + ep: ep_out, + buf: [0; MCTP_USB_MAX_PACKET], + remaining: Default::default(), + }; + + Self { sender, receiver } + } + + /// Split into `Sender` and `Receiver` + pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { + (self.sender, self.receiver) + } + + /// Run with a `mctp::Router` stack. + pub async fn run( + self, + router: &Router<'_>, + bottom: PortBottom<'_>, + port: PortId, + ) -> ! { + let (s, r) = self.split(); + let _ = join(s.run(bottom), r.run(router, port)).await; + unreachable!() + } +}