diff --git a/battery-service-messages/src/lib.rs b/battery-service-messages/src/lib.rs index 3c856b9a..a219730e 100644 --- a/battery-service-messages/src/lib.rs +++ b/battery-service-messages/src/lib.rs @@ -685,6 +685,8 @@ pub enum AcpiBatteryError { UnspecifiedFailure = 2, } +pub type AcpiBatteryResult = Result; + impl SerializableMessage for AcpiBatteryError { fn serialize(self, _buffer: &mut [u8]) -> Result { match self { diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index 6531fcf6..00d9e5d0 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -30,7 +30,7 @@ pub trait SerializableMessage: Sized { fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; } -// Prevent other types from implementing SerializableResponse - they should instead use SerializableMessage on a Response type and an Error type +// 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 {} @@ -38,30 +38,30 @@ mod private { impl Sealed for Result {} } -/// Responses are of type Result where T and E both implement SerializableMessage -pub trait SerializableResponse: private::Sealed + Sized { - /// The type of the response when the operation being responsed to succeeded +/// 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 response when the operation being responsed to failed + /// The type of the result when the operation being responded to failed type ErrorType: SerializableMessage; - /// Returns true if the response represents a successful operation, false otherwise + /// 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 response. + /// 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 response into the provided buffer. + /// 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 response from the provided buffer. + /// Attempts to deserialize the result from the provided buffer. fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; } -impl SerializableResponse for Result +impl SerializableResult for Result where T: SerializableMessage, E: SerializableMessage, @@ -95,3 +95,328 @@ where } } } + +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, + } + + /// This macro generates the necessary types and impls to support relaying ODP messages to and from the comms system. + /// It takes as input a list of (service name, service ID, comms endpoint ID, request type, result type) tuples and + /// emits the following types: + /// - enum OdpService - a mapping from service name to MCTP endpoint ID + /// - enum HostRequest - an enum containing all the possible request types that were passed into the macro + /// - enum HostResult - an enum containing all the possible result types that were passed into the macro + /// - struct OdpHeader - a type representing the ODP MCTP header. + /// - fn send_to_comms(&comms::Message, impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, + /// a function that takes a received message and sends it to the appropriate service based on its type using the provided send function. + /// + /// Because this macro emits a number of types, it is recommended to invoke it inside a dedicated module. + /// + /// Arguments: + /// $service_name (identifier) - the name that this service will have in the emitted OdpService enum + /// $service_id (u8) - the service ID that will be used in the ODP MCTP header for messages related to this service. + /// $endpoint_id (comms::EndpointID value) - the comms endpoint ID that this service corresponds to. + /// NOTE: due to technical limitations in Rust macros, this must be surrounded with parentheses. + /// $request_type (type implementing SerializableMessage) - the type that represents requests for this service + /// $result_type (type implementing SerializableResult) - the type that represents results for this service + /// + /// Example usage: + /// + /// impl_odp_relay_types!( + /// Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + /// Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + /// Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + /// ); + /// ^ ^ + /// note the above parentheses - these are required + #[macro_export] + macro_rules! impl_odp_mctp_relay_types { + ($($service_name:ident, + $service_id:expr, + ($($endpoint_id:tt)+), + $request_type:ty, + $result_type:ty; + )+) => { + + use bitfield::bitfield; + use core::convert::Infallible; + use mctp_rs::smbus_espi::SmbusEspiMedium; + use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; + + #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(u8)] + pub(crate) enum OdpService { + $( + $service_name = $service_id, + )+ + } + + impl TryFrom for OdpService { + type Error = embedded_services::relay::mctp::MctpError; + fn try_from(endpoint_id_value: comms::EndpointID) -> Result { + match endpoint_id_value { + $( + $($endpoint_id)+ => Ok(OdpService::$service_name), + )+ + _ => Err(embedded_services::relay::mctp::MctpError::UnknownEndpointId), + } + } + } + + impl OdpService { + pub fn get_endpoint_id(&self) -> comms::EndpointID { + match self { + $( + OdpService::$service_name => $($endpoint_id)+, + )+ + } + } + } + + pub(crate) enum HostRequest { + $( + $service_name($request_type), + )+ + } + + impl HostRequest { + pub(crate) async fn send_to_endpoint(&self, source_endpoint: &comms::Endpoint, destination_endpoint_id: comms::EndpointID) -> Result<(), Infallible> { + match self { + $( + HostRequest::$service_name(request) => source_endpoint.send(destination_endpoint_id, request).await, + )+ + } + } + } + + 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) => request + .serialize(buffer) + .map_err(|_| mctp_rs::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( + <$request_type>::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, + ), + )+ + }) + } + } + + #[derive(Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) enum HostResult { + $( + $service_name($result_type), + )+ + } + + impl HostResult { + pub(crate) fn discriminant(&self) -> u16 { + match self { + $( + HostResult::$service_name(result) => result.discriminant(), + )+ + } + } + + pub(crate) fn is_ok(&self) -> bool { + match self { + $( + HostResult::$service_name(result) => result.is_ok(), + )+ + } + } + } + + 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(|_| mctp_rs::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(<$result_type as SerializableResult>::deserialize(is_error, header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) + } + } + }, + )+ + } + } + } + + bitfield! { + /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct OdpHeaderWireFormat(u32); + impl Debug; + impl new; + /// If true, represents a request; otherwise, represents a result + is_request, set_is_request: 25; + + // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + is_datagram, set_is_datagram: 24; + + /// 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)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) enum OdpMessageType { + Request, + Result { is_error: bool }, + } + + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) struct OdpHeader { + pub message_type: OdpMessageType, + pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + 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.is_datagram, + 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, + is_datagram: src.is_datagram(), + 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"))?, + )) + } + } + + /// Attempt to route the provided message to the service that is registered to handle it based on its type. + pub(crate) fn send_to_comms( + message: &comms::Message, + send_fn: impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, + ) -> Result<(), comms::MailboxDelegateError> { + $( + if let Some(msg) = message.data.get::<$result_type>() { + send_fn( + $($endpoint_id)+, + HostResult::$service_name(*msg), + )?; + Ok(()) + } else + )+ + { + Err(comms::MailboxDelegateError::MessageNotFound) + } + } + }; +} + + pub use impl_odp_mctp_relay_types; +} diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 82f22ddb..55dcd55e 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -1,6 +1,6 @@ use core::slice; -use crate::mctp::{HostRequest, HostResponse, OdpHeader, OdpMessageType, OdpService}; +use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService}; use core::borrow::BorrowMut; use embassy_imxrt::espi; use embassy_sync::channel::Channel; @@ -25,9 +25,9 @@ embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; ASSEMBLY_BUF_SI #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct HostResponseMessage { +pub(crate) struct HostResultMessage { pub source_endpoint: EndpointID, - pub message: HostResponse, + pub message: HostResult, } #[derive(Debug, Clone, Copy)] @@ -40,7 +40,7 @@ pub enum Error { pub struct Service<'a> { endpoint: comms::Endpoint, _ec_memory: Mutex, - host_tx_queue: Channel, + host_tx_queue: Channel, assembly_buf_owned_ref: OwnedRef<'a, u8>, } @@ -54,7 +54,7 @@ impl Service<'_> { } } - pub(crate) async fn wait_for_response(&self) -> HostResponseMessage { + pub(crate) async fn wait_for_response(&self) -> HostResultMessage { self.host_tx_queue.receive().await } @@ -69,14 +69,13 @@ impl Service<'_> { async fn serialize_packet_from_subsystem( &self, espi: &mut espi::Espi<'static>, - response: &HostResponseMessage, + result: &HostResultMessage, ) -> Result<(), Error> { let mut assembly_buf_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; let pkt_ctx_buf = assembly_buf_access.borrow_mut(); let mut mctp_ctx = mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, pkt_ctx_buf); - let source_service: OdpService = - OdpService::try_from(response.source_endpoint).map_err(|_| Error::Serialize)?; + let source_service: OdpService = OdpService::try_from(result.source_endpoint).map_err(|_| Error::Serialize)?; let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { source_endpoint_id: mctp_rs::EndpointId::Id(0x80), @@ -93,16 +92,16 @@ impl Service<'_> { }; let header = OdpHeader { - message_type: OdpMessageType::Response { - is_error: !response.message.is_ok(), + message_type: OdpMessageType::Result { + is_error: !result.message.is_ok(), }, is_datagram: false, service: source_service, - message_id: response.message.discriminant(), + message_id: result.message.discriminant(), }; let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, response.message.clone())) + .serialize_packet(reply_context, (header, result.message.clone())) .map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); Error::Serialize @@ -142,9 +141,9 @@ impl Service<'_> { async fn send_mctp_error_response(&self, endpoint: EndpointID, espi: &mut espi::Espi<'static>) { // TODO we may want to add more detail in future, but that will require more integration with the debug service - let error_msg = HostResponseMessage { + let error_msg = HostResultMessage { source_endpoint: endpoint, - message: HostResponse::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), + message: HostResult::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), }; self.serialize_packet_from_subsystem(espi, &error_msg) .await @@ -153,7 +152,7 @@ impl Service<'_> { }); } - pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResponseMessage) { + pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResultMessage) { match self.serialize_packet_from_subsystem(espi, &response).await { Err(e) => { error!("Packet serialize error {:?}", e); @@ -173,11 +172,11 @@ impl Service<'_> { fn queue_response_to_host( &self, source_endpoint: EndpointID, - message: HostResponse, + message: HostResult, ) -> Result<(), comms::MailboxDelegateError> { debug!("Espi service: recvd response"); self.host_tx_queue - .try_send(HostResponseMessage { + .try_send(HostResultMessage { source_endpoint, message, }) @@ -189,7 +188,7 @@ impl Service<'_> { impl comms::MailboxDelegate for Service<'_> { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - crate::mctp::try_route_request_to_comms(message, |source_endpoint, message| { + crate::mctp::send_to_comms(message, |source_endpoint, message| { self.queue_response_to_host(source_endpoint, message) }) } diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs index 9820f58d..7ef3968e 100644 --- a/espi-service/src/mctp.rs +++ b/espi-service/src/mctp.rs @@ -1,354 +1,16 @@ -use bitfield::bitfield; -use core::convert::Infallible; -use embedded_services::{ - comms, - relay::{SerializableMessage, SerializableResponse}, -}; -use mctp_rs::smbus_espi::SmbusEspiMedium; -use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; - -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub(crate) enum OdpService { - Battery = 0x08, - Thermal = 0x09, - Debug = 0x0A, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum MctpError { - // The endpoint ID does not correspond to a known service - UnknownEndpointId, -} - -impl TryFrom for OdpService { - type Error = MctpError; - fn try_from(endpoint_id: comms::EndpointID) -> Result { - match endpoint_id { - comms::EndpointID::Internal(comms::Internal::Battery) => Ok(OdpService::Battery), - comms::EndpointID::Internal(comms::Internal::Thermal) => Ok(OdpService::Thermal), - comms::EndpointID::Internal(comms::Internal::Debug) => Ok(OdpService::Debug), - _ => Err(MctpError::UnknownEndpointId), - } - } -} - -impl OdpService { - pub fn get_endpoint_id(&self) -> comms::EndpointID { - match self { - OdpService::Battery => comms::EndpointID::Internal(comms::Internal::Battery), - OdpService::Thermal => comms::EndpointID::Internal(comms::Internal::Thermal), - OdpService::Debug => comms::EndpointID::Internal(comms::Internal::Debug), - } - } -} - -// TODO We'd ideally like these types to be passed in as a generic or something when the eSPI service is instantiated -// so the eSPI service can be extended to handle 3rd party message types without needing to fork the eSPI service, -// but that's dependant on us migrating to have storage for the eSPI service be allocated by the caller of init() -// rather than statically allocated inside this module, so for now we accept this hardcoded list of supported message -// types, and we can maybe convert this to a macro that accepts a list of types at some point. -// New services should follow the pattern of defining their own message crates using the request/response -// traits, and the only additions here should be the mapping between message types and endpoint IDs. -// -// Additionally, we probably want some sort of macro that can generate most or all of this from a table mapping service IDs -// to (request type, response type, comms endpoint) tuples for maintainability. -// -pub(crate) enum HostRequest { - Battery(battery_service_messages::AcpiBatteryRequest), - Debug(debug_service_messages::DebugRequest), - Thermal(thermal_service_messages::ThermalRequest), -} - -impl MctpMessageTrait<'_> for HostRequest { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - HostRequest::Battery(request) => request - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize battery request")), - - HostRequest::Debug(request) => request - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize debug request")), - - HostRequest::Thermal(request) => request - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize thermal request")), - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - OdpService::Battery => Self::Battery( - battery_service_messages::AcpiBatteryRequest::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse battery request"))?, - ), - OdpService::Debug => Self::Debug( - debug_service_messages::DebugRequest::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse debug request"))?, - ), - OdpService::Thermal => Self::Thermal( - thermal_service_messages::ThermalRequest::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse thermal request"))?, - ), - }) - } -} - -impl HostRequest { - pub(crate) async fn send_to_endpoint( - &self, - source_endpoint: &comms::Endpoint, - destination_endpoint_id: comms::EndpointID, - ) -> Result<(), Infallible> { - match self { - HostRequest::Battery(request) => source_endpoint.send(destination_endpoint_id, request).await, - HostRequest::Debug(request) => source_endpoint.send(destination_endpoint_id, request).await, - HostRequest::Thermal(request) => source_endpoint.send(destination_endpoint_id, request).await, - } - } -} - -#[derive(Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum HostResponse { - Battery(Result), - Debug(Result), - Thermal(Result), -} - -impl MctpMessageTrait<'_> for HostResponse { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - HostResponse::Battery(response) => response - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize battery response")), - - HostResponse::Debug(response) => response - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize debug response")), - - HostResponse::Thermal(response) => response - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize thermal response")), - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - OdpService::Battery => { - if let Ok(success) = - battery_service_messages::AcpiBatteryResponse::deserialize(header.message_id, buffer) - { - Self::Battery(Ok(success)) - } else { - let error = battery_service_messages::AcpiBatteryError::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse battery error response"))?; - Self::Battery(Err(error)) - } - } - OdpService::Debug => { - if let Ok(success) = debug_service_messages::DebugResponse::deserialize(header.message_id, buffer) { - Self::Debug(Ok(success)) - } else { - let error = debug_service_messages::DebugError::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse debug error response"))?; - Self::Debug(Err(error)) - } - } - OdpService::Thermal => { - if let Ok(success) = thermal_service_messages::ThermalResponse::deserialize(header.message_id, buffer) { - Self::Thermal(Ok(success)) - } else { - let error = thermal_service_messages::ThermalError::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse thermal error response"))?; - Self::Thermal(Err(error)) - } - } - }) - } -} - -impl HostResponse { - pub(crate) fn discriminant(&self) -> u16 { - match self { - HostResponse::Battery(response) => response.discriminant(), - HostResponse::Debug(response) => response.discriminant(), - HostResponse::Thermal(response) => response.discriminant(), - } - } - - pub(crate) fn is_ok(&self) -> bool { - match self { - HostResponse::Battery(response) => response.is_ok(), - HostResponse::Debug(response) => response.is_ok(), - HostResponse::Thermal(response) => response.is_ok(), - } - } -} - -/// Attempt to route the provided message to the service that is registered to handle it based on its type. -pub(crate) fn try_route_request_to_comms( - message: &comms::Message, - send_fn: impl FnOnce(comms::EndpointID, HostResponse) -> Result<(), comms::MailboxDelegateError>, -) -> Result<(), comms::MailboxDelegateError> { - // TODO we're going to have a bunch of types that all implement the SerializableResponse trait; in C++ I'd reach for dynamic_cast or a pointer-to-interface, - // but not sure how to do that with Rust's Any - it seems like it requires a concrete type rather than a trait to cast. Is there a cleaner way to - // say "if the message implements the SerializableResponse trait" so we don't have to spell out all the types here? - // - if let Some(msg) = message - .data - .get::>() - { - send_fn( - comms::EndpointID::Internal(comms::Internal::Battery), - HostResponse::Battery(*msg), - )?; - Ok(()) - } else if let Some(msg) = message - .data - .get::>() - { - send_fn( - comms::EndpointID::Internal(comms::Internal::Debug), - HostResponse::Debug(*msg), - )?; - Ok(()) - } else if let Some(msg) = message - .data - .get::>() - { - send_fn( - comms::EndpointID::Internal(comms::Internal::Thermal), - HostResponse::Thermal(*msg), - )?; - Ok(()) - } else { - Err(comms::MailboxDelegateError::MessageNotFound) - } -} - -bitfield! { - /// Raw bitfield of possible port status events - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct OdpHeaderWireFormat(u32); - impl Debug; - impl new; - /// If true, represents a request; otherwise, represents a response - is_request, set_is_request: 25; - - // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - is_datagram, set_is_datagram: 24; - - /// 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 responses, indicates if the response 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)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum OdpMessageType { - Request, - Response { is_error: bool }, -} - -#[derive(Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct OdpHeader { - pub message_type: OdpMessageType, - pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - 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.is_datagram, - src.service.into(), - match src.message_type { - OdpMessageType::Request => false, // unused on requests - OdpMessageType::Response { 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::Response { - is_error: src.is_error(), - } - }; - - Ok(OdpHeader { - message_type, - is_datagram: src.is_datagram(), - 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"))?, - )) - } -} +use embedded_services::{ + comms, + relay::{SerializableMessage, SerializableResult, mctp::impl_odp_mctp_relay_types}, +}; + +// TODO We'd ideally like these types to be passed in as a generic or something when the eSPI service is instantiated +// so the eSPI service can be extended to handle 3rd party message types without needing to fork the eSPI service, +// but that's dependant on us migrating to have storage for the eSPI service be allocated by the caller of init() +// rather than statically allocated inside this module, so for now we accept this hardcoded list of supported message +// types. + +impl_odp_mctp_relay_types!( + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; +);