diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 27556de9e..eede3ac1c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -108,7 +108,7 @@ Services use a variety of async IPC mechanisms from `embassy-sync` and `embedded - **`embassy_sync::signal::Signal`** — single-value async notifications - **`embedded_services::ipc::deferred`** — request/response channels where the caller awaits a reply - **`embedded_services::broadcaster`** — publish/subscribe pattern for event fan-out -- **`embedded_services::relay`** — relay service pattern for MCTP-based request/response dispatch with direct async calls +- **`odp_client::server`** — relay service pattern for MCTP-based request/response dispatch with direct async calls ### Composition diff --git a/Cargo.lock b/Cargo.lock index 1671e7da6..eb607a1c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ "embedded-services", "log", "num_enum", + "odp-client", ] [[package]] @@ -419,6 +420,7 @@ dependencies = [ "embassy-sync", "embedded-services", "log", + "odp-client", "rtt-target", ] @@ -427,8 +429,8 @@ name = "debug-service-messages" version = "0.1.0" dependencies = [ "defmt 0.3.100", - "embedded-services", "num_enum", + "odp-client", ] [[package]] @@ -811,15 +813,12 @@ dependencies = [ name = "embedded-services" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "cortex-m", "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "log", - "mctp-rs", - "paste", "portable-atomic", "serde", "static_cell", @@ -908,6 +907,7 @@ dependencies = [ "embedded-services", "log", "mctp-rs", + "odp-client", "odp-service-common", ] @@ -1394,6 +1394,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "odp-client" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embedded-io 0.7.1", + "heapless 0.9.3", + "mctp-rs", + "paste", +] + [[package]] name = "odp-service-common" version = "0.1.0" @@ -1934,8 +1946,8 @@ name = "thermal-service-relay" version = "0.1.0" dependencies = [ "defmt 0.3.100", - "embedded-services", "num_enum", + "odp-client", "thermal-service-interface", "uuid", ] @@ -2006,9 +2018,9 @@ version = "0.1.0" dependencies = [ "defmt 0.3.100", "embedded-mcu-hal", - "embedded-services", "log", "num_enum", + "odp-client", "time-alarm-service-interface", ] @@ -2244,6 +2256,7 @@ dependencies = [ "embedded-services", "log", "mctp-rs", + "odp-client", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 404f79a30..ae089f81e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "debug-service-messages", "keyboard-service", "power-policy-interface", + "odp-client", "odp-service-common", "type-c-interface", "fw-update-interface", @@ -79,6 +80,7 @@ debug-service-messages = { path = "./debug-service-messages" } embassy-executor = "0.10.0" cfu-service = { path = "./cfu-service" } embedded-hal-nb = "1.0" +embedded-io = "0.7" embedded-io-async = "0.7.0" embedded-mcu-hal = "0.2.0" embassy-futures = "0.1.2" @@ -97,6 +99,7 @@ fw-update-interface = { path = "./fw-update-interface" } fw-update-interface-mocks = { path = "./fw-update-interface-mocks" } mctp-rs = { path = "./mctp-rs" } num_enum = { version = "0.7.5", default-features = false } +odp-client = { path = "./odp-client" } portable-atomic = { version = "1.11", default-features = false } power-policy-interface = { path = "./power-policy-interface" } paste = "1.0.15" diff --git a/battery-service-relay/Cargo.toml b/battery-service-relay/Cargo.toml index 7d53c8541..6da40f4df 100644 --- a/battery-service-relay/Cargo.toml +++ b/battery-service-relay/Cargo.toml @@ -12,6 +12,7 @@ ignored = ["log"] defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } embedded-services.workspace = true +odp-client.workspace = true num_enum.workspace = true battery-service-interface.workspace = true @@ -21,6 +22,7 @@ defmt = [ "dep:defmt", "embedded-services/defmt", "battery-service-interface/defmt", + "odp-client/defmt", ] log = ["dep:log", "embedded-services/log", "battery-service-interface/log"] diff --git a/battery-service-relay/src/lib.rs b/battery-service-relay/src/lib.rs index 92988ae2f..7694a6a59 100644 --- a/battery-service-relay/src/lib.rs +++ b/battery-service-relay/src/lib.rs @@ -18,14 +18,14 @@ impl BatteryServiceRelayHandler } } -impl embedded_services::relay::mctp::RelayServiceHandlerTypes +impl odp_client::server::RelayServiceHandlerTypes for BatteryServiceRelayHandler { type RequestType = serialization::AcpiBatteryRequest; type ResultType = serialization::AcpiBatteryResult; } -impl embedded_services::relay::mctp::RelayServiceHandler +impl odp_client::server::RelayServiceHandler for BatteryServiceRelayHandler { async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { diff --git a/battery-service-relay/src/serialization.rs b/battery-service-relay/src/serialization.rs index 39f411a44..e23000236 100644 --- a/battery-service-relay/src/serialization.rs +++ b/battery-service-relay/src/serialization.rs @@ -1,5 +1,5 @@ use battery_service_interface::*; -use embedded_services::relay::{MessageSerializationError, SerializableMessage}; +use odp_client::{MessageSerializationError, SerializableMessage}; #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] #[repr(u16)] diff --git a/debug-service-messages/Cargo.toml b/debug-service-messages/Cargo.toml index 5127a7579..edd290abc 100644 --- a/debug-service-messages/Cargo.toml +++ b/debug-service-messages/Cargo.toml @@ -7,11 +7,11 @@ repository.workspace = true [dependencies] defmt = { workspace = true, optional = true } -embedded-services.workspace = true num_enum.workspace = true +odp-client.workspace = true [lints] workspace = true [features] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "odp-client/defmt"] diff --git a/debug-service-messages/src/lib.rs b/debug-service-messages/src/lib.rs index a4dafc673..44621dde9 100644 --- a/debug-service-messages/src/lib.rs +++ b/debug-service-messages/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -use embedded_services::relay::{MessageSerializationError, SerializableMessage}; +use odp_client::{MessageSerializationError, SerializableMessage}; /// Standard Debug Service Log Buffer Size pub const STD_DEBUG_BUF_SIZE: usize = 128; diff --git a/debug-service/Cargo.toml b/debug-service/Cargo.toml index 5381d6267..1eb328ba4 100644 --- a/debug-service/Cargo.toml +++ b/debug-service/Cargo.toml @@ -16,10 +16,11 @@ debug-service-messages.workspace = true embassy-sync.workspace = true embedded-services.workspace = true log.workspace = true +odp-client.workspace = true rtt-target = "0.6.1" [features] -defmt = ["debug-service-messages/defmt"] +defmt = ["debug-service-messages/defmt", "odp-client/defmt"] [lints] workspace = true diff --git a/debug-service/src/debug_service.rs b/debug-service/src/debug_service.rs index ef9762307..0fc7b1415 100644 --- a/debug-service/src/debug_service.rs +++ b/debug-service/src/debug_service.rs @@ -34,12 +34,12 @@ impl Service { } } -impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service { +impl odp_client::server::RelayServiceHandlerTypes for Service { type RequestType = DebugRequest; type ResultType = DebugResult; } -impl embedded_services::relay::mctp::RelayServiceHandler for Service { +impl odp_client::server::RelayServiceHandler for Service { async fn process_request(&self, _request: Self::RequestType) -> Self::ResultType { // Host sent an ACPI/MCTP request (e.g. GetDebugBuffer). Treat this as the // trigger to send the staged debug buffer back to the host. diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index 9774188c1..35543035f 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -11,17 +11,11 @@ rust-version.workspace = true workspace = true [dependencies] -bitfield.workspace = true critical-section.workspace = true defmt = { workspace = true, optional = true } embassy-sync.workspace = true embassy-futures.workspace = true log = { workspace = true, optional = true } -paste.workspace = true - -[dependencies.mctp-rs] -workspace = true -features = ["espi"] [dependencies.serde] workspace = true @@ -41,5 +35,5 @@ tokio = { workspace = true, features = ["rt", "macros", "time"] } [features] default = [] -defmt = ["dep:defmt", "embassy-sync/defmt", "mctp-rs/defmt"] +defmt = ["dep:defmt", "embassy-sync/defmt"] log = ["dep:log", "embassy-sync/log"] diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index e62562e3a..7b1cb55ab 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -22,18 +22,8 @@ pub mod init; pub mod ipc; pub mod keyboard; pub mod named; -pub mod relay; pub mod sync; -/// Hidden re-exports used by macros defined in this crate. -/// Not part of the public API — do not depend on these directly. -#[doc(hidden)] -pub mod _macro_internal { - pub use bitfield; - pub use mctp_rs; - pub use paste; -} - /// Global Mutex type, ThreadModeRawMutex is used in a microcontroller context, whereas CriticalSectionRawMutex is used /// in a standard context for unit testing. /// diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs deleted file mode 100644 index d92cfb97f..000000000 --- a/embedded-service/src/relay/mod.rs +++ /dev/null @@ -1,503 +0,0 @@ -//! Helper code for serialization/deserialization of arbitrary messages to/from the embedded controller via a relay service, e.g. the eSPI service. - -/// Error type for serializing/deserializing messages -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum MessageSerializationError { - /// The message payload does not represent a valid message - InvalidPayload(&'static str), - - /// The message discriminant does not represent a known message type - UnknownMessageDiscriminant(u16), - - /// The provided buffer is too small to serialize the message - BufferTooSmall, - - /// Unspecified error - Other(&'static str), -} - -/// Trait for serializing and deserializing messages -pub trait SerializableMessage: Sized { - /// Serializes the message into the provided buffer. - /// On success, returns the number of bytes written - fn serialize(self, buffer: &mut [u8]) -> Result; - - /// Returns the discriminant needed to deserialize this type of message. - fn discriminant(&self) -> u16; - - /// Deserializes the message from the provided buffer. - fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; -} - -// Prevent other types from implementing SerializableResult - they should instead use SerializableMessage on a Response type and an Error type -#[doc(hidden)] -mod private { - pub trait Sealed {} - - impl Sealed for Result {} -} - -/// Responses sent over MCTP are called "Results" and are of type Result where T and E both implement SerializableMessage -pub trait SerializableResult: private::Sealed + Sized { - /// The type of the result when the operation being responded to succeeded - type SuccessType: SerializableMessage; - - /// The type of the result when the operation being responded to failed - type ErrorType: SerializableMessage; - - /// Returns true if the result represents a successful operation, false otherwise - fn is_ok(&self) -> bool; - - /// Returns a unique discriminant that can be used to deserialize the specific type of result. - /// Discriminants can be reused for success and error messages. - fn discriminant(&self) -> u16; - - /// Writes the result into the provided buffer. - /// On success, returns the number of bytes written - fn serialize(self, buffer: &mut [u8]) -> Result; - - /// Attempts to deserialize the result from the provided buffer. - fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; -} - -impl SerializableResult for Result -where - T: SerializableMessage, - E: SerializableMessage, -{ - type SuccessType = T; - type ErrorType = E; - - fn is_ok(&self) -> bool { - Result::::is_ok(self) - } - - fn discriminant(&self) -> u16 { - match self { - Ok(success_value) => success_value.discriminant(), - Err(error_value) => error_value.discriminant(), - } - } - - fn serialize(self, buffer: &mut [u8]) -> Result { - match self { - Ok(success_value) => success_value.serialize(buffer), - Err(error_value) => error_value.serialize(buffer), - } - } - - fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result { - if is_error { - Ok(Err(E::deserialize(discriminant, buffer)?)) - } else { - Ok(Ok(T::deserialize(discriminant, buffer)?)) - } - } -} - -pub mod mctp { - //! Contains helper functions for services that relay comms messages over MCTP - - /// Error type for MCTP relay operations - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum MctpError { - /// The endpoint ID does not correspond to a known service - UnknownEndpointId, - } - - /// Trait for types that are used by a relay service to relay messages from your service over the wire. - /// If you are implementing this trait, you should also implement RelayServiceHandler. - /// - pub trait RelayServiceHandlerTypes { - /// The request type that this service handler processes - type RequestType: super::SerializableMessage; - - /// The result type that this service handler processes - type ResultType: super::SerializableResult; - } - - /// Trait for a service that can be relayed over an external bus (e.g. battery service, thermal service, time-alarm service) - /// - pub trait RelayServiceHandler: RelayServiceHandlerTypes { - /// Process the provided request and yield a result. - fn process_request<'a>( - &'a self, - request: Self::RequestType, - ) -> impl core::future::Future + 'a; - } - - // Traits below this point are intended for consumption by relay services (e.g. the eSPI service), not individual services that want their messages relayed. - // In general, you should not implement these yourself; rather, you should leverage the `impl_odp_mctp_relay_handler` macro to do that for you. - - /// Contains additional methods that must be implemented on the relay header type. - /// Do not implement this yourself - rather, rely on the `impl_odp_mctp_relay_handler` macro to implement this. - #[doc(hidden)] - pub trait RelayHeader { - /// Return the ID of the service associated with the request - fn get_service_id(&self) -> ServiceIdType; - } - - /// Contains additional methods that must be implemented on the relay response type. - /// Do not implement this yourself - rather, rely on the `impl_odp_mctp_relay_handler` macro to implement this. - #[doc(hidden)] - pub trait RelayResponse { - /// Construct an MCTP header suitable for representing the result based on the provided service handler ID and result - fn create_header(&self, service_id: &ServiceIdType) -> HeaderType; - } - - /// Trait for aggregating collections of services that can be relayed over an external bus. - /// Do not implement this yourself - rather, rely on the `impl_odp_mctp_relay_handler` macro to implement this. - /// - pub trait RelayHandler { - /// The type that uniquely identifies individual services. Generally expected to be a C-style enum. - type ServiceIdType: Into + TryFrom + Copy; - - /// The header type used by request and result enums - type HeaderType: mctp_rs::MctpMessageHeaderTrait + RelayHeader; - - /// An enum over all possible request types - type RequestEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType>; - - /// An enum over all possible result types - type ResultEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType> - + RelayResponse; - - /// Process the provided request and yield a result. - fn process_request<'a>( - &'a self, - message: Self::RequestEnumType, - ) -> impl core::future::Future + 'a; - } - - /// This macro generates a relay type over a collection of message types, which can be used by a relay service to - /// receive messages over the wire and translate them into calls to a particular service on the EC. - /// - /// This is the recommended way to implement a relay handler - you should not implement the RelayHandler trait yourself. - /// - /// This macro will emit a type with the name you specify that is generic over a lifetime for the hardware (probably 'static in production code), - /// implements the `RelayHandler` trait, and has a single constructor method `new` that takes as arguments references to the service handler - /// types that you specify that have the 'hardware lifetime'. - /// - /// The macro takes the following inputs once: - /// relay_type_name: The name of the relay type to generate. This is arbitrary. The macro will emit a type with this name. - /// - /// Followed by a list of any number of service entries, which are specified by the following inputs: - /// service_name: A name to assign to generated identifiers associated with the service, e.g. "Battery". - /// This can be arbitrary. - /// service_id: A unique u8 that addresses that service on the EC. - /// service_handler_type: A type that implements the RelayServiceHandler trait, which will be used to process messages - /// for this service. - /// - /// Example usage: - /// - /// ```ignore - /// - /// impl_odp_mctp_relay_handler!( - /// MyRelayHandlerType; - /// Battery, 0x9, battery_service_relay::RelayHandler>; - /// TimeAlarm, 0xB, time_alarm_service_relay::RelayHandler>; - /// ); - /// - /// let relay_handler = MyRelayHandlerType::new(battery_service_instance, time_alarm_service_instance); - /// - /// // Then, pass relay_handler to your relay service (e.g. eSPI service), which should be generic over an `impl RelayHandler`. - /// - /// ``` - /// - #[macro_export] - macro_rules! impl_odp_mctp_relay_handler { - ( - $relay_type_name:ident; - $( - $service_name:ident, - $service_id:expr, - $service_handler_type:ty; - )+ - ) => { - $crate::_macro_internal::paste::paste! { - mod [< _odp_impl_ $relay_type_name:snake >] { - use $crate::_macro_internal::bitfield::bitfield; - use core::convert::Infallible; - use $crate::_macro_internal::mctp_rs::smbus_espi::SmbusEspiMedium; - use $crate::_macro_internal::mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; - use $crate::relay::{SerializableMessage, SerializableResult}; - use $crate::relay::mctp::RelayServiceHandler; - - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - #[repr(u8)] - pub enum OdpService { - $( - $service_name = $service_id, - )+ - } - - impl From for u8 { - fn from(val: OdpService) -> u8 { - val as u8 - } - } - - impl TryFrom for OdpService { - type Error = u8; - fn try_from(value: u8) -> Result { - match value { - $( - $service_id => Ok(OdpService::$service_name), - )+ - other => Err(other), - } - } - } - - pub enum HostRequest { - $( - $service_name(<$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::RequestType), - )+ - } - - impl MctpMessageTrait<'_> for HostRequest { - type Header = OdpHeader; - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - $( - HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) - .map_err(|_| MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), - )+ - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - $( - OdpService::$service_name => Self::$service_name( - <$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::RequestType::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, - ), - )+ - }) - } - } - - bitfield! { - /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. - #[derive(Copy, Clone, PartialEq, Eq)] - struct OdpHeaderWireFormat(u32); - impl Debug; - impl new; - /// If true, represents a request; otherwise, represents a result - is_request, set_is_request: 25; - - /// The service ID that this message is related to - /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. - u8, service_id, set_service_id: 23, 16; - - /// On results, indicates if the result message is an error. Unused on requests. - is_error, set_is_error: 15; - - /// The message type/discriminant - u16, message_id, set_message_id: 14, 0; - } - - #[derive(Copy, Clone, PartialEq, Eq)] - pub enum OdpMessageType { - Request, - Result { is_error: bool }, - } - - #[derive(Copy, Clone, PartialEq, Eq)] - pub struct OdpHeader { - pub message_type: OdpMessageType, - pub service: OdpService, - pub message_id: u16, - } - - impl From for OdpHeaderWireFormat { - fn from(src: OdpHeader) -> Self { - Self::new( - matches!(src.message_type, OdpMessageType::Request), - src.service.into(), - match src.message_type { - OdpMessageType::Request => false, // unused on requests - OdpMessageType::Result { is_error } => is_error, - }, - src.message_id, - ) - } - } - - impl TryFrom for OdpHeader { - type Error = MctpPacketError; - - fn try_from(src: OdpHeaderWireFormat) -> Result { - let service = OdpService::try_from(src.service_id()) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; - - let message_type = if src.is_request() { - OdpMessageType::Request - } else { - OdpMessageType::Result { - is_error: src.is_error(), - } - }; - - Ok(OdpHeader { - message_type, - service, - message_id: src.message_id(), - }) - } - } - - impl MctpMessageHeaderTrait for OdpHeader { - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - let wire_format = OdpHeaderWireFormat::from(self); - let bytes = wire_format.0.to_be_bytes(); - buffer - .get_mut(0..bytes.len()) - .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? - .copy_from_slice(&bytes); - - Ok(bytes.len()) - } - - fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { - let bytes = buffer - .get(0..core::mem::size_of::()) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; - let raw = u32::from_be_bytes( - bytes - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - ); - - let parsed_wire_format = OdpHeaderWireFormat(raw); - let header = OdpHeader::try_from(parsed_wire_format) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; - - Ok(( - header, - buffer - .get(core::mem::size_of::()..) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - )) - } - } - - impl $crate::relay::mctp::RelayHeader for OdpHeader { - fn get_service_id(&self) -> OdpService { - self.service - } - } - - #[derive(Clone)] - pub enum HostResult { - $( - $service_name(<$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::ResultType), - )+ - } - - impl $crate::relay::mctp::RelayResponse for HostResult { - fn create_header(&self, service_id: &OdpService) -> OdpHeader { - match (self) { - $( - (HostResult::$service_name(result)) => OdpHeader { - message_type: OdpMessageType::Result { is_error: !result.is_ok() }, - service: *service_id, - message_id: result.discriminant(), - }, - )+ - } - } - } - - impl MctpMessageTrait<'_> for HostResult { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - $( - HostResult::$service_name(result) => result - .serialize(buffer) - .map_err(|_| MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), - )+ - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - match header.service { - $( - OdpService::$service_name => { - match header.message_type { - OdpMessageType::Request => { - Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) - } - OdpMessageType::Result { is_error } => { - Ok(HostResult::$service_name(<$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::ResultType::deserialize(is_error, header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) - } - } - }, - )+ - } - } - } - - - pub struct $relay_type_name { - $( - [<$service_name:snake _handler>]: $service_handler_type, - )+ - } - - impl $relay_type_name { - pub fn new( - $( - [<$service_name:snake _handler>]: $service_handler_type, - )+ - ) -> Self { - Self { - $( - [<$service_name:snake _handler>], - )+ - } - } - } - - impl $crate::relay::mctp::RelayHandler for $relay_type_name { - type ServiceIdType = OdpService; - type HeaderType = OdpHeader; - type RequestEnumType = HostRequest; - type ResultEnumType = HostResult; - - fn process_request<'a>( - &'a self, - message: HostRequest, - ) -> impl core::future::Future + 'a { - async move { - match message { - $( - HostRequest::$service_name(request) => { - let result = self.[<$service_name:snake _handler>].process_request(request).await; - HostResult::$service_name(result) - } - )+ - } - } - } - } - } // end mod __odp_impl - - // Allows this generated relay type to be publicly re-exported - pub use [< _odp_impl_ $relay_type_name:snake >]::$relay_type_name; - - } // end paste! - }; // end macro arm - } // end macro - - pub use impl_odp_mctp_relay_handler; -} diff --git a/espi-service/Cargo.toml b/espi-service/Cargo.toml index 350adc4a8..446886d0b 100644 --- a/espi-service/Cargo.toml +++ b/espi-service/Cargo.toml @@ -21,6 +21,7 @@ embassy-sync.workspace = true embassy-imxrt = { workspace = true, features = ["mimxrt633s"] } embassy-futures.workspace = true mctp-rs = { workspace = true, features = ["espi"] } +odp-client.workspace = true odp-service-common.workspace = true [target.'cfg(target_os = "none")'.dependencies] @@ -43,6 +44,7 @@ defmt = [ "embassy-sync/defmt", "embassy-imxrt/defmt", "mctp-rs/defmt", + "odp-client/defmt", ] log = ["dep:log", "embedded-services/log"] diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index f47ab3fd3..e69854e21 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -19,7 +19,7 @@ const ASSEMBLY_BUF_SIZE: usize = 256; #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct HostResultMessage { +struct HostResultMessage { pub handler_service_id: RelayHandler::ServiceIdType, pub message: RelayHandler::ResultEnumType, } @@ -32,23 +32,23 @@ pub enum Error { } /// The memory required by the eSPI service to run -pub struct Resources<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { +pub struct Resources<'hw, RelayHandler: odp_client::server::RelayHandler> { inner: Option>, } -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Default for Resources<'hw, RelayHandler> { +impl<'hw, RelayHandler: odp_client::server::RelayHandler> Default for Resources<'hw, RelayHandler> { fn default() -> Self { Self { inner: None } } } /// Service runner for the eSPI service. Users must call the run() method on the runner for the service to start processing events. -pub struct Runner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { +pub struct Runner<'hw, RelayHandler: odp_client::server::RelayHandler> { inner: &'hw ServiceInner<'hw, RelayHandler>, } -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> - odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw, RelayHandler> +impl<'hw, RelayHandler: odp_client::server::RelayHandler> odp_service_common::runnable_service::ServiceRunner<'hw> + for Runner<'hw, RelayHandler> { /// Run the service event loop. async fn run(self) -> embedded_services::Never { @@ -56,11 +56,11 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> } } -pub struct Service<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { +pub struct Service<'hw, RelayHandler: odp_client::server::RelayHandler> { _inner: &'hw ServiceInner<'hw, RelayHandler>, } -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> odp_service_common::runnable_service::Service<'hw> +impl<'hw, RelayHandler: odp_client::server::RelayHandler> odp_service_common::runnable_service::Service<'hw> for Service<'hw, RelayHandler> { type Resources = Resources<'hw, RelayHandler>; @@ -77,18 +77,18 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> odp_servic } } -pub struct InitParams<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { +pub struct InitParams<'hw, RelayHandler: odp_client::server::RelayHandler> { pub espi: espi::Espi<'hw>, pub relay_handler: RelayHandler, } -struct ServiceInner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { +struct ServiceInner<'hw, RelayHandler: odp_client::server::RelayHandler> { espi: Mutex>, host_tx_queue: Channel, HOST_TX_QUEUE_SIZE>, relay_handler: RelayHandler, } -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> ServiceInner<'hw, RelayHandler> { +impl<'hw, RelayHandler: odp_client::server::RelayHandler> ServiceInner<'hw, RelayHandler> { async fn new(mut init_params: InitParams<'hw, RelayHandler>) -> Self { init_params.espi.wait_for_plat_reset().await; @@ -229,7 +229,7 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> ServiceInn espi: &mut espi::Espi<'hw>, port_event: &espi::PortEvent, ) -> Result<(), Error> { - use embedded_services::relay::mctp::RelayHeader; + use odp_client::server::RelayHeader; info!("Host Request received"); espi.complete_port(port_event.port); @@ -263,7 +263,7 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> ServiceInn espi: &mut espi::Espi<'hw>, result: HostResultMessage, ) -> Result<(), Error> { - use embedded_services::relay::mctp::RelayResponse; + use odp_client::server::RelayResponse; let mut assembly_buf = [0u8; ASSEMBLY_BUF_SIZE]; let mut mctp_ctx = mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, assembly_buf.as_mut_slice()); diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 6f6ff3682..03193db9d 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -76,12 +76,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "bare-metal" version = "0.2.5" @@ -114,22 +108,6 @@ dependencies = [ "log", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?tag=v0.1.0#583015c08ad9855f310bdb25d5cf9abff77b5e08" -dependencies = [ - "num-traits", -] - -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities#583015c08ad9855f310bdb25d5cf9abff77b5e08" -dependencies = [ - "num-traits", -] - [[package]] name = "bitfield" version = "0.13.2" @@ -437,14 +415,11 @@ dependencies = [ name = "embedded-services" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "cortex-m", "critical-section", "embassy-futures", "embassy-sync", "log", - "mctp-rs", - "paste", "portable-atomic", "serde", ] @@ -488,19 +463,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "espi-device" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#290aa80a4c281857f3bed94581200b330119286c" -dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?tag=v0.1.0)", - "bitflags", - "num-traits", - "num_enum", - "static_assertions", - "subenum", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -576,12 +538,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "io-kit-sys" version = "0.4.1" @@ -707,17 +663,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "mctp-rs" -version = "0.1.0" -dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", - "espi-device", - "num_enum", - "smbus-pec", - "thiserror", -] - [[package]] name = "memchr" version = "2.8.0" @@ -760,15 +705,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "num_enum" version = "0.7.6" @@ -829,12 +765,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pico-de-gallo" version = "0.1.0" @@ -1205,12 +1135,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "static_cell" version = "2.1.1" @@ -1220,18 +1144,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "subenum" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee3fb942ed39f3971438fcc7e05e20717e599e14c5c7cb50edd0df2a44b274" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "syn" version = "2.0.117" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 655a00c78..c6ab73714 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -656,14 +656,11 @@ dependencies = [ name = "embedded-services" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "cortex-m", "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", - "mctp-rs", - "paste", "portable-atomic", "serde", ] @@ -916,6 +913,7 @@ name = "mctp-rs" version = "0.1.0" dependencies = [ "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "crc", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1028,6 +1026,18 @@ dependencies = [ "syn", ] +[[package]] +name = "odp-client" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embedded-io 0.7.1", + "heapless 0.9.3", + "mctp-rs", + "paste", +] + [[package]] name = "odp-service-common" version = "0.1.0" @@ -1202,6 +1212,7 @@ dependencies = [ "embedded-services", "embedded-usb-pd", "mimxrt600-fcb 0.1.0", + "odp-client", "odp-service-common", "panic-probe", "platform-service", @@ -1429,8 +1440,8 @@ version = "0.1.0" dependencies = [ "defmt 0.3.100", "embedded-mcu-hal", - "embedded-services", "num_enum", + "odp-client", "time-alarm-service-interface", ] diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index b411ed717..3ac35d6e2 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -50,6 +50,7 @@ mimxrt600-fcb = "0.1.0" embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu"} embedded-services = { path = "../../embedded-service", features = ["defmt"] } +odp-client = { path = "../../odp-client", features = ["defmt"] } odp-service-common = { path = "../../odp-service-common" } power-button-service = { path = "../../power-button-service", features = [ "defmt", diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 8673d9096..a77564d3a 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -45,8 +45,8 @@ async fn main(spawner: embassy_executor::Spawner) { ) .expect("Failed to spawn time alarm service"); - use embedded_services::relay::mctp::impl_odp_mctp_relay_handler; - impl_odp_mctp_relay_handler!( + use odp_client::impl_odp_relay_handler; + impl_odp_relay_handler!( EspiRelayHandler; TimeAlarm, 0x0B, crate::TimeAlarmServiceRelayHandlerType; ); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index b202d82b9..9d5f01d4f 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -284,6 +284,21 @@ dependencies = [ "volatile-register", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + [[package]] name = "critical-section" version = "1.2.0" @@ -336,6 +351,7 @@ dependencies = [ "embassy-sync", "embedded-services", "log", + "odp-client", "rtt-target", ] @@ -343,8 +359,8 @@ dependencies = [ name = "debug-service-messages" version = "0.1.0" dependencies = [ - "embedded-services", "num_enum", + "odp-client", ] [[package]] @@ -634,14 +650,11 @@ dependencies = [ name = "embedded-services" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "cortex-m", "critical-section", "embassy-futures", "embassy-sync", "log", - "mctp-rs", - "paste", "portable-atomic", "serde", ] @@ -914,6 +927,7 @@ name = "mctp-rs" version = "0.1.0" dependencies = [ "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "crc", "espi-device", "num_enum", "smbus-pec", @@ -992,6 +1006,17 @@ dependencies = [ "syn", ] +[[package]] +name = "odp-client" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "embedded-io 0.7.1", + "heapless 0.9.3", + "mctp-rs", + "paste", +] + [[package]] name = "odp-service-common" version = "0.1.0" diff --git a/mctp-rs/Cargo.toml b/mctp-rs/Cargo.toml index 7d16b1c5c..6cca84076 100644 --- a/mctp-rs/Cargo.toml +++ b/mctp-rs/Cargo.toml @@ -10,8 +10,9 @@ ignored = ["embedded-batteries", "espi-device", "uuid", "crc"] [features] default = [] -espi = ["dep:espi-device"] defmt = ["dep:defmt", "embedded-batteries/defmt"] +espi = ["dep:espi-device"] +odp = [] serial = ["dep:crc"] [dependencies] diff --git a/mctp-rs/src/message_type/mod.rs b/mctp-rs/src/message_type/mod.rs index bb356dc14..28e3c0fc6 100644 --- a/mctp-rs/src/message_type/mod.rs +++ b/mctp-rs/src/message_type/mod.rs @@ -1,4 +1,6 @@ mod mctp_control; +#[cfg(feature = "odp")] +pub mod odp; mod vendor_defined_pci; pub use mctp_control::*; diff --git a/mctp-rs/src/message_type/odp.rs b/mctp-rs/src/message_type/odp.rs new file mode 100644 index 000000000..40ab0d12a --- /dev/null +++ b/mctp-rs/src/message_type/odp.rs @@ -0,0 +1,332 @@ +//! ODP (Open Device Partnership) MCTP message type. +//! +//! Wire format defined by the ODP project. Message type byte `0x7D`. +//! Header layout: 32-bit bitfield, big-endian on the wire. + +use crate::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, error::MctpPacketResult}; +use bit_register::bit_register; + +/// MCTP message type byte assigned to ODP traffic. +pub const ODP_MESSAGE_TYPE: u8 = 0x7D; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum OdpService { + Battery = 0x08, + Thermal = 0x09, + TimeAlarm = 0x0B, +} + +impl OdpService { + pub fn from_u8(raw: u8) -> Option { + match raw { + 0x08 => Some(Self::Battery), + 0x09 => Some(Self::Thermal), + 0x0B => Some(Self::TimeAlarm), + _ => None, + } + } +} + +bit_register! { + #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] + pub struct OdpHeaderWireFormat: little_endian u32 { + pub message_id: u16 => [0:14], + pub is_error: u8 => [15], + pub service_id: u8 => [16:23], + pub _reserved_b24: u8 => [24], + pub is_request: u8 => [25], + pub _reserved_b26_31: u8 => [26:31], + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct OdpHeader { + pub is_request: bool, + pub service: OdpService, + pub is_error: bool, + /// Caller-supplied 15-bit message identifier. Values above `0x7FFF` + /// are silently masked on serialize (matching legacy behavior in + /// `odp-platform-common::ec-test-lib::serial.rs:106`). + pub message_id: u16, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct UnknownService(pub u8); + +impl OdpHeader { + pub const HEADER_LEN: usize = 4; + + pub fn to_be_bytes(self) -> [u8; 4] { + let w = OdpHeaderWireFormat { + is_request: self.is_request as u8, + service_id: self.service as u8, + is_error: self.is_error as u8, + message_id: self.message_id & 0x7FFF, + ..Default::default() + }; + let raw: u32 = w.try_into().expect("reserved fields are zero; cannot overflow"); + raw.to_be_bytes() + } + + pub fn from_be_bytes(bytes: [u8; 4]) -> Result { + let raw = u32::from_be_bytes(bytes); + let w: OdpHeaderWireFormat = raw + .try_into() + .expect("primitive fields u8/u16 always parse from any u32"); + let raw_service = w.service_id; + let service = OdpService::from_u8(raw_service).ok_or(UnknownService(raw_service))?; + Ok(Self { + is_request: w.is_request != 0, + service, + is_error: w.is_error != 0, + message_id: w.message_id, + }) + } +} + +impl MctpMessageHeaderTrait for OdpHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + if buffer.len() < Self::HEADER_LEN { + return Err(MctpPacketError::SerializeError("buffer too small for ODP header")); + } + buffer[..Self::HEADER_LEN].copy_from_slice(&self.to_be_bytes()); + Ok(Self::HEADER_LEN) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + if buffer.len() < Self::HEADER_LEN { + return Err(MctpPacketError::HeaderParseError("buffer too small for ODP header")); + } + let mut b = [0u8; 4]; + b.copy_from_slice(&buffer[..Self::HEADER_LEN]); + let header = Self::from_be_bytes(b).map_err(|_| MctpPacketError::HeaderParseError("unknown ODP service id"))?; + Ok((header, &buffer[Self::HEADER_LEN..])) + } +} + +/// An ODP message: a typed header followed by an opaque body payload. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct OdpMessage<'a> { + pub header: OdpHeader, + pub body: &'a [u8], +} + +impl<'a> OdpMessage<'a> { + /// Serialize the full ODP wire packet (header + body) into `out`. + /// + /// Returns the total number of bytes written (`OdpHeader::HEADER_LEN + + /// self.body.len()`) on success, or `MctpPacketError::SerializeError` if + /// the output buffer is too small. + /// + /// This is the convenience entry point for callers that hold a complete + /// `OdpMessage` and want a single call to produce on-wire bytes. The + /// framework-level trait method, `::serialize`, + /// is body-only — the header is serialized separately by the packet framing + /// layer when using the MCTP trait infrastructure. See `MctpMessageTrait` + /// in `mctp-rs/src/message_type/mod.rs` for the framework pattern. + pub fn serialize_with_header(&self, out: &mut [u8]) -> MctpPacketResult { + let needed = OdpHeader::HEADER_LEN + self.body.len(); + if out.len() < needed { + return Err(MctpPacketError::SerializeError("buffer too small for ODP message")); + } + out[..OdpHeader::HEADER_LEN].copy_from_slice(&self.header.to_be_bytes()); + out[OdpHeader::HEADER_LEN..needed].copy_from_slice(self.body); + Ok(needed) + } +} + +/// Body-only serialization per the `MctpMessageTrait` framework contract. +/// The header is serialized separately at the packet framing layer. +/// Use [`OdpMessage::serialize_with_header`] for a single-call full-packet +/// serialization. +impl<'buf> MctpMessageTrait<'buf> for OdpMessage<'buf> { + type Header = OdpHeader; + const MESSAGE_TYPE: u8 = ODP_MESSAGE_TYPE; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + if buffer.len() < self.body.len() { + return Err(MctpPacketError::SerializeError("buffer too small for ODP message body")); + } + buffer[..self.body.len()].copy_from_slice(self.body); + Ok(self.body.len()) + } + + fn deserialize(header: &Self::Header, buffer: &'buf [u8]) -> MctpPacketResult { + Ok(OdpMessage { + header: *header, + body: buffer, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn message_type_constant_is_0x7d() { + assert_eq!(ODP_MESSAGE_TYPE, 0x7D); + } + + #[test] + fn odp_service_known_codes_round_trip() { + for (raw, service) in [ + (0x08, OdpService::Battery), + (0x09, OdpService::Thermal), + (0x0B, OdpService::TimeAlarm), + ] { + assert_eq!(OdpService::from_u8(raw), Some(service)); + assert_eq!(service as u8, raw); + } + } + + #[test] + fn odp_service_unknown_code_is_none() { + assert_eq!(OdpService::from_u8(0xFF), None); + } + + #[test] + fn odp_header_round_trip_via_wire_bytes() { + let original = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 0x1234, + }; + let bytes = original.to_be_bytes(); + let parsed = OdpHeader::from_be_bytes(bytes).expect("known service"); + assert_eq!(parsed, original); + } + + #[test] + fn odp_header_bit_layout_is_stable() { + // Snapshot matches odp-platform-common::ec-test-lib::serial.rs:509: + // build_odp_header(is_request=true, service=Battery=0x08, msg_id=2) + // => [0x02, 0x08, 0x00, 0x02] + let h = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 2, + }; + assert_eq!(h.to_be_bytes(), [0x02, 0x08, 0x00, 0x02]); + } + + #[test] + fn odp_header_response_round_trip() { + // is_request=false (response), is_error=false, msg_id=2 — mirrors serial.rs:530. + let h = OdpHeader { + is_request: false, + service: OdpService::Battery, + is_error: false, + message_id: 2, + }; + let parsed = OdpHeader::from_be_bytes(h.to_be_bytes()).expect("known service"); + assert_eq!(parsed, h); + } + + #[test] + fn odp_header_rejects_unknown_service() { + // service_id 0xFF is not in the known set. + let raw_u32: u32 = (1 << 25) | (0xFF << 16) | 1; + let bytes = raw_u32.to_be_bytes(); + assert!(matches!(OdpHeader::from_be_bytes(bytes), Err(UnknownService(0xFF)))); + } + + #[test] + fn odp_message_serializes_header_then_body() { + use crate::test_util::TestMedium; + let msg = OdpMessage { + header: OdpHeader { + service: OdpService::Battery, + is_error: false, + message_id: 7, + is_request: true, + }, + body: &[0xDE, 0xAD, 0xBE, 0xEF], + }; + let mut out = [0u8; 32]; + let n = msg.serialize_with_header::(&mut out).unwrap(); + assert_eq!(n, OdpHeader::HEADER_LEN + 4); + assert_eq!(&out[..OdpHeader::HEADER_LEN], &msg.header.to_be_bytes()); + assert_eq!( + &out[OdpHeader::HEADER_LEN..OdpHeader::HEADER_LEN + 4], + &[0xDE, 0xAD, 0xBE, 0xEF] + ); + } + + #[test] + fn odp_header_implements_mctp_message_header_trait() { + use crate::MctpMessageHeaderTrait; + use crate::test_util::TestMedium; + + let h = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 0x42, + }; + let mut buf = [0u8; 4]; + let written = h.serialize::(&mut buf).unwrap(); + assert_eq!(written, 4); + let (parsed, rest) = OdpHeader::deserialize::(&buf).unwrap(); + assert_eq!(rest.len(), 0); + assert_eq!(parsed, h); + } + + #[test] + fn wire_snapshot_matches_legacy_impls_battery_get_bst() { + // Source of truth: odp-platform-common/ec/test-lib/src/serial.rs:505-510 + // build_odp_header(is_request=true, service=Battery=0x08, msg_id=2) + // produces [0x02, 0x08, 0x00, 0x02]. + let h = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 2, + }; + assert_eq!(h.to_be_bytes(), [0x02, 0x08, 0x00, 0x02]); + } + + #[test] + fn wire_snapshot_matches_legacy_impls_thermal_request() { + // Source: serial.rs:513-516. + let h = OdpHeader { + is_request: true, + service: OdpService::Thermal, + is_error: false, + message_id: 1, + }; + assert_eq!(h.to_be_bytes(), [0x02, 0x09, 0x00, 0x01]); + } + + #[test] + fn wire_snapshot_matches_legacy_impls_time_alarm_15bit_id() { + // Source: serial.rs:520-526. service_id=0x0B, msg_id=0x1234. + let h = OdpHeader { + is_request: true, + service: OdpService::TimeAlarm, + is_error: false, + message_id: 0x1234, + }; + let bytes = h.to_be_bytes(); + let parsed = OdpHeader::from_be_bytes(bytes).expect("known service"); + assert_eq!(parsed, h); + // raw_u32 = (1<<25) | (0x0B<<16) | 0x1234 = 0x020B1234 → [0x02, 0x0B, 0x12, 0x34] + assert_eq!(bytes, [0x02, 0x0B, 0x12, 0x34]); + } + + #[test] + fn wire_snapshot_battery_response() { + // Source: serial.rs:530-538. is_request=false, service=Battery, msg_id=2. + let h = OdpHeader { + is_request: false, + service: OdpService::Battery, + is_error: false, + message_id: 2, + }; + // raw_u32 = (0<<25) | (0x08<<16) | 2 = 0x00080002 → [0x00, 0x08, 0x00, 0x02] + assert_eq!(h.to_be_bytes(), [0x00, 0x08, 0x00, 0x02]); + } +} diff --git a/odp-client/Cargo.toml b/odp-client/Cargo.toml new file mode 100644 index 000000000..73d464979 --- /dev/null +++ b/odp-client/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "odp-client" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Transport-blind sync ODP client and server-side service-handler macro." + +[features] +default = [] +defmt = ["mctp-rs/defmt", "dep:defmt"] + +[dependencies] +bitfield = { workspace = true } +defmt = { workspace = true, optional = true } +embedded-io = { workspace = true } +heapless = { workspace = true } +mctp-rs = { workspace = true, features = ["odp", "serial", "espi"] } +paste = { workspace = true } diff --git a/odp-client/src/client.rs b/odp-client/src/client.rs new file mode 100644 index 000000000..9ea9536ef --- /dev/null +++ b/odp-client/src/client.rs @@ -0,0 +1,78 @@ +use crate::{OdpError, OdpHeader, OdpTransport}; + +/// Decoded ODP response returned by [`Relay::invoke`]. +/// +/// The body slice borrows from the relay's internal assembly buffer; the +/// caller must finish reading the response before calling `invoke` again or +/// dropping the relay. +#[derive(Debug)] +pub struct OdpResponse<'a> { + pub header: OdpHeader, + pub body: &'a [u8], +} + +/// Transport-blind abstraction over "issue a request and receive a +/// decoded response". Per-service code depends on `R: Relay` (or +/// `&mut dyn Relay`) rather than a concrete [`OdpClient`]. +/// +/// The returned [`OdpResponse`] borrows from the relay's internal +/// buffer, so the borrow checker prevents calling `invoke` again or +/// dropping the relay until the response is consumed — no copy is required. +pub trait Relay { + /// Encode `header` + `body`, send via the underlying transport, + /// receive the response, validate the `message_id` round-trip, and + /// return the decoded [`OdpResponse`]. + fn invoke<'a>(&'a mut self, header: OdpHeader, body: &[u8]) -> Result, OdpError>; +} + +/// Sync ODP client. Owns the transport and a 256-byte buffer used +/// sequentially for request encoding and response decoding. +pub struct OdpClient { + transport: T, + buf: [u8; 256], +} + +impl OdpClient { + /// Create a new client, consuming `transport` by value. + pub fn new(transport: T) -> Self { + Self { + transport, + buf: [0u8; 256], + } + } +} + +impl Relay for OdpClient { + fn invoke<'a>(&'a mut self, header: OdpHeader, body: &[u8]) -> Result, OdpError> { + let need = 4 + body.len(); + if need > self.buf.len() { + return Err(OdpError::BufferTooSmall); + } + + // Compose request: 4-byte BE header + body. + self.buf[..4].copy_from_slice(&header.to_be_bytes()); + self.buf[4..need].copy_from_slice(body); + self.transport.send_message(&self.buf[..need])?; + + // Receive response into the same buffer (send is complete). + let n = self.transport.recv_message(&mut self.buf)?; + if n < 4 { + return Err(OdpError::Decode); + } + let mut hb = [0u8; 4]; + hb.copy_from_slice(&self.buf[..4]); + let resp_header = OdpHeader::from_be_bytes(hb).map_err(|_| OdpError::Decode)?; + + if resp_header.message_id != header.message_id { + return Err(OdpError::UnexpectedMessageId { + expected: header.message_id, + got: resp_header.message_id, + }); + } + + Ok(OdpResponse { + header: resp_header, + body: &self.buf[4..n], + }) + } +} diff --git a/odp-client/src/error.rs b/odp-client/src/error.rs new file mode 100644 index 000000000..4bb1e3a9c --- /dev/null +++ b/odp-client/src/error.rs @@ -0,0 +1,40 @@ +/// Errors surfaced by the `odp-client` API. +/// +/// Transport implementations map their own lower-level errors into these +/// variants so callers see one consistent surface. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OdpError { + /// Underlying transport reported a failure. + Transport, + /// Timed out waiting for a response from the peer. + Timeout, + /// Caller-supplied buffer was too small to hold the message. + BufferTooSmall, + /// Peer responded with a different `OdpService` than the request. + UnexpectedResponseKind, + /// Peer responded with a different `message_id` than the request. + UnexpectedMessageId { expected: u16, got: u16 }, + /// Failed to decode an incoming ODP message (malformed header / body). + Decode, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for OdpError { + fn format(&self, f: defmt::Formatter) { + match self { + OdpError::Transport => defmt::write!(f, "OdpError::Transport"), + OdpError::Timeout => defmt::write!(f, "OdpError::Timeout"), + OdpError::BufferTooSmall => defmt::write!(f, "OdpError::BufferTooSmall"), + OdpError::UnexpectedResponseKind => defmt::write!(f, "OdpError::UnexpectedResponseKind"), + OdpError::UnexpectedMessageId { expected, got } => { + defmt::write!( + f, + "OdpError::UnexpectedMessageId {{ expected: {}, got: {} }}", + expected, + got + ) + } + OdpError::Decode => defmt::write!(f, "OdpError::Decode"), + } + } +} diff --git a/odp-client/src/lib.rs b/odp-client/src/lib.rs new file mode 100644 index 000000000..490b0c7f2 --- /dev/null +++ b/odp-client/src/lib.rs @@ -0,0 +1,33 @@ +#![no_std] +//! Transport-blind sync ODP client and server-side service-handler macro. +//! +//! Callers issue ODP requests through an [`OdpClient`] (or any other +//! [`Relay`]) without depending on a concrete transport. The transport is +//! selected at construction time and hidden behind the [`OdpTransport`] +//! trait, so swapping the underlying medium does not affect call sites. + +pub use client::{OdpClient, OdpResponse, Relay}; +pub use error::OdpError; +pub use loopback::LoopbackTransport; +pub use mctp_rs::odp::{OdpHeader, OdpService}; +pub use serial::SerialTransport; +pub use serializable::{MessageSerializationError, SerializableMessage, SerializableResult}; +pub use server::{RelayHandler, RelayHeader, RelayResponse, RelayServiceHandler, RelayServiceHandlerTypes}; +pub use transport::OdpTransport; + +mod client; +mod error; +mod loopback; +mod serial; +pub mod serializable; +pub mod server; +mod transport; + +/// Hidden re-exports used by the [`impl_odp_relay_handler`] macro. +/// Not part of the public API — do not depend on these directly. +#[doc(hidden)] +pub mod _macro_internal { + pub use bitfield; + pub use mctp_rs; + pub use paste; +} diff --git a/odp-client/src/loopback.rs b/odp-client/src/loopback.rs new file mode 100644 index 000000000..5f1d20e6c --- /dev/null +++ b/odp-client/src/loopback.rs @@ -0,0 +1,52 @@ +use crate::{OdpError, OdpTransport}; +use heapless::Vec; + +/// In-memory test double for [`OdpTransport`]. Echoes what was sent on +/// the next [`OdpTransport::recv_message`] call. Capacity is fixed at +/// 256 bytes. +/// +/// # Lifecycle +/// +/// - `send_message(payload)` replaces any previously buffered bytes +/// with `payload`. +/// - `recv_message(buf)` copies the buffered bytes into `buf` and +/// clears the internal buffer. A subsequent `recv_message` without +/// an intervening `send_message` returns [`OdpError::Transport`]. +pub struct LoopbackTransport { + queued: Vec, +} + +impl LoopbackTransport { + pub fn new() -> Self { + Self { queued: Vec::new() } + } +} + +impl Default for LoopbackTransport { + fn default() -> Self { + Self::new() + } +} + +impl OdpTransport for LoopbackTransport { + fn send_message(&mut self, payload: &[u8]) -> Result<(), OdpError> { + self.queued.clear(); + self.queued + .extend_from_slice(payload) + .map_err(|_| OdpError::BufferTooSmall)?; + Ok(()) + } + + fn recv_message(&mut self, buf: &mut [u8]) -> Result { + if self.queued.is_empty() { + return Err(OdpError::Transport); + } + if buf.len() < self.queued.len() { + return Err(OdpError::BufferTooSmall); + } + let n = self.queued.len(); + buf[..n].copy_from_slice(&self.queued); + self.queued.clear(); + Ok(n) + } +} diff --git a/odp-client/src/serial.rs b/odp-client/src/serial.rs new file mode 100644 index 000000000..80b80344c --- /dev/null +++ b/odp-client/src/serial.rs @@ -0,0 +1,157 @@ +use crate::{OdpError, OdpTransport}; +use embedded_io::{Read, ReadExactError, Write}; +use mctp_rs::odp::ODP_MESSAGE_TYPE; +use mctp_rs::{ + EndpointId, MctpMedium, MctpMessageHeaderTrait, MctpMessageTag, MctpMessageTrait, MctpPacketContext, + MctpPacketError, MctpPacketResult, MctpReplyContext, MctpSequenceNumber, MctpSerialMedium, +}; + +/// MCTP serial framing END flag (DSP0253 0x7E). +const SERIAL_END_FLAG: u8 = 0x7E; + +const ASSEMBLY_BUF_LEN: usize = 128; +const RX_FRAME_BUF_LEN: usize = 128; + +struct RawHeader([u8; 4]); + +impl MctpMessageHeaderTrait for RawHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + if buffer.len() < 4 { + return Err(MctpPacketError::SerializeError("buffer too small for odp header")); + } + buffer[..4].copy_from_slice(&self.0); + Ok(4) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + if buffer.len() < 4 { + return Err(MctpPacketError::HeaderParseError("buffer too small for odp header")); + } + let mut h = [0u8; 4]; + h.copy_from_slice(&buffer[..4]); + Ok((RawHeader(h), &buffer[4..])) + } +} + +struct RawBody<'b>(&'b [u8]); + +impl<'buf> MctpMessageTrait<'buf> for RawBody<'buf> { + type Header = RawHeader; + const MESSAGE_TYPE: u8 = ODP_MESSAGE_TYPE; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let n = self.0.len(); + if buffer.len() < n { + return Err(MctpPacketError::SerializeError("buffer too small for odp body")); + } + buffer[..n].copy_from_slice(self.0); + Ok(n) + } + + fn deserialize(_h: &RawHeader, buffer: &'buf [u8]) -> MctpPacketResult { + Ok(RawBody(buffer)) + } +} + +fn reply_context(src: EndpointId, dst: EndpointId) -> MctpReplyContext { + MctpReplyContext { + source_endpoint_id: src, + destination_endpoint_id: dst, + packet_sequence_number: MctpSequenceNumber::new(0), + message_tag: MctpMessageTag::try_from(0).expect("tag 0 always valid"), + medium_context: (), + } +} + +/// ODP-over-serial transport. +/// +/// Owns the UART and internal framing buffers. Framing is handled +/// internally; callers hand in raw ODP wire bytes (4-byte header + body) +/// and receive raw ODP wire bytes back. +/// +/// # Endpoint IDs +/// +/// `src_eid` / `dst_eid` populate the framing header of outbound frames. +/// Inbound frames are decoded but the EIDs are not validated against +/// expectation — the serial medium has no addressing of its own. +pub struct SerialTransport { + uart: U, + src_eid: EndpointId, + dst_eid: EndpointId, + rx_frame_buf: [u8; RX_FRAME_BUF_LEN], + assembly_buf: [u8; ASSEMBLY_BUF_LEN], +} + +impl SerialTransport { + /// Construct a transport over `uart`. `src_eid` / `dst_eid` are the + /// endpoint IDs stamped into every outbound frame. + pub fn new(uart: U, src_eid: EndpointId, dst_eid: EndpointId) -> Self { + Self { + uart, + src_eid, + dst_eid, + rx_frame_buf: [0u8; RX_FRAME_BUF_LEN], + assembly_buf: [0u8; ASSEMBLY_BUF_LEN], + } + } +} + +impl OdpTransport for SerialTransport { + fn send_message(&mut self, payload: &[u8]) -> Result<(), OdpError> { + if payload.len() < 4 { + return Err(OdpError::BufferTooSmall); + } + let mut header = [0u8; 4]; + header.copy_from_slice(&payload[..4]); + let body = &payload[4..]; + + // Use a local tx buffer so `assembly_buf` stays free for recv. + let mut tx_buf = [0u8; ASSEMBLY_BUF_LEN]; + let mut tx_ctx = MctpPacketContext::::new(MctpSerialMedium, &mut tx_buf); + let reply_ctx = reply_context(self.src_eid, self.dst_eid); + let mut state = tx_ctx + .serialize_packet(reply_ctx, (RawHeader(header), RawBody(body))) + .map_err(|_| OdpError::Transport)?; + while let Some(pkt) = state.next() { + let pkt = pkt.map_err(|_| OdpError::Transport)?; + self.uart.write_all(pkt).map_err(|_| OdpError::Transport)?; + } + Ok(()) + } + + fn recv_message(&mut self, buf: &mut [u8]) -> Result { + // 1. Read serial frame (bytes until 0x7E) into rx_frame_buf. + let mut filled = 0usize; + loop { + if filled >= self.rx_frame_buf.len() { + return Err(OdpError::BufferTooSmall); + } + let mut byte = [0u8; 1]; + match self.uart.read_exact(&mut byte) { + Ok(()) => {} + Err(ReadExactError::UnexpectedEof) => return Err(OdpError::Transport), + Err(ReadExactError::Other(_)) => return Err(OdpError::Transport), + } + self.rx_frame_buf[filled] = byte[0]; + filled += 1; + if byte[0] == SERIAL_END_FLAG { + break; + } + } + + // 2. Strip framing via a fresh PacketContext borrowing assembly_buf. + let mut rx_ctx = MctpPacketContext::::new(MctpSerialMedium, &mut self.assembly_buf); + let message = rx_ctx + .deserialize_packet(&self.rx_frame_buf[..filled]) + .map_err(|_| OdpError::Decode)? + .ok_or(OdpError::Decode)?; + + // 3. Copy out the message body (= ODP header+body wire bytes). + let body = message.message_buffer.body(); + if buf.len() < body.len() { + return Err(OdpError::BufferTooSmall); + } + buf[..body.len()].copy_from_slice(body); + Ok(body.len()) + } +} diff --git a/odp-client/src/serializable.rs b/odp-client/src/serializable.rs new file mode 100644 index 000000000..dab03dca3 --- /dev/null +++ b/odp-client/src/serializable.rs @@ -0,0 +1,98 @@ +//! Serialization/deserialization helpers for ODP relay request/response message types. + +/// Error type for serializing/deserializing messages. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MessageSerializationError { + /// The message payload does not represent a valid message. + InvalidPayload(&'static str), + + /// The message discriminant does not represent a known message type. + UnknownMessageDiscriminant(u16), + + /// The provided buffer is too small to serialize the message. + BufferTooSmall, + + /// Unspecified error. + Other(&'static str), +} + +/// Trait for serializing and deserializing messages. +pub trait SerializableMessage: Sized { + /// Serialize the message into `buffer`, returning the number of bytes written. + fn serialize(self, buffer: &mut [u8]) -> Result; + + /// Discriminant used to identify the concrete message type on deserialize. + fn discriminant(&self) -> u16; + + /// Deserialize a message of the type identified by `discriminant` from `buffer`. + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; +} + +// Sealed: only `Result` may implement `SerializableResult`. Custom response +// types should implement `SerializableMessage` on a Response and an Error type and +// use `Result` for the result. +#[doc(hidden)] +mod private { + pub trait Sealed {} + + impl Sealed for Result {} +} + +/// Sealed trait implemented for `Result` where both `T` and `E` +/// implement [`SerializableMessage`]. Used for response ("result") types. +pub trait SerializableResult: private::Sealed + Sized { + /// The success arm's message type. + type SuccessType: SerializableMessage; + + /// The error arm's message type. + type ErrorType: SerializableMessage; + + /// `true` if this result represents a successful operation. + fn is_ok(&self) -> bool; + + /// Discriminant of the inner success or error message. Success and + /// error variants may reuse the same discriminant. + fn discriminant(&self) -> u16; + + /// Serialize the inner message into `buffer`, returning the number of bytes written. + fn serialize(self, buffer: &mut [u8]) -> Result; + + /// Deserialize a result. `is_error` selects between success and error decoding. + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; +} + +impl SerializableResult for Result +where + T: SerializableMessage, + E: SerializableMessage, +{ + type SuccessType = T; + type ErrorType = E; + + fn is_ok(&self) -> bool { + Result::::is_ok(self) + } + + fn discriminant(&self) -> u16 { + match self { + Ok(success_value) => success_value.discriminant(), + Err(error_value) => error_value.discriminant(), + } + } + + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Ok(success_value) => success_value.serialize(buffer), + Err(error_value) => error_value.serialize(buffer), + } + } + + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result { + if is_error { + Ok(Err(E::deserialize(discriminant, buffer)?)) + } else { + Ok(Ok(T::deserialize(discriminant, buffer)?)) + } + } +} diff --git a/odp-client/src/server.rs b/odp-client/src/server.rs new file mode 100644 index 000000000..858826f22 --- /dev/null +++ b/odp-client/src/server.rs @@ -0,0 +1,389 @@ +//! Server-side relay traits and the [`impl_odp_relay_handler`] macro. +//! +//! Provides traits that individual service handlers implement, plus the +//! macro that aggregates them into a single [`RelayHandler`] suitable for +//! plugging into a relay service (e.g. the eSPI service). + +/// Companion-types trait for [`RelayServiceHandler`]: declares the +/// request and result types for a single service. +pub trait RelayServiceHandlerTypes { + /// The request type this service handler processes. + type RequestType: crate::serializable::SerializableMessage; + + /// The result type this service handler produces. + type ResultType: crate::serializable::SerializableResult; +} + +/// Trait for a service that can be relayed over an external bus (e.g. +/// battery service, thermal service, time-alarm service). +pub trait RelayServiceHandler: RelayServiceHandlerTypes { + /// Process the provided request and yield a result. + fn process_request<'a>( + &'a self, + request: Self::RequestType, + ) -> impl core::future::Future + 'a; +} + +// Traits below this point are intended for consumption by relay services (e.g. the eSPI service), +// not individual services that want their messages relayed. Implement these via the +// `impl_odp_relay_handler` macro rather than by hand. + +/// Additional methods required on the relay header type. Implemented by +/// the [`impl_odp_relay_handler`] macro; do not implement directly. +#[doc(hidden)] +pub trait RelayHeader { + /// Return the ID of the service associated with the request. + fn get_service_id(&self) -> ServiceIdType; +} + +/// Additional methods required on the relay response type. Implemented +/// by the [`impl_odp_relay_handler`] macro; do not implement directly. +#[doc(hidden)] +pub trait RelayResponse { + /// Construct a header for this result given its service ID. + fn create_header(&self, service_id: &ServiceIdType) -> HeaderType; +} + +/// Aggregates a collection of services into a single relay surface. +/// Implemented by the [`impl_odp_relay_handler`] macro; do not implement +/// directly. +pub trait RelayHandler { + /// The type that uniquely identifies individual services. Generally a C-style enum. + type ServiceIdType: Into + TryFrom + Copy; + + /// The header type used by request and result enums. + type HeaderType: mctp_rs::MctpMessageHeaderTrait + RelayHeader; + + /// An enum over all possible request types. + type RequestEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType>; + + /// An enum over all possible result types. + type ResultEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType> + + RelayResponse; + + /// Process the provided request and yield a result. + fn process_request<'a>( + &'a self, + message: Self::RequestEnumType, + ) -> impl core::future::Future + 'a; +} + +/// Generates a relay type that aggregates multiple service handlers into a +/// single [`RelayHandler`] suitable for use by a relay service (e.g. the +/// eSPI service). This is the recommended way to obtain a `RelayHandler` +/// — do not implement that trait by hand. +/// +/// Inputs: +/// - `relay_type_name`: identifier for the generated struct +/// - For each service: +/// - `service_name`: identifier used to name fields and variants +/// - `service_id`: unique `u8` addressing the service on the EC +/// - `service_handler_type`: a type implementing [`RelayServiceHandler`] +/// +/// The generated type exposes a `new` constructor taking one +/// `service_handler_type` argument per registered service. +/// +/// # Example +/// +/// ```ignore +/// impl_odp_relay_handler!( +/// MyRelayHandlerType; +/// Battery, 0x9, battery_service_relay::RelayHandler>; +/// TimeAlarm, 0xB, time_alarm_service_relay::RelayHandler>; +/// ); +/// +/// let relay_handler = MyRelayHandlerType::new(battery_handler, time_alarm_handler); +/// // Pass relay_handler to a relay service that is generic over `impl RelayHandler`. +/// ``` +#[macro_export] +macro_rules! impl_odp_relay_handler { + ( + $relay_type_name:ident; + $( + $service_name:ident, + $service_id:expr, + $service_handler_type:ty; + )+ + ) => { + $crate::_macro_internal::paste::paste! { + mod [< _odp_impl_ $relay_type_name:snake >] { + use $crate::_macro_internal::bitfield::bitfield; + use core::convert::Infallible; + use $crate::_macro_internal::mctp_rs::smbus_espi::SmbusEspiMedium; + use $crate::_macro_internal::mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; + use $crate::serializable::{SerializableMessage, SerializableResult}; + use $crate::server::RelayServiceHandler; + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[repr(u8)] + pub enum OdpService { + $( + $service_name = $service_id, + )+ + } + + impl From for u8 { + fn from(val: OdpService) -> u8 { + val as u8 + } + } + + impl TryFrom for OdpService { + type Error = u8; + fn try_from(value: u8) -> Result { + match value { + $( + $service_id => Ok(OdpService::$service_name), + )+ + other => Err(other), + } + } + } + + pub enum HostRequest { + $( + $service_name(<$service_handler_type as $crate::server::RelayServiceHandlerTypes>::RequestType), + )+ + } + + impl MctpMessageTrait<'_> for HostRequest { + type Header = OdpHeader; + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) + .map_err(|_| MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + Ok(match header.service { + $( + OdpService::$service_name => Self::$service_name( + <$service_handler_type as $crate::server::RelayServiceHandlerTypes>::RequestType::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, + ), + )+ + }) + } + } + + bitfield! { + /// Wire format for ODP headers. Not user-facing — use OdpHeader instead. + #[derive(Copy, Clone, PartialEq, Eq)] + struct OdpHeaderWireFormat(u32); + impl Debug; + impl new; + /// If true, represents a request; otherwise, represents a result + is_request, set_is_request: 25; + + /// The service ID that this message is related to + /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. + u8, service_id, set_service_id: 23, 16; + + /// On results, indicates if the result message is an error. Unused on requests. + is_error, set_is_error: 15; + + /// The message type/discriminant + u16, message_id, set_message_id: 14, 0; + } + + #[derive(Copy, Clone, PartialEq, Eq)] + pub enum OdpMessageType { + Request, + Result { is_error: bool }, + } + + #[derive(Copy, Clone, PartialEq, Eq)] + pub struct OdpHeader { + pub message_type: OdpMessageType, + pub service: OdpService, + pub message_id: u16, + } + + impl From for OdpHeaderWireFormat { + fn from(src: OdpHeader) -> Self { + Self::new( + matches!(src.message_type, OdpMessageType::Request), + src.service.into(), + match src.message_type { + OdpMessageType::Request => false, // unused on requests + OdpMessageType::Result { is_error } => is_error, + }, + src.message_id, + ) + } + } + + impl TryFrom for OdpHeader { + type Error = MctpPacketError; + + fn try_from(src: OdpHeaderWireFormat) -> Result { + let service = OdpService::try_from(src.service_id()) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; + + let message_type = if src.is_request() { + OdpMessageType::Request + } else { + OdpMessageType::Result { + is_error: src.is_error(), + } + }; + + Ok(OdpHeader { + message_type, + service, + message_id: src.message_id(), + }) + } + } + + impl MctpMessageHeaderTrait for OdpHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let wire_format = OdpHeaderWireFormat::from(self); + let bytes = wire_format.0.to_be_bytes(); + buffer + .get_mut(0..bytes.len()) + .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? + .copy_from_slice(&bytes); + + Ok(bytes.len()) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + let bytes = buffer + .get(0..core::mem::size_of::()) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; + let raw = u32::from_be_bytes( + bytes + .try_into() + .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + ); + + let parsed_wire_format = OdpHeaderWireFormat(raw); + let header = OdpHeader::try_from(parsed_wire_format) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; + + Ok(( + header, + buffer + .get(core::mem::size_of::()..) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + )) + } + } + + impl $crate::server::RelayHeader for OdpHeader { + fn get_service_id(&self) -> OdpService { + self.service + } + } + + #[derive(Clone)] + pub enum HostResult { + $( + $service_name(<$service_handler_type as $crate::server::RelayServiceHandlerTypes>::ResultType), + )+ + } + + impl $crate::server::RelayResponse for HostResult { + fn create_header(&self, service_id: &OdpService) -> OdpHeader { + match (self) { + $( + (HostResult::$service_name(result)) => OdpHeader { + message_type: OdpMessageType::Result { is_error: !result.is_ok() }, + service: *service_id, + message_id: result.discriminant(), + }, + )+ + } + } + } + + impl MctpMessageTrait<'_> for HostResult { + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + type Header = OdpHeader; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostResult::$service_name(result) => result + .serialize(buffer) + .map_err(|_| MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + match header.service { + $( + OdpService::$service_name => { + match header.message_type { + OdpMessageType::Request => { + Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) + } + OdpMessageType::Result { is_error } => { + Ok(HostResult::$service_name(<$service_handler_type as $crate::server::RelayServiceHandlerTypes>::ResultType::deserialize(is_error, header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) + } + } + }, + )+ + } + } + } + + + pub struct $relay_type_name { + $( + [<$service_name:snake _handler>]: $service_handler_type, + )+ + } + + impl $relay_type_name { + pub fn new( + $( + [<$service_name:snake _handler>]: $service_handler_type, + )+ + ) -> Self { + Self { + $( + [<$service_name:snake _handler>], + )+ + } + } + } + + impl $crate::server::RelayHandler for $relay_type_name { + type ServiceIdType = OdpService; + type HeaderType = OdpHeader; + type RequestEnumType = HostRequest; + type ResultEnumType = HostResult; + + fn process_request<'a>( + &'a self, + message: HostRequest, + ) -> impl core::future::Future + 'a { + async move { + match message { + $( + HostRequest::$service_name(request) => { + let result = self.[<$service_name:snake _handler>].process_request(request).await; + HostResult::$service_name(result) + } + )+ + } + } + } + } + } // end mod __odp_impl + + // Allows this generated relay type to be publicly re-exported + pub use [< _odp_impl_ $relay_type_name:snake >]::$relay_type_name; + + } // end paste! + }; // end macro arm +} // end macro diff --git a/odp-client/src/transport.rs b/odp-client/src/transport.rs new file mode 100644 index 000000000..6fa4c22ba --- /dev/null +++ b/odp-client/src/transport.rs @@ -0,0 +1,17 @@ +use crate::OdpError; + +/// Carries fully-framed ODP messages over an arbitrary medium. +/// +/// Implementations own the underlying medium and any framing. Callers +/// hand in raw ODP wire bytes (header + body) and receive raw ODP wire +/// bytes back — they do not need to know what is underneath. This is the +/// seam that lets transports be swapped without rewriting callers. +pub trait OdpTransport { + /// Send one fully-framed ODP message (header + body, wire bytes). + fn send_message(&mut self, payload: &[u8]) -> Result<(), OdpError>; + + /// Receive one fully-framed ODP message into `buf`. Returns the number + /// of bytes written, or [`OdpError::BufferTooSmall`] if `buf` cannot + /// hold the entire message. + fn recv_message(&mut self, buf: &mut [u8]) -> Result; +} diff --git a/odp-client/tests/client_invoke.rs b/odp-client/tests/client_invoke.rs new file mode 100644 index 000000000..0b72c437f --- /dev/null +++ b/odp-client/tests/client_invoke.rs @@ -0,0 +1,112 @@ +use odp_client::{LoopbackTransport, OdpClient, OdpError, OdpHeader, OdpService, Relay}; + +#[test] +fn invoke_round_trips_request_and_returns_response() { + // LoopbackTransport echoes the request bytes back as the response. + let transport = LoopbackTransport::new(); + let mut client = OdpClient::new(transport); + + let req_header = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 0x11, + }; + + let resp = client.invoke(req_header, &[0xAA, 0xBB]).unwrap(); + assert_eq!(resp.header.service, OdpService::Battery); + assert_eq!(resp.header.message_id, 0x11); + assert_eq!(resp.body, &[0xAA, 0xBB]); +} + +#[test] +fn invoke_with_oversized_body_returns_buffer_too_small() { + // Internal buf is 256 bytes; 4-byte header + 253-byte body = 257 → overflow. + let mut client = OdpClient::new(LoopbackTransport::new()); + let req_header = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 1, + }; + let huge = [0u8; 253]; + let err = client.invoke(req_header, &huge).unwrap_err(); + assert_eq!(err, OdpError::BufferTooSmall); +} + +/// Inline stub transport that allows pre-loading recv bytes independently +/// of what was sent. Used to test message_id mismatch detection. +struct StubTransport { + recv_bytes: [u8; 64], + recv_len: usize, +} + +impl StubTransport { + fn with_recv(bytes: &[u8]) -> Self { + let mut buf = [0u8; 64]; + buf[..bytes.len()].copy_from_slice(bytes); + Self { + recv_bytes: buf, + recv_len: bytes.len(), + } + } +} + +impl odp_client::OdpTransport for StubTransport { + fn send_message(&mut self, _data: &[u8]) -> Result<(), OdpError> { + Ok(()) + } + fn recv_message(&mut self, buf: &mut [u8]) -> Result { + let n = self.recv_len; + buf[..n].copy_from_slice(&self.recv_bytes[..n]); + Ok(n) + } +} + +#[test] +fn invoke_with_mismatched_response_message_id_returns_unexpected() { + // Pre-load a response with message_id=0x99; request uses message_id=0x11. + let stale_header = OdpHeader { + is_request: false, + service: OdpService::Battery, + is_error: false, + message_id: 0x99, + }; + let recv_bytes = stale_header.to_be_bytes(); + let transport = StubTransport::with_recv(&recv_bytes); + let mut client = OdpClient::new(transport); + + let req_header = OdpHeader { + is_request: true, + service: OdpService::Battery, + is_error: false, + message_id: 0x11, + }; + let err = client.invoke(req_header, &[]).unwrap_err(); + assert_eq!( + err, + OdpError::UnexpectedMessageId { + expected: 0x11, + got: 0x99 + } + ); +} + +#[test] +fn relay_is_object_safe_via_dyn() { + // Proves the trait is object-safe: dynamic dispatch through &mut dyn Relay. + let mut client = OdpClient::new(LoopbackTransport::new()); + let relay: &mut dyn Relay = &mut client; + + let req_header = OdpHeader { + is_request: true, + service: OdpService::Thermal, + is_error: false, + message_id: 0x42, + }; + + let resp = relay.invoke(req_header, &[0xDE, 0xAD]).unwrap(); + assert_eq!(resp.header.service, OdpService::Thermal); + assert_eq!(resp.header.message_id, 0x42); + assert_eq!(resp.body, &[0xDE, 0xAD]); +} diff --git a/odp-client/tests/error.rs b/odp-client/tests/error.rs new file mode 100644 index 000000000..509b1ba99 --- /dev/null +++ b/odp-client/tests/error.rs @@ -0,0 +1,29 @@ +use odp_client::OdpError; + +#[test] +fn odp_error_is_copy_eq_and_debug() { + let e = OdpError::Transport; + // Copy + Clone + let _ = e; + let _ = e; + // Eq + assert_eq!(OdpError::Transport, OdpError::Transport); + assert_ne!(OdpError::Transport, OdpError::Timeout); + // Debug (just confirm it doesn't panic) + let _ = format!("{e:?}"); +} + +#[test] +fn odp_error_unexpected_message_id_carries_expected_and_got() { + let e = OdpError::UnexpectedMessageId { + expected: 0x1234, + got: 0x5678, + }; + match e { + OdpError::UnexpectedMessageId { expected, got } => { + assert_eq!(expected, 0x1234); + assert_eq!(got, 0x5678); + } + _ => panic!("wrong variant"), + } +} diff --git a/odp-client/tests/loopback.rs b/odp-client/tests/loopback.rs new file mode 100644 index 000000000..ac3c79205 --- /dev/null +++ b/odp-client/tests/loopback.rs @@ -0,0 +1,49 @@ +use odp_client::{LoopbackTransport, OdpError, OdpTransport}; + +#[test] +fn loopback_echoes_what_you_send() { + let mut t = LoopbackTransport::new(); + t.send_message(&[9, 8, 7]).unwrap(); + let mut buf = [0u8; 8]; + let n = t.recv_message(&mut buf).unwrap(); + assert_eq!(&buf[..n], &[9, 8, 7]); +} + +#[test] +fn loopback_recv_with_no_pending_message_returns_transport_error() { + let mut t = LoopbackTransport::new(); + let mut buf = [0u8; 8]; + let err = t.recv_message(&mut buf).unwrap_err(); + assert_eq!(err, OdpError::Transport); +} + +#[test] +fn loopback_recv_with_too_small_buffer_returns_buffer_too_small() { + let mut t = LoopbackTransport::new(); + t.send_message(&[1, 2, 3, 4, 5]).unwrap(); + let mut buf = [0u8; 2]; + let err = t.recv_message(&mut buf).unwrap_err(); + assert_eq!(err, OdpError::BufferTooSmall); +} + +#[test] +fn loopback_send_overflow_returns_buffer_too_small() { + let mut t = LoopbackTransport::new(); + let huge = [0u8; 512]; // larger than the 256-byte internal buffer + let err = t.send_message(&huge).unwrap_err(); + assert_eq!(err, OdpError::BufferTooSmall); +} + +#[test] +fn loopback_send_then_recv_is_idempotent_across_messages() { + let mut t = LoopbackTransport::new(); + // Round 1 + t.send_message(&[1, 2]).unwrap(); + let mut buf = [0u8; 8]; + let n = t.recv_message(&mut buf).unwrap(); + assert_eq!(&buf[..n], &[1, 2]); + // Round 2 — should work after the previous recv drained the buffer + t.send_message(&[3, 4, 5]).unwrap(); + let n = t.recv_message(&mut buf).unwrap(); + assert_eq!(&buf[..n], &[3, 4, 5]); +} diff --git a/odp-client/tests/serial.rs b/odp-client/tests/serial.rs new file mode 100644 index 000000000..af6719bad --- /dev/null +++ b/odp-client/tests/serial.rs @@ -0,0 +1,73 @@ +use mctp_rs::{EC_EID, SP_EID}; +use odp_client::{OdpTransport, SerialTransport}; + +struct LoopbackUart { + buf: Vec, + cursor: usize, +} + +impl LoopbackUart { + fn new() -> Self { + Self { + buf: Vec::new(), + cursor: 0, + } + } +} + +impl embedded_io::ErrorType for LoopbackUart { + type Error = core::convert::Infallible; +} + +impl embedded_io::Read for LoopbackUart { + fn read(&mut self, out: &mut [u8]) -> Result { + let avail = self.buf.len() - self.cursor; + if avail == 0 { + return Ok(0); + } + let n = avail.min(out.len()); + out[..n].copy_from_slice(&self.buf[self.cursor..self.cursor + n]); + self.cursor += n; + Ok(n) + } +} + +impl embedded_io::Write for LoopbackUart { + fn write(&mut self, data: &[u8]) -> Result { + self.buf.extend_from_slice(data); + Ok(data.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[test] +fn serial_transport_round_trips_one_message() { + // Hand in ODP wire bytes (4-byte header + body); the transport + // frames them before writing to the UART and strips the framing + // on read, returning the original ODP bytes. + let uart = LoopbackUart::new(); + let mut t = SerialTransport::new(uart, SP_EID, EC_EID); + + // Arbitrary 6 bytes: first 4 are the OdpHeader byte pattern, last 2 + // are the body. The transport does not semantically validate the + // header — it just blits bytes — so a service-id (0x55) that is not a + // real OdpService variant works here. + let payload = [0x01u8, 0x55, 0x00, 0x00, 0xDE, 0xAD]; + t.send_message(&payload).unwrap(); + + let mut out = [0u8; 64]; + let n = t.recv_message(&mut out).unwrap(); + assert_eq!(&out[..n], &payload); +} + +#[test] +fn serial_transport_send_with_too_short_payload_errors() { + // Header is 4 bytes; a payload shorter than that has no valid + // header bytes to wrap and must be rejected. + let uart = LoopbackUart::new(); + let mut t = SerialTransport::new(uart, SP_EID, EC_EID); + let err = t.send_message(&[0x01, 0x02]).unwrap_err(); + assert_eq!(err, odp_client::OdpError::BufferTooSmall); +} diff --git a/odp-client/tests/transport.rs b/odp-client/tests/transport.rs new file mode 100644 index 000000000..281063dac --- /dev/null +++ b/odp-client/tests/transport.rs @@ -0,0 +1,44 @@ +use odp_client::{OdpError, OdpTransport}; + +struct DummyTransport(Option>); + +impl OdpTransport for DummyTransport { + fn send_message(&mut self, payload: &[u8]) -> Result<(), OdpError> { + self.0 = Some(payload.to_vec()); + Ok(()) + } + fn recv_message(&mut self, buf: &mut [u8]) -> Result { + let v = self.0.take().ok_or(OdpError::Transport)?; + if buf.len() < v.len() { + return Err(OdpError::BufferTooSmall); + } + buf[..v.len()].copy_from_slice(&v); + Ok(v.len()) + } +} + +#[test] +fn dummy_transport_satisfies_the_trait() { + let mut t = DummyTransport(None); + t.send_message(&[1, 2, 3]).unwrap(); + let mut buf = [0u8; 8]; + let n = t.recv_message(&mut buf).unwrap(); + assert_eq!(&buf[..n], &[1, 2, 3]); +} + +#[test] +fn dummy_transport_recv_with_no_pending_message_returns_transport_error() { + let mut t = DummyTransport(None); + let mut buf = [0u8; 8]; + let err = t.recv_message(&mut buf).unwrap_err(); + assert_eq!(err, OdpError::Transport); +} + +#[test] +fn dummy_transport_recv_with_too_small_buffer_returns_buffer_too_small() { + let mut t = DummyTransport(None); + t.send_message(&[1, 2, 3, 4, 5]).unwrap(); + let mut buf = [0u8; 2]; + let err = t.recv_message(&mut buf).unwrap_err(); + assert_eq!(err, OdpError::BufferTooSmall); +} diff --git a/thermal-service-relay/Cargo.toml b/thermal-service-relay/Cargo.toml index 43f7c1dbd..c1cd1559c 100644 --- a/thermal-service-relay/Cargo.toml +++ b/thermal-service-relay/Cargo.toml @@ -7,7 +7,7 @@ repository.workspace = true [dependencies] defmt = { workspace = true, optional = true } -embedded-services.workspace = true +odp-client.workspace = true thermal-service-interface.workspace = true num_enum.workspace = true uuid.workspace = true @@ -16,4 +16,4 @@ uuid.workspace = true workspace = true [features] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "odp-client/defmt"] diff --git a/thermal-service-relay/src/lib.rs b/thermal-service-relay/src/lib.rs index fceae864d..5cde311c1 100644 --- a/thermal-service-relay/src/lib.rs +++ b/thermal-service-relay/src/lib.rs @@ -191,12 +191,12 @@ impl ThermalServiceRelayHandler { } } -impl embedded_services::relay::mctp::RelayServiceHandlerTypes for ThermalServiceRelayHandler { +impl odp_client::server::RelayServiceHandlerTypes for ThermalServiceRelayHandler { type RequestType = ThermalRequest; type ResultType = ThermalResult; } -impl embedded_services::relay::mctp::RelayServiceHandler for ThermalServiceRelayHandler { +impl odp_client::server::RelayServiceHandler for ThermalServiceRelayHandler { async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { match request { ThermalRequest::ThermalGetTmpRequest { instance_id } => self.sensor_get_tmp(instance_id).await, diff --git a/thermal-service-relay/src/serialization.rs b/thermal-service-relay/src/serialization.rs index 6b1a7a63d..890ea952e 100644 --- a/thermal-service-relay/src/serialization.rs +++ b/thermal-service-relay/src/serialization.rs @@ -1,5 +1,5 @@ use crate::DeciKelvin; -use embedded_services::relay::{MessageSerializationError, SerializableMessage}; +use odp_client::{MessageSerializationError, SerializableMessage}; // Standard MPTF requests expected by the thermal subsystem #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] diff --git a/time-alarm-service-relay/Cargo.toml b/time-alarm-service-relay/Cargo.toml index e098b1904..3c716abfa 100644 --- a/time-alarm-service-relay/Cargo.toml +++ b/time-alarm-service-relay/Cargo.toml @@ -11,9 +11,9 @@ ignored = ["log"] [dependencies] defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } -embedded-services.workspace = true embedded-mcu-hal.workspace = true num_enum.workspace = true +odp-client.workspace = true time-alarm-service-interface.workspace = true [features] @@ -21,8 +21,9 @@ defmt = [ "dep:defmt", "embedded-mcu-hal/defmt", "time-alarm-service-interface/defmt", + "odp-client/defmt", ] -log = ["dep:log", "embedded-services/log"] +log = ["dep:log"] [lints] workspace = true diff --git a/time-alarm-service-relay/src/lib.rs b/time-alarm-service-relay/src/lib.rs index ed444a394..f8c3edb74 100644 --- a/time-alarm-service-relay/src/lib.rs +++ b/time-alarm-service-relay/src/lib.rs @@ -17,12 +17,12 @@ impl TimeAlarmServiceRelayHandler { } } -impl embedded_services::relay::mctp::RelayServiceHandlerTypes for TimeAlarmServiceRelayHandler { +impl odp_client::server::RelayServiceHandlerTypes for TimeAlarmServiceRelayHandler { type RequestType = AcpiTimeAlarmRequest; type ResultType = AcpiTimeAlarmResult; } -impl embedded_services::relay::mctp::RelayServiceHandler for TimeAlarmServiceRelayHandler { +impl odp_client::server::RelayServiceHandler for TimeAlarmServiceRelayHandler { async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { match request { AcpiTimeAlarmRequest::GetCapabilities => { diff --git a/time-alarm-service-relay/src/serialization.rs b/time-alarm-service-relay/src/serialization.rs index bc77f46e7..d43257e36 100644 --- a/time-alarm-service-relay/src/serialization.rs +++ b/time-alarm-service-relay/src/serialization.rs @@ -1,5 +1,5 @@ use core::array::TryFromSliceError; -use embedded_services::relay::{MessageSerializationError, SerializableMessage}; +use odp_client::{MessageSerializationError, SerializableMessage}; use time_alarm_service_interface::{ AcpiDaylightSavingsTimeStatus, AcpiTimerId, AcpiTimestamp, AlarmExpiredWakePolicy, AlarmTimerSeconds, TimeAlarmDeviceCapabilities, TimerStatus, diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml index beb373559..ede516f7e 100644 --- a/uart-service/Cargo.toml +++ b/uart-service/Cargo.toml @@ -19,6 +19,7 @@ defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } embassy-sync.workspace = true mctp-rs = { workspace = true } +odp-client.workspace = true embedded-io-async.workspace = true [features] @@ -28,6 +29,7 @@ defmt = [ "embedded-services/defmt", "embassy-sync/defmt", "mctp-rs/defmt", + "odp-client/defmt", ] log = ["dep:log", "embedded-services/log"] diff --git a/uart-service/src/lib.rs b/uart-service/src/lib.rs index e3ecffe6a..93a2a2225 100644 --- a/uart-service/src/lib.rs +++ b/uart-service/src/lib.rs @@ -15,10 +15,10 @@ use embassy_sync::channel::Channel; use embedded_io_async::Read as UartRead; use embedded_io_async::Write as UartWrite; use embedded_services::GlobalRawMutex; -use embedded_services::relay::mctp::{RelayHandler, RelayHeader, RelayResponse}; use embedded_services::trace; use mctp_rs::MctpMedium; use mctp_rs::smbus_espi::{SmbusEspiMedium, SmbusEspiReplyContext}; +use odp_client::server::{RelayHandler, RelayHeader, RelayResponse}; // Should be as large as the largest possible MCTP packet and its metadata. const BUF_SIZE: usize = 256; diff --git a/uart-service/src/task.rs b/uart-service/src/task.rs index 06a2b8c05..291bb6f4c 100644 --- a/uart-service/src/task.rs +++ b/uart-service/src/task.rs @@ -2,8 +2,8 @@ use crate::{Error, Service}; use embedded_io_async::Read as UartRead; use embedded_io_async::Write as UartWrite; use embedded_services::error; -use embedded_services::relay::mctp::RelayHandler; use mctp_rs::MctpMedium; +use odp_client::server::RelayHandler; pub async fn uart_service( uart_service: &Service,