diff --git a/Cargo.lock b/Cargo.lock index b2be6058..a48728f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ + "battery-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -172,6 +173,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + [[package]] name = "bbq2" version = "0.4.2" @@ -202,14 +213,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -450,6 +453,7 @@ version = "0.1.0" dependencies = [ "bbq2", "critical-section", + "debug-service-messages", "defmt 0.3.100", "embassy-sync", "embassy-time", @@ -458,6 +462,15 @@ dependencies = [ "rtt-target", ] +[[package]] +name = "debug-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -910,9 +923,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags 2.9.4", "num-traits", "num_enum", @@ -924,8 +937,11 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ + "battery-service-messages", + "bitfield 0.17.0", "cortex-m", "cortex-m-rt", + "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-imxrt", @@ -934,6 +950,8 @@ dependencies = [ "embedded-services", "log", "mctp-rs", + "num_enum", + "thermal-service-messages", ] [[package]] @@ -1253,9 +1271,9 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1986,6 +2004,17 @@ dependencies = [ "heapless", "log", "mctp-rs", + "thermal-service-messages", + "uuid", +] + +[[package]] +name = "thermal-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index e5a02b4b..3f7b0142 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,9 @@ resolver = "2" members = [ "battery-service", + "battery-service-messages", "thermal-service", + "thermal-service-messages", "cfu-service", "embedded-service", "espi-service", @@ -15,6 +17,7 @@ members = [ "power-policy-service", "type-c-service", "debug-service", + "debug-service-messages", "keyboard-service", ] exclude = ["examples/*"] @@ -45,6 +48,7 @@ unwrap_used = "deny" [workspace.dependencies] aligned = "0.4" anyhow = "1.0" +battery-service-messages = { path = "./battery-service-messages" } bitfield = "0.17.0" bitflags = "2.8.0" bitvec = { version = "1.0.1", default-features = false } @@ -56,6 +60,7 @@ cortex-m-rt = "0.7.5" critical-section = "1.1" defmt = "0.3" document-features = "0.2.7" +debug-service-messages = { path = "./debug-service-messages" } embassy-futures = "0.1.2" embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" } embassy-sync = "0.7.2" @@ -68,6 +73,7 @@ embedded-hal-async = "1.0" embedded-hal-nb = "1.0" embedded-io = "0.6.1" embedded-io-async = "0.6.1" +embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } embedded-services = { path = "./embedded-service" } embedded-storage = "0.3" embedded-storage-async = "0.4.1" @@ -86,6 +92,7 @@ rstest = { version = "0.26.1", default-features = false } serde = { version = "1.0.*", default-features = false } static_cell = "2.1.0" toml = { version = "0.8", default-features = false } +thermal-service-messages = { path = "./thermal-service-messages" } syn = "2.0" tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" } tokio = { version = "1.42.0" } diff --git a/battery-service-messages/Cargo.toml b/battery-service-messages/Cargo.toml new file mode 100644 index 00000000..86d56e08 --- /dev/null +++ b/battery-service-messages/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "battery-service-messages" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embedded-batteries-async.workspace = true +embedded-services.workspace = true +num_enum.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt", "embedded-batteries-async/defmt"] diff --git a/battery-service-messages/src/lib.rs b/battery-service-messages/src/lib.rs new file mode 100644 index 00000000..3c856b9a --- /dev/null +++ b/battery-service-messages/src/lib.rs @@ -0,0 +1,719 @@ +#![no_std] + +use embedded_batteries_async::acpi::{ + BCT_RETURN_SIZE_BYTES, BMD_RETURN_SIZE_BYTES, BPC_RETURN_SIZE_BYTES, BPS_RETURN_SIZE_BYTES, BST_RETURN_SIZE_BYTES, + BTM_RETURN_SIZE_BYTES, PSR_RETURN_SIZE_BYTES, STA_RETURN_SIZE_BYTES, +}; +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// Standard Battery Service Model Number String Size +pub const STD_BIX_MODEL_SIZE: usize = 8; +/// Standard Battery Service Serial Number String Size +pub const STD_BIX_SERIAL_SIZE: usize = 8; +/// Standard Battery Service Battery Type String Size +pub const STD_BIX_BATTERY_SIZE: usize = 8; +/// Standard Battery Service OEM Info String Size +pub const STD_BIX_OEM_SIZE: usize = 8; +/// Standard Power Policy Service Model Number String Size +pub const STD_PIF_MODEL_SIZE: usize = 8; +/// Standard Power Policy Serial Number String Size +pub const STD_PIF_SERIAL_SIZE: usize = 8; +/// Standard Power Policy Service OEM Info String Size +pub const STD_PIF_OEM_SIZE: usize = 8; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// ACPI Battery Methods +enum BatteryCmd { + /// Battery Information eXtended + GetBix = 1, + /// Battery Status + GetBst = 2, + /// Power Source + GetPsr = 3, + /// Power source InFormation + GetPif = 4, + /// Battery Power State + GetBps = 5, + /// Battery Trip Point + SetBtp = 6, + /// Battery Power Threshold + SetBpt = 7, + /// Battery Power Characteristics + GetBpc = 8, + /// Battery Maintenance Control + SetBmc = 9, + /// Battery Maintenance Data + GetBmd = 10, + /// Battery Charge Time + GetBct = 11, + /// Battery Time + GetBtm = 12, + /// Battery Measurement Sampling Time + SetBms = 13, + /// Battery Measurement Averaging Interval + SetBma = 14, + /// Device Status + GetSta = 15, +} + +impl From<&AcpiBatteryRequest> for BatteryCmd { + fn from(request: &AcpiBatteryRequest) -> Self { + match request { + AcpiBatteryRequest::BatteryGetBixRequest { .. } => BatteryCmd::GetBix, + AcpiBatteryRequest::BatteryGetBstRequest { .. } => BatteryCmd::GetBst, + AcpiBatteryRequest::BatteryGetPsrRequest { .. } => BatteryCmd::GetPsr, + AcpiBatteryRequest::BatteryGetPifRequest { .. } => BatteryCmd::GetPif, + AcpiBatteryRequest::BatteryGetBpsRequest { .. } => BatteryCmd::GetBps, + AcpiBatteryRequest::BatterySetBtpRequest { .. } => BatteryCmd::SetBtp, + AcpiBatteryRequest::BatterySetBptRequest { .. } => BatteryCmd::SetBpt, + AcpiBatteryRequest::BatteryGetBpcRequest { .. } => BatteryCmd::GetBpc, + AcpiBatteryRequest::BatterySetBmcRequest { .. } => BatteryCmd::SetBmc, + AcpiBatteryRequest::BatteryGetBmdRequest { .. } => BatteryCmd::GetBmd, + AcpiBatteryRequest::BatteryGetBctRequest { .. } => BatteryCmd::GetBct, + AcpiBatteryRequest::BatteryGetBtmRequest { .. } => BatteryCmd::GetBtm, + AcpiBatteryRequest::BatterySetBmsRequest { .. } => BatteryCmd::SetBms, + AcpiBatteryRequest::BatterySetBmaRequest { .. } => BatteryCmd::SetBma, + AcpiBatteryRequest::BatteryGetStaRequest { .. } => BatteryCmd::GetSta, + } + } +} + +impl From<&AcpiBatteryResponse> for BatteryCmd { + fn from(response: &AcpiBatteryResponse) -> Self { + match response { + AcpiBatteryResponse::BatteryGetBixResponse { .. } => BatteryCmd::GetBix, + AcpiBatteryResponse::BatteryGetBstResponse { .. } => BatteryCmd::GetBst, + AcpiBatteryResponse::BatteryGetPsrResponse { .. } => BatteryCmd::GetPsr, + AcpiBatteryResponse::BatteryGetPifResponse { .. } => BatteryCmd::GetPif, + AcpiBatteryResponse::BatteryGetBpsResponse { .. } => BatteryCmd::GetBps, + AcpiBatteryResponse::BatterySetBtpResponse { .. } => BatteryCmd::SetBtp, + AcpiBatteryResponse::BatterySetBptResponse { .. } => BatteryCmd::SetBpt, + AcpiBatteryResponse::BatteryGetBpcResponse { .. } => BatteryCmd::GetBpc, + AcpiBatteryResponse::BatterySetBmcResponse { .. } => BatteryCmd::SetBmc, + AcpiBatteryResponse::BatteryGetBmdResponse { .. } => BatteryCmd::GetBmd, + AcpiBatteryResponse::BatteryGetBctResponse { .. } => BatteryCmd::GetBct, + AcpiBatteryResponse::BatteryGetBtmResponse { .. } => BatteryCmd::GetBtm, + AcpiBatteryResponse::BatterySetBmsResponse { .. } => BatteryCmd::SetBms, + AcpiBatteryResponse::BatterySetBmaResponse { .. } => BatteryCmd::SetBma, + AcpiBatteryResponse::BatteryGetStaResponse { .. } => BatteryCmd::GetSta, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BixFixedStrings { + /// Revision of the BIX structure. Current revision is 1. + pub revision: u32, + /// Unit used for capacity and rate values. + pub power_unit: embedded_batteries_async::acpi::PowerUnit, + /// Design capacity of the battery (in mWh or mAh). + pub design_capacity: u32, + /// Last full charge capacity (in mWh or mAh). + pub last_full_charge_capacity: u32, + /// Battery technology type. + pub battery_technology: embedded_batteries_async::acpi::BatteryTechnology, + /// Design voltage (in mV). + pub design_voltage: u32, + /// Warning capacity threshold (in mWh or mAh). + pub design_cap_of_warning: u32, + /// Low capacity threshold (in mWh or mAh). + pub design_cap_of_low: u32, + /// Number of charge/discharge cycles. + pub cycle_count: u32, + /// Measurement accuracy in thousandths of a percent (e.g., 80000 = 80.000%). + pub measurement_accuracy: u32, + /// Maximum supported sampling time (in ms). + pub max_sampling_time: u32, + /// Minimum supported sampling time (in ms). + pub min_sampling_time: u32, + /// Maximum supported averaging interval (in ms). + pub max_averaging_interval: u32, + /// Minimum supported averaging interval (in ms). + pub min_averaging_interval: u32, + /// Capacity granularity between low and warning (in mWh or mAh). + pub battery_capacity_granularity_1: u32, + /// Capacity granularity between warning and full (in mWh or mAh). + pub battery_capacity_granularity_2: u32, + /// OEM-specific model number (ASCIIZ). + pub model_number: [u8; STD_BIX_MODEL_SIZE], + /// OEM-specific serial number (ASCIIZ). + pub serial_number: [u8; STD_BIX_SERIAL_SIZE], + /// OEM-specific battery type (ASCIIZ). + pub battery_type: [u8; STD_BIX_BATTERY_SIZE], + /// OEM-specific information (ASCIIZ). + pub oem_info: [u8; STD_BIX_OEM_SIZE], + /// Battery swapping capability. + pub battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability, +} + +// TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? +impl BixFixedStrings { + pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), MessageSerializationError> { + const MODEL_NUM_START_IDX: usize = 64; + let model_num_end_idx: usize = MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; + let serial_num_start_idx = model_num_end_idx; + let serial_num_end_idx = serial_num_start_idx + STD_BIX_SERIAL_SIZE; + let battery_type_start_idx = serial_num_end_idx; + let battery_type_end_idx = battery_type_start_idx + STD_BIX_BATTERY_SIZE; + let oem_info_start_idx = battery_type_end_idx; + let oem_info_end_idx = oem_info_start_idx + STD_BIX_OEM_SIZE; + + if dst_slice.len() < oem_info_end_idx { + return Err(MessageSerializationError::BufferTooSmall); + } + + dst_slice + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.revision)); + dst_slice + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.power_unit.into())); + dst_slice + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_capacity)); + dst_slice + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.last_full_charge_capacity)); + dst_slice + .get_mut(16..20) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_technology.into())); + dst_slice + .get_mut(20..24) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_voltage)); + dst_slice + .get_mut(24..28) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_cap_of_warning)); + dst_slice + .get_mut(28..32) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_cap_of_low)); + dst_slice + .get_mut(32..36) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.cycle_count)); + dst_slice + .get_mut(36..40) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.measurement_accuracy)); + dst_slice + .get_mut(40..44) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_sampling_time)); + dst_slice + .get_mut(44..48) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.min_sampling_time)); + dst_slice + .get_mut(48..52) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_averaging_interval)); + dst_slice + .get_mut(52..56) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.min_averaging_interval)); + dst_slice + .get_mut(56..60) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_1)); + dst_slice + .get_mut(60..64) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_2)); + dst_slice + .get_mut(MODEL_NUM_START_IDX..model_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.model_number); + dst_slice + .get_mut(serial_num_start_idx..serial_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.serial_number); + dst_slice + .get_mut(battery_type_start_idx..battery_type_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.battery_type); + dst_slice + .get_mut(oem_info_start_idx..oem_info_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.oem_info); + dst_slice + .get_mut(oem_info_end_idx..oem_info_end_idx + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_swapping_capability.into())); + Ok(()) + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PifFixedStrings { + /// Bitfield describing the state and characteristics of the power source. + pub power_source_state: embedded_batteries_async::acpi::PowerSourceState, + /// Maximum rated output power in milliwatts (mW). + /// + /// 0xFFFFFFFF indicates the value is unavailable. + pub max_output_power: u32, + /// Maximum rated input power in milliwatts (mW). + /// + /// 0xFFFFFFFF indicates the value is unavailable. + pub max_input_power: u32, + /// OEM-specific model number (ASCIIZ). Empty string if not supported. + pub model_number: [u8; STD_BIX_MODEL_SIZE], + /// OEM-specific serial number (ASCIIZ). Empty string if not supported. + pub serial_number: [u8; STD_BIX_SERIAL_SIZE], + /// OEM-specific information (ASCIIZ). Empty string if not supported. + pub oem_info: [u8; STD_BIX_OEM_SIZE], +} + +impl PifFixedStrings { + pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), MessageSerializationError> { + const MODEL_NUM_START_IDX: usize = 12; + let model_num_end_idx: usize = MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; + let serial_num_start_idx = model_num_end_idx; + let serial_num_end_idx = serial_num_start_idx + STD_BIX_SERIAL_SIZE; + let oem_info_start_idx = serial_num_end_idx; + let oem_info_end_idx = oem_info_start_idx + STD_BIX_OEM_SIZE; + + if dst_slice.len() < oem_info_end_idx { + return Err(MessageSerializationError::BufferTooSmall); + } + + dst_slice + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.power_source_state.bits())); + dst_slice + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_output_power)); + dst_slice + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_input_power)); + dst_slice + .get_mut(MODEL_NUM_START_IDX..model_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.model_number); + dst_slice + .get_mut(serial_num_start_idx..serial_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.serial_number); + dst_slice + .get_mut(oem_info_start_idx..oem_info_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.oem_info); + Ok(()) + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiBatteryRequest { + BatteryGetBixRequest { + battery_id: u8, + }, + BatteryGetBstRequest { + battery_id: u8, + }, + BatteryGetPsrRequest { + battery_id: u8, + }, + BatteryGetPifRequest { + battery_id: u8, + }, + BatteryGetBpsRequest { + battery_id: u8, + }, + BatterySetBtpRequest { + battery_id: u8, + btp: embedded_batteries_async::acpi::Btp, + }, + BatterySetBptRequest { + battery_id: u8, + bpt: embedded_batteries_async::acpi::Bpt, + }, + BatteryGetBpcRequest { + battery_id: u8, + }, + BatterySetBmcRequest { + battery_id: u8, + bmc: embedded_batteries_async::acpi::Bmc, + }, + BatteryGetBmdRequest { + battery_id: u8, + }, + BatteryGetBctRequest { + battery_id: u8, + bct: embedded_batteries_async::acpi::Bct, + }, + BatteryGetBtmRequest { + battery_id: u8, + btm: embedded_batteries_async::acpi::Btm, + }, + BatterySetBmsRequest { + battery_id: u8, + bms: embedded_batteries_async::acpi::Bms, + }, + BatterySetBmaRequest { + battery_id: u8, + bma: embedded_batteries_async::acpi::Bma, + }, + BatteryGetStaRequest { + battery_id: u8, + }, +} + +impl SerializableMessage for AcpiBatteryRequest { + fn serialize(self, _buffer: &mut [u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match BatteryCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + BatteryCmd::GetBix => Self::BatteryGetBixRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBst => Self::BatteryGetBstRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetPsr => Self::BatteryGetPsrRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetPif => Self::BatteryGetPifRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBps => Self::BatteryGetBpsRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::SetBtp => Self::BatterySetBtpRequest { + battery_id: safe_get_u8(buffer, 0)?, + btp: embedded_batteries_async::acpi::Btp { + trip_point: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBpt => Self::BatterySetBptRequest { + battery_id: safe_get_u8(buffer, 0)?, + bpt: embedded_batteries_async::acpi::Bpt { + revision: safe_get_dword(buffer, 1)?, + threshold_id: match safe_get_dword(buffer, 5)? { + 0 => embedded_batteries_async::acpi::ThresholdId::ClearAll, + 1 => embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower, + 2 => embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower, + _ => { + return Err(MessageSerializationError::InvalidPayload("Unsupported threshold id")); + } + }, + threshold_value: safe_get_dword(buffer, 9)?, + }, + }, + BatteryCmd::GetBpc => Self::BatteryGetBpcRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::SetBmc => Self::BatterySetBmcRequest { + battery_id: safe_get_u8(buffer, 0)?, + bmc: embedded_batteries_async::acpi::Bmc { + maintenance_control_flags: embedded_batteries_async::acpi::BmcControlFlags::from_bits_retain( + safe_get_dword(buffer, 1)?, + ), + }, + }, + BatteryCmd::GetBmd => Self::BatteryGetBmdRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBct => Self::BatteryGetBctRequest { + battery_id: safe_get_u8(buffer, 0)?, + bct: embedded_batteries_async::acpi::Bct { + charge_level_percent: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::GetBtm => Self::BatteryGetBtmRequest { + battery_id: safe_get_u8(buffer, 0)?, + btm: embedded_batteries_async::acpi::Btm { + discharge_rate: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBms => Self::BatterySetBmsRequest { + battery_id: safe_get_u8(buffer, 0)?, + bms: embedded_batteries_async::acpi::Bms { + sampling_time_ms: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBma => Self::BatterySetBmaRequest { + battery_id: safe_get_u8(buffer, 0)?, + bma: embedded_batteries_async::acpi::Bma { + averaging_interval_ms: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::GetSta => Self::BatteryGetStaRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + }, + ) + } + + fn discriminant(&self) -> u16 { + BatteryCmd::from(self).into() + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiBatteryResponse { + BatteryGetBixResponse { + bix: BixFixedStrings, + }, + BatteryGetBstResponse { + bst: embedded_batteries_async::acpi::BstReturn, + }, + BatteryGetPsrResponse { + psr: embedded_batteries_async::acpi::PsrReturn, + }, + BatteryGetPifResponse { + pif: PifFixedStrings, + }, + BatteryGetBpsResponse { + bps: embedded_batteries_async::acpi::Bps, + }, + BatterySetBtpResponse {}, + BatterySetBptResponse {}, + BatteryGetBpcResponse { + bpc: embedded_batteries_async::acpi::Bpc, + }, + BatterySetBmcResponse {}, + BatteryGetBmdResponse { + bmd: embedded_batteries_async::acpi::Bmd, + }, + BatteryGetBctResponse { + bct_response: embedded_batteries_async::acpi::BctReturnResult, + }, + BatteryGetBtmResponse { + btm_response: embedded_batteries_async::acpi::BtmReturnResult, + }, + BatterySetBmsResponse { + status: u32, + }, + BatterySetBmaResponse { + status: u32, + }, + BatteryGetStaResponse { + sta: embedded_batteries_async::acpi::StaReturn, + }, +} + +impl SerializableMessage for AcpiBatteryResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::BatteryGetBixResponse { bix } => bix.to_bytes(buffer).map(|_| 100), + Self::BatteryGetBstResponse { bst } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_state.bits())); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_present_rate)); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_remaining_capacity)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_present_voltage)); + + Ok(BST_RETURN_SIZE_BYTES) + } + Self::BatteryGetPsrResponse { psr } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(psr.power_source.into())); + + Ok(PSR_RETURN_SIZE_BYTES) + } + + Self::BatteryGetPifResponse { pif } => pif.to_bytes(buffer).map(|_| 36), + Self::BatteryGetBpsResponse { bps } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.revision)); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_level)); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_period)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_level)); + buffer + .get_mut(16..20) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_period)); + + Ok(BPS_RETURN_SIZE_BYTES) + } + Self::BatterySetBtpResponse {} => Ok(0), + Self::BatterySetBptResponse {} => Ok(0), + Self::BatteryGetBpcResponse { bpc } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.revision)); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.power_threshold_support.bits())); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.max_instantaneous_peak_power_threshold)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.max_sustainable_peak_power_threshold)); + + Ok(BPC_RETURN_SIZE_BYTES) + } + Self::BatterySetBmcResponse {} => Ok(0), + Self::BatteryGetBmdResponse { bmd } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.status_flags.bits())); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.capability_flags.bits())); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.recalibrate_count)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.quick_recalibrate_time)); + buffer + .get_mut(16..20) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.slow_recalibrate_time)); + + Ok(BMD_RETURN_SIZE_BYTES) + } + Self::BatteryGetBctResponse { bct_response } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bct_response.into())); + + Ok(BCT_RETURN_SIZE_BYTES) + } + Self::BatteryGetBtmResponse { btm_response } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(btm_response.into())); + + Ok(BTM_RETURN_SIZE_BYTES) + } + Self::BatterySetBmsResponse { status } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(status)); + + Ok(4) + } + Self::BatterySetBmaResponse { status } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(status)); + + Ok(4) + } + Self::BatteryGetStaResponse { sta } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(sta.bits())); + + Ok(STA_RETURN_SIZE_BYTES) + } + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + BatteryCmd::from(self).into() + } +} + +/// Fuel gauge ID +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DeviceId(pub u8); + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum AcpiBatteryError { + UnknownDeviceId = 1, + UnspecifiedFailure = 2, +} + +impl SerializableMessage for AcpiBatteryError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + AcpiBatteryError::UnknownDeviceId | AcpiBatteryError::UnspecifiedFailure => Ok(0), + } + } + + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + AcpiBatteryError::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant)) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +fn safe_get_u8(buffer: &[u8], index: usize) -> Result { + buffer + .get(index) + .copied() + .ok_or(MessageSerializationError::BufferTooSmall) +} + +fn safe_get_dword(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u32::from_le_bytes(bytes)) +} diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 44d718bf..2e12930a 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] defmt = { workspace = true, optional = true } +battery-service-messages.workspace = true embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true @@ -27,6 +28,7 @@ mctp-rs = { workspace = true, features = ["espi"] } default = [] defmt = [ "dep:defmt", + "battery-service-messages/defmt", "embedded-services/defmt", "embassy-time/defmt", "embassy-sync/defmt", diff --git a/battery-service/src/acpi.rs b/battery-service/src/acpi.rs index c2d0dab0..91031852 100644 --- a/battery-service/src/acpi.rs +++ b/battery-service/src/acpi.rs @@ -2,89 +2,19 @@ use core::ops::Deref; use embedded_batteries_async::acpi::{PowerSourceState, PowerUnit}; -use embedded_services::{ - debug, - ec_type::message::{ - STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, - STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, StdHostMsg, StdHostRequest, - }, - ec_type::protocols::mctp, - error, info, - power::policy::PowerCapability, - trace, +use embedded_services::{info, power::policy::PowerCapability, trace}; + +use battery_service_messages::{ + AcpiBatteryResponse, BixFixedStrings, DeviceId, PifFixedStrings, STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, + STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, }; use crate::{ + AcpiBatteryError, context::PsuState, - device::{DeviceId, DynamicBatteryMsgs, StaticBatteryMsgs}, + device::{DynamicBatteryMsgs, StaticBatteryMsgs}, }; -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct Payload<'a> { - pub command: AcpiCmd, - pub status: u8, - pub data: &'a [u8], -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum PayloadError { - MalformedPayload, - BufTooSmall(usize), -} - -const ACPI_HEADER_SIZE: usize = 4; - -#[derive(Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum AcpiCmd { - GetBix = 1, - GetBst = 2, - GetPsr = 3, - GetPif = 4, - GetBps = 5, - SetBtp = 6, - SetBpt = 7, - GetBpc = 8, - SetBmc = 9, - GetBmd = 10, - GetBct = 11, - GetBtm = 12, - SetBms = 13, - SetBma = 14, - GetSta = 15, -} - -impl TryFrom for AcpiCmd { - type Error = PayloadError; - fn try_from(value: u8) -> Result { - match value { - 1 => Ok(AcpiCmd::GetBix), - 2 => Ok(AcpiCmd::GetBst), - 3 => Ok(AcpiCmd::GetPsr), - 4 => Ok(AcpiCmd::GetPif), - 5 => Ok(AcpiCmd::GetBps), - 6 => Ok(AcpiCmd::SetBtp), - 7 => Ok(AcpiCmd::SetBpt), - 8 => Ok(AcpiCmd::GetBpc), - 9 => Ok(AcpiCmd::SetBmc), - 10 => Ok(AcpiCmd::GetBmd), - 11 => Ok(AcpiCmd::GetBct), - 12 => Ok(AcpiCmd::GetBtm), - 13 => Ok(AcpiCmd::SetBms), - 14 => Ok(AcpiCmd::SetBma), - 15 => Ok(AcpiCmd::GetSta), - _ => Err(PayloadError::MalformedPayload), - } - } -} - -impl From for u8 { - fn from(value: AcpiCmd) -> Self { - value as u8 - } -} - pub(crate) fn compute_bst(cache: &DynamicBatteryMsgs) -> embedded_batteries_async::acpi::BstReturn { let charging = if cache.battery_status & (1 << 6) == 0 { embedded_batteries_async::acpi::BatteryState::CHARGING @@ -104,37 +34,34 @@ pub(crate) fn compute_bst(cache: &DynamicBatteryMsgs) -> embedded_batteries_asyn pub(crate) fn compute_bix<'a>( static_cache: &'a StaticBatteryMsgs, dynamic_cache: &'a DynamicBatteryMsgs, -) -> Result, ()> -{ - let mut bix_return = - mctp::BixFixedStrings:: { - revision: 1, - power_unit: if static_cache.battery_mode.capacity_mode() { - PowerUnit::MilliWatts - } else { - PowerUnit::MilliAmps - }, - design_capacity: static_cache.design_capacity_mwh, - last_full_charge_capacity: dynamic_cache.full_charge_capacity_mwh, - battery_technology: embedded_batteries_async::acpi::BatteryTechnology::Secondary, - design_voltage: static_cache.design_voltage_mv.into(), - design_cap_of_warning: static_cache.design_cap_warning, - design_cap_of_low: static_cache.design_cap_low, - cycle_count: dynamic_cache.cycle_count.into(), - measurement_accuracy: u32::from(100 - dynamic_cache.max_error_pct) * 1000u32, - max_sampling_time: static_cache.max_sample_time, - min_sampling_time: static_cache.min_sample_time, - max_averaging_interval: static_cache.max_averaging_interval, - min_averaging_interval: static_cache.min_averaging_interval, - battery_capacity_granularity_1: static_cache.cap_granularity_1, - battery_capacity_granularity_2: static_cache.cap_granularity_2, - model_number: [0u8; STD_BIX_MODEL_SIZE], - serial_number: [0u8; STD_BIX_SERIAL_SIZE], - battery_type: [0u8; STD_BIX_BATTERY_SIZE], - oem_info: [0u8; STD_BIX_OEM_SIZE], - battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability::NonSwappable, - }; - +) -> Result { + let mut bix_return = BixFixedStrings { + revision: 1, + power_unit: if static_cache.battery_mode.capacity_mode() { + PowerUnit::MilliWatts + } else { + PowerUnit::MilliAmps + }, + design_capacity: static_cache.design_capacity_mwh, + last_full_charge_capacity: dynamic_cache.full_charge_capacity_mwh, + battery_technology: embedded_batteries_async::acpi::BatteryTechnology::Secondary, + design_voltage: static_cache.design_voltage_mv.into(), + design_cap_of_warning: static_cache.design_cap_warning, + design_cap_of_low: static_cache.design_cap_low, + cycle_count: dynamic_cache.cycle_count.into(), + measurement_accuracy: u32::from(100 - dynamic_cache.max_error_pct) * 1000u32, + max_sampling_time: static_cache.max_sample_time, + min_sampling_time: static_cache.min_sample_time, + max_averaging_interval: static_cache.max_averaging_interval, + min_averaging_interval: static_cache.min_averaging_interval, + battery_capacity_granularity_1: static_cache.cap_granularity_1, + battery_capacity_granularity_2: static_cache.cap_granularity_2, + model_number: [0u8; STD_BIX_MODEL_SIZE], + serial_number: [0u8; STD_BIX_SERIAL_SIZE], + battery_type: [0u8; STD_BIX_BATTERY_SIZE], + oem_info: [0u8; STD_BIX_OEM_SIZE], + battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability::NonSwappable, + }; let model_number_len = core::cmp::min(STD_BIX_MODEL_SIZE - 1, static_cache.device_name.len() - 1); bix_return .model_number @@ -234,16 +161,14 @@ pub(crate) fn compute_psr(psu_state: &PsuState) -> embedded_batteries_async::acp } } -pub(crate) fn compute_pif( - psu_state: &PsuState, -) -> mctp::PifFixedStrings { +pub(crate) fn compute_pif(psu_state: &PsuState) -> PifFixedStrings { // TODO: Grab real values from power policy let capability = psu_state.power_capability.unwrap_or(PowerCapability { voltage_mv: 0, current_ma: 0, }); - mctp::PifFixedStrings { + PifFixedStrings { power_source_state: PowerSourceState::empty(), max_output_power: capability.max_power_mw(), max_input_power: capability.max_power_mw(), @@ -255,437 +180,221 @@ pub(crate) fn compute_pif( impl crate::context::Context { // TODO Move these to a trait - pub(super) async fn bix_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bix_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BIX command!"); - // Enough space for all string fields to have 7 bytes + 1 null terminator byte - match request.payload { - mctp::Odp::BatteryGetBixRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - let static_cache_guard = fg.get_static_battery_cache_guarded().await; - let dynamic_cache_guard = fg.get_dynamic_battery_cache_guarded().await; - request.payload = mctp::Odp::BatteryGetBixResponse { - bix: match compute_bix(static_cache_guard.deref(), dynamic_cache_guard.deref()) { - Ok(bix) => bix, - Err(()) => { - error!("Battery service: Failed to compute BIX"); - // Drop locks before next await point to eliminate possibility of deadlock - drop(static_cache_guard); - drop(dynamic_cache_guard); - - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - request, - ) - .await - .unwrap(); - debug!("response sent to espi_service"); - return; - } - }, - }; - // Drop locks before next await point to eliminate possibility of deadlock - drop(static_cache_guard); - drop(dynamic_cache_guard); - - request.status = 0; - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - - debug!("response sent to espi_service"); - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - } - } - _ => error!("Battery service: command and body mismatch!"), - } + + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + let static_cache_guard = fg.get_static_battery_cache_guarded().await; + let dynamic_cache_guard = fg.get_dynamic_battery_cache_guarded().await; + + Ok(AcpiBatteryResponse::BatteryGetBixResponse { + bix: compute_bix(static_cache_guard.deref(), dynamic_cache_guard.deref()) + .map_err(|_| AcpiBatteryError::UnspecifiedFailure)?, + }) } - pub(super) async fn bst_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bst_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BST command!"); - match request.payload { - mctp::Odp::BatteryGetBstRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetBstResponse { - bst: compute_bst(&fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - - trace!("response sent to espi_service"); + + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetBstResponse { + bst: compute_bst(&fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn psr_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn psr_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got PSR command!"); - match request.payload { - mctp::Odp::BatteryGetPsrRequest { battery_id } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetPsrResponse { - psr: compute_psr(&self.get_power_info().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - trace!("response sent to espi_service"); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetPsrResponse { + psr: compute_psr(&self.get_power_info().await), + }) } - pub(super) async fn pif_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn pif_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got PIF command!"); - match request.payload { - mctp::Odp::BatteryGetPifRequest { battery_id } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetPifResponse { - pif: compute_pif(&self.get_power_info().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - trace!("response sent to espi_service"); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetPifResponse { + pif: compute_pif(&self.get_power_info().await), + }) } - pub(super) async fn bps_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bps_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BPS command!"); - match request.payload { - mctp::Odp::BatteryGetBpsRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetBpsResponse { - bps: compute_bps(&fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetBpsResponse { + bps: compute_bps(&fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn btp_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn btp_handler( + &self, + device_id: DeviceId, + btp: embedded_batteries_async::acpi::Btp, + ) -> Result { trace!("Battery service: got BTP command!"); - match request.payload { - mctp::Odp::BatterySetBtpRequest { battery_id, btp } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - // TODO: Save trip point - info!("Battery service: New BTP {}", btp.trip_point); - request.payload = mctp::Odp::BatterySetBtpResponse {}; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + // TODO: Save trip point + info!("Battery service: New BTP {}", btp.trip_point); + + Ok(AcpiBatteryResponse::BatterySetBtpResponse {}) } - pub(super) async fn bpt_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bpt_handler( + &self, + device_id: DeviceId, + bpt: embedded_batteries_async::acpi::Bpt, + ) -> Result { trace!("Battery service: got BPT command!"); - match request.payload { - mctp::Odp::BatterySetBptRequest { battery_id, bpt } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!( - "Battery service: Threshold ID: {:?}, Threshold value: {:?}", - bpt.threshold_id as u32, bpt.threshold_value - ); - request.payload = mctp::Odp::BatterySetBptResponse {}; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!( + "Battery service: Threshold ID: {:?}, Threshold value: {:?}", + bpt.threshold_id as u32, bpt.threshold_value + ); + + Ok(AcpiBatteryResponse::BatterySetBptResponse {}) } - pub(super) async fn bpc_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bpc_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BPC command!"); - match request.payload { - mctp::Odp::BatteryGetBpcRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - // TODO: Save trip point - request.payload = mctp::Odp::BatteryGetBpcResponse { - bpc: compute_bpc(&fg.get_static_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + // TODO: Save trip point + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetBpcResponse { + bpc: compute_bpc(&fg.get_static_battery_cache().await), + }) } - pub(super) async fn bmc_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bmc_handler( + &self, + device_id: DeviceId, + bmc: embedded_batteries_async::acpi::Bmc, + ) -> Result { trace!("Battery service: got BMC command!"); - match request.payload { - mctp::Odp::BatterySetBmcRequest { battery_id, bmc } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Battery service: Bmc {}", bmc.maintenance_control_flags.bits()); - request.payload = mctp::Odp::BatterySetBmcResponse {}; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Battery service: Bmc {}", bmc.maintenance_control_flags.bits()); + + Ok(AcpiBatteryResponse::BatterySetBmcResponse {}) } - pub(super) async fn bmd_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bmd_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BMD command!"); - match request.payload { - mctp::Odp::BatteryGetBmdRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - let static_cache = fg.get_static_battery_cache().await; - let dynamic_cache = fg.get_dynamic_battery_cache().await; - request.payload = mctp::Odp::BatteryGetBmdResponse { - bmd: compute_bmd(&static_cache, &dynamic_cache), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + let static_cache = fg.get_static_battery_cache().await; + let dynamic_cache = fg.get_dynamic_battery_cache().await; + + Ok(AcpiBatteryResponse::BatteryGetBmdResponse { + bmd: compute_bmd(&static_cache, &dynamic_cache), + }) } - pub(super) async fn bct_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bct_handler( + &self, + device_id: DeviceId, + bct: embedded_batteries_async::acpi::Bct, + ) -> Result { trace!("Battery service: got BCT command!"); - match request.payload { - mctp::Odp::BatteryGetBctRequest { battery_id, bct } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BCT charge_level_percent: {}", bct.charge_level_percent); - request.payload = mctp::Odp::BatteryGetBctResponse { - bct_response: compute_bct(&bct, &fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BCT charge_level_percent: {}", bct.charge_level_percent); + Ok(AcpiBatteryResponse::BatteryGetBctResponse { + bct_response: compute_bct(&bct, &fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn btm_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn btm_handler( + &self, + device_id: DeviceId, + btm: embedded_batteries_async::acpi::Btm, + ) -> Result { trace!("Battery service: got BTM command!"); - match request.payload { - mctp::Odp::BatteryGetBtmRequest { battery_id, btm } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BTM discharge_rate: {}", btm.discharge_rate); - request.payload = mctp::Odp::BatteryGetBtmResponse { - btm_response: compute_btm(&btm, &fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BTM discharge_rate: {}", btm.discharge_rate); + Ok(AcpiBatteryResponse::BatteryGetBtmResponse { + btm_response: compute_btm(&btm, &fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn bms_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bms_handler( + &self, + device_id: DeviceId, + bms: embedded_batteries_async::acpi::Bms, + ) -> Result { trace!("Battery service: got BMS command!"); - match request.payload { - mctp::Odp::BatterySetBmsRequest { battery_id, bms } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BMS sampling_time: {}", bms.sampling_time_ms); - request.payload = mctp::Odp::BatterySetBmsResponse { status: 0 }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::BatterySetBmsResponse { status: 1 }; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BMS sampling_time: {}", bms.sampling_time_ms); + Ok(AcpiBatteryResponse::BatterySetBmsResponse { status: 0 }) } - pub(super) async fn bma_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bma_handler( + &self, + device_id: DeviceId, + bma: embedded_batteries_async::acpi::Bma, + ) -> Result { trace!("Battery service: got BMA command!"); - match request.payload { - mctp::Odp::BatterySetBmaRequest { battery_id, bma } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BMA averaging_interval_ms: {}", bma.averaging_interval_ms); - request.payload = mctp::Odp::BatterySetBmaResponse { status: 0 }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::BatterySetBmaResponse { status: 1 }; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BMA averaging_interval_ms: {}", bma.averaging_interval_ms); + Ok(AcpiBatteryResponse::BatterySetBmaResponse { status: 0 }) } - pub(super) async fn sta_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn sta_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got STA command!"); - match request.payload { - mctp::Odp::BatteryGetStaRequest { battery_id } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetStaResponse { sta: compute_sta() }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetStaResponse { sta: compute_sta() }) } } diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index 48764f60..73edb63d 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -1,5 +1,7 @@ -use crate::device::{self, DeviceId}; +use crate::AcpiBatteryError; +use crate::device::{self}; use crate::device::{Device, FuelGaugeError}; +use battery_service_messages::{AcpiBatteryRequest, AcpiBatteryResponse, DeviceId}; use embassy_sync::channel::Channel; use embassy_sync::channel::TrySendError; use embassy_sync::mutex::Mutex; @@ -7,8 +9,6 @@ use embassy_sync::signal::Signal; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::comms::MailboxDelegateError; -use embedded_services::ec_type::message::StdHostRequest; -use embedded_services::ec_type::protocols::acpi::BatteryCmd; use embedded_services::power::policy::PowerCapability; use embedded_services::{IntrusiveList, debug, error, info, intrusive_list, trace, warn}; @@ -138,7 +138,7 @@ pub struct Context { battery_response: Channel, no_op_retry_count: AtomicUsize, config: Config, - acpi_request: Signal, + acpi_request: Signal, power_info: Mutex, } @@ -162,8 +162,6 @@ impl Default for Config { } } -embedded_services::define_static_buffer!(acpi_buf, u8, [0u8; 133]); - impl Context { /// Create a new context instance. pub fn new() -> Self { @@ -404,27 +402,51 @@ impl Context { } } - pub(super) async fn process_acpi_cmd(&self, acpi_msg: &mut StdHostRequest) { - match acpi_msg.command { - embedded_services::ec_type::message::OdpCommand::Battery(cmd) => match cmd { - BatteryCmd::GetBix => self.bix_handler(acpi_msg).await, - BatteryCmd::GetBst => self.bst_handler(acpi_msg).await, - BatteryCmd::GetPsr => self.psr_handler(acpi_msg).await, - BatteryCmd::GetPif => self.pif_handler(acpi_msg).await, - BatteryCmd::GetBps => self.bps_handler(acpi_msg).await, - BatteryCmd::SetBtp => self.btp_handler(acpi_msg).await, - BatteryCmd::SetBpt => self.bpt_handler(acpi_msg).await, - BatteryCmd::GetBpc => self.bpc_handler(acpi_msg).await, - BatteryCmd::SetBmc => self.bmc_handler(acpi_msg).await, - BatteryCmd::GetBmd => self.bmd_handler(acpi_msg).await, - BatteryCmd::GetBct => self.bct_handler(acpi_msg).await, - BatteryCmd::GetBtm => self.btm_handler(acpi_msg).await, - BatteryCmd::SetBms => self.bms_handler(acpi_msg).await, - BatteryCmd::SetBma => self.bma_handler(acpi_msg).await, - BatteryCmd::GetSta => self.sta_handler(acpi_msg).await, - }, - _ => error!("Battery service: host command not found!"), + pub(super) async fn process_acpi_cmd(&self, acpi_msg: &AcpiBatteryRequest) { + let response: Result = match *acpi_msg { + AcpiBatteryRequest::BatteryGetBixRequest { battery_id } => self.bix_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetBstRequest { battery_id } => self.bst_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetPsrRequest { battery_id } => self.psr_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetPifRequest { battery_id } => self.pif_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetBpsRequest { battery_id } => self.bps_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatterySetBtpRequest { battery_id, btp } => { + self.btp_handler(DeviceId(battery_id), btp).await + } + AcpiBatteryRequest::BatterySetBptRequest { battery_id, bpt } => { + self.bpt_handler(DeviceId(battery_id), bpt).await + } + AcpiBatteryRequest::BatteryGetBpcRequest { battery_id } => self.bpc_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatterySetBmcRequest { battery_id, bmc } => { + self.bmc_handler(DeviceId(battery_id), bmc).await + } + AcpiBatteryRequest::BatteryGetBmdRequest { battery_id } => self.bmd_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetBctRequest { battery_id, bct } => { + self.bct_handler(DeviceId(battery_id), bct).await + } + AcpiBatteryRequest::BatteryGetBtmRequest { battery_id, btm } => { + self.btm_handler(DeviceId(battery_id), btm).await + } + + AcpiBatteryRequest::BatterySetBmsRequest { battery_id, bms } => { + self.bms_handler(DeviceId(battery_id), bms).await + } + AcpiBatteryRequest::BatterySetBmaRequest { battery_id, bma } => { + self.bma_handler(DeviceId(battery_id), bma).await + } + AcpiBatteryRequest::BatteryGetStaRequest { battery_id } => self.sta_handler(DeviceId(battery_id)).await, + }; + + if let Err(e) = response { + error!("Battery service command failed: {:?}", e); } + + // TODO We should probably be responding to the requestor rather than just assuming the request came from the host + super::comms_send( + crate::EndpointID::External(embedded_services::comms::External::Host), + &response, + ) + .await + .expect("comms_send is infallible"); } pub(crate) fn get_fuel_gauge(&self, id: DeviceId) -> Option<&'static Device> { @@ -472,11 +494,11 @@ impl Context { self.battery_event.receive().await } - pub(super) fn send_acpi_cmd(&self, raw: StdHostRequest) { - self.acpi_request.signal(raw); + pub(super) fn send_acpi_cmd(&self, request: AcpiBatteryRequest) { + self.acpi_request.signal(request); } - pub(super) async fn wait_acpi_cmd(&self) -> StdHostRequest { + pub(super) async fn wait_acpi_cmd(&self) -> AcpiBatteryRequest { self.acpi_request.wait().await } diff --git a/battery-service/src/device.rs b/battery-service/src/device.rs index 6cf314f6..3c0f7378 100644 --- a/battery-service/src/device.rs +++ b/battery-service/src/device.rs @@ -9,6 +9,8 @@ use embedded_batteries_async::{ }; use embedded_services::{GlobalRawMutex, Node, NodeContainer, SyncCell}; +pub use battery_service_messages::DeviceId; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Device errors. @@ -155,11 +157,6 @@ pub struct DynamicBatteryMsgs { pub bmd_status: BmdStatusFlags, } -/// Fuel gauge ID -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - /// Hardware agnostic device object to be registered with context. pub struct Device { node: embedded_services::Node, diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index c6554076..811d48c2 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -2,11 +2,11 @@ use core::{any::Any, convert::Infallible}; +use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest}; use context::BatteryEvent; use embassy_futures::select::select; use embedded_services::{ comms::{self, EndpointID}, - ec_type::message::StdHostRequest, trace, }; @@ -62,9 +62,9 @@ impl Service { trace!("Battery service: state machine event recvd {:?}", event); self.context.process(event).await } - Event::AcpiRequest(mut acpi_msg) => { + Event::AcpiRequest(acpi_msg) => { trace!("Battery service: ACPI cmd recvd"); - self.context.process_acpi_cmd(&mut acpi_msg).await + self.context.process_acpi_cmd(&acpi_msg).await } } } @@ -74,7 +74,7 @@ impl Service { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Event { StateMachine(BatteryEvent), - AcpiRequest(StdHostRequest), + AcpiRequest(AcpiBatteryRequest), } impl Default for Service { @@ -89,8 +89,8 @@ impl comms::MailboxDelegate for Service { self.context.send_event_no_wait(*event).map_err(|e| match e { embassy_sync::channel::TrySendError::Full(_) => comms::MailboxDelegateError::BufferFull, })? - } else if let Some(acpi_cmd) = message.data.get::() { - self.context.send_acpi_cmd(*acpi_cmd); + } else if let Some(battery_request) = message.data.get::() { + self.context.send_acpi_cmd(*battery_request); } else if let Some(power_policy_msg) = message.data.get::() { self.context.set_power_info(&power_policy_msg.data)?; } diff --git a/debug-service-messages/Cargo.toml b/debug-service-messages/Cargo.toml new file mode 100644 index 00000000..5127a757 --- /dev/null +++ b/debug-service-messages/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "debug-service-messages" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embedded-services.workspace = true +num_enum.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt"] diff --git a/debug-service-messages/src/lib.rs b/debug-service-messages/src/lib.rs new file mode 100644 index 00000000..c8b1262e --- /dev/null +++ b/debug-service-messages/src/lib.rs @@ -0,0 +1,115 @@ +#![no_std] +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// Standard Debug Service Log Buffer Size +pub const STD_DEBUG_BUF_SIZE: usize = 128; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// ODP Specific Debug Commands +enum DebugCmd { + /// Get buffer of debug messages, if available. + /// Can be used to poll debug messages. + GetMsgs = 1, +} + +impl From<&DebugRequest> for DebugCmd { + fn from(request: &DebugRequest) -> Self { + match request { + DebugRequest::DebugGetMsgsRequest => DebugCmd::GetMsgs, + } + } +} + +impl From<&DebugResponse> for DebugCmd { + fn from(response: &DebugResponse) -> Self { + match response { + DebugResponse::DebugGetMsgsResponse { .. } => DebugCmd::GetMsgs, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DebugRequest { + DebugGetMsgsRequest, +} + +impl SerializableMessage for DebugRequest { + fn serialize(self, _buffer: &mut [u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + Ok( + match DebugCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + DebugCmd::GetMsgs => Self::DebugGetMsgsRequest, + }, + ) + } + + fn discriminant(&self) -> u16 { + let cmd: DebugCmd = self.into(); + cmd.into() + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DebugResponse { + DebugGetMsgsResponse { debug_buf: [u8; STD_DEBUG_BUF_SIZE] }, +} + +impl SerializableMessage for DebugResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::DebugGetMsgsResponse { debug_buf } => { + buffer + .get_mut(..debug_buf.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&debug_buf); + Ok(debug_buf.len()) + } + } + } + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + DebugCmd::from(self).into() + } +} + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum DebugError { + UnspecifiedFailure = 1, +} + +impl SerializableMessage for DebugError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + Self::UnspecifiedFailure => Ok(0), + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +pub type DebugResult = Result; diff --git a/debug-service/Cargo.toml b/debug-service/Cargo.toml index 799499ca..1e27eda1 100644 --- a/debug-service/Cargo.toml +++ b/debug-service/Cargo.toml @@ -9,11 +9,15 @@ repository.workspace = true bbq2 = "0.4.2" critical-section.workspace = true defmt.workspace = true +debug-service-messages.workspace = true embassy-sync.workspace = true embassy-time.workspace = true embedded-services.workspace = true log.workspace = true rtt-target = "0.6.1" +[features] +defmt = ["debug-service-messages/defmt"] + [lints] workspace = true diff --git a/debug-service/src/debug_service.rs b/debug-service/src/debug_service.rs index 134fed49..1626ca6b 100644 --- a/debug-service/src/debug_service.rs +++ b/debug-service/src/debug_service.rs @@ -2,7 +2,6 @@ use embassy_sync::{once_lock::OnceLock, signal::Signal}; use embedded_services::GlobalRawMutex; use embedded_services::buffer::{OwnedRef, SharedRef}; use embedded_services::comms::{self, EndpointID, Internal}; -use embedded_services::ec_type::message::StdHostRequest; use embedded_services::{debug, error}; // Maximum number of bytes to request per defmt frame write grant. @@ -15,7 +14,7 @@ use embedded_services::{debug, error}; // - No partial publication: a frame is either not yet committed or fully committed. // If a defmt log event were to exceed this size, it will be split across multiple // BBQueue frames (each ≤ 1024). The consumer always observes complete frames. -pub(crate) const DEFMT_MAX_BYTES: u16 = embedded_services::ec_type::message::STD_DEBUG_BUF_SIZE as u16; +pub(crate) const DEFMT_MAX_BYTES: u16 = debug_service_messages::STD_DEBUG_BUF_SIZE as u16; // Static buffer for ACPI-style messages carrying defmt frames embedded_services::define_static_buffer!(defmt_acpi_buf, u8, [0u8; DEFMT_MAX_BYTES as usize]); @@ -69,7 +68,7 @@ impl Service { impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(_request) = message.data.get::() { + if let Some(_request) = message.data.get::() { // Host sent an ACPI/MCTP request (e.g. GetDebugBuffer). Treat this as the // trigger to send the staged debug buffer back to the host. embedded_services::trace!("Received host ACPI request for debug buffer from {:?}", message.from); diff --git a/debug-service/src/task.rs b/debug-service/src/task.rs index 757c31bb..5d973a4a 100644 --- a/debug-service/src/task.rs +++ b/debug-service/src/task.rs @@ -1,9 +1,7 @@ use core::borrow::{Borrow, BorrowMut}; -use embedded_services::{ - comms, - ec_type::message::{StdHostPayload, StdHostRequest}, -}; +use debug_service_messages::{DebugError, DebugResponse}; +use embedded_services::comms; use crate::{debug_service_entry, defmt_ring_logger::DEFMT_BUFFER, frame_available, shared_buffer}; @@ -20,7 +18,6 @@ pub async fn defmt_to_host_task() -> Result { embedded_services::info!("defmt to host task start"); use crate::debug_service::{host_endpoint_id, response_notify_signal}; use embedded_services::comms::{self, EndpointID, Internal}; - use embedded_services::ec_type::message::HostMsg; let framed_consumer = DEFMT_BUFFER.framed_consumer(); @@ -67,19 +64,13 @@ pub async fn defmt_to_host_task() -> Result { // Send the staged defmt bytes frame as an ACPI-style message. // Scope the message so the shared borrow is dropped before we clear the buffer. { - let msg = HostMsg::Response(StdHostRequest { - command: embedded_services::ec_type::message::OdpCommand::Debug( - embedded_services::ec_type::protocols::debug::DebugCmd::GetMsgs, - ), - status: 0, - payload: StdHostPayload::DebugGetMsgsResponse { - debug_buf: { - let access = shared_buffer().borrow().map_err(Error::Buffer)?; - let slice: &[u8] = access.borrow(); - slice.try_into().unwrap() - }, + let msg = DebugResponse::DebugGetMsgsResponse { + debug_buf: { + let access = shared_buffer().borrow().map_err(Error::Buffer)?; + let slice: &[u8] = access.borrow(); + slice.try_into().unwrap() }, - }); + }; let _ = comms::send(EndpointID::Internal(Internal::Debug), host_ep, &msg).await; embedded_services::trace!("sent {} defmt bytes to host", copy_len); } @@ -99,7 +90,6 @@ pub async fn no_avail_to_host_task() -> Result embedded_services::info!("no avail to host task start"); use crate::debug_service::{host_endpoint_id, no_avail_notify_signal}; use embedded_services::comms::{self, EndpointID, Internal}; - use embedded_services::ec_type::message::HostMsg; let host_ep = host_endpoint_id().await; @@ -111,13 +101,7 @@ pub async fn no_avail_to_host_task() -> Result buf[4..12].copy_from_slice(&0xDEADBEEFu64.to_be_bytes()); } - let msg = HostMsg::Response(StdHostRequest { - command: embedded_services::ec_type::message::OdpCommand::Debug( - embedded_services::ec_type::protocols::debug::DebugCmd::GetMsgs, - ), - status: 1, - payload: StdHostPayload::ErrorResponse {}, - }); + let msg: Result = Err(DebugError::UnspecifiedFailure); // Send DEADBEEF if host requests frame but non available loop { diff --git a/embedded-service/src/ec_type/message.rs b/embedded-service/src/ec_type/message.rs index 3aa6be58..f31f178a 100644 --- a/embedded-service/src/ec_type/message.rs +++ b/embedded-service/src/ec_type/message.rs @@ -1,7 +1,5 @@ //! EC Internal Messages -use crate::ec_type::protocols::{acpi, debug, mctp::OdpCommandCode, mptf}; - #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] pub enum CapabilitiesMessage { @@ -68,26 +66,6 @@ pub enum BatteryMessage { SampleTime(u32), } -/// ACPI Message, compatible with comms system -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct HostRequest { - /// Command - pub command: Command, - /// Status code - pub status: u8, - /// Data payload - pub payload: Payload, -} - -/// Notification type to be sent to Host -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct NotificationMsg { - /// Interrupt offset - pub offset: u8, -} - #[allow(missing_docs)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum ThermalMessage { @@ -108,163 +86,3 @@ pub enum ThermalMessage { Tmp1Low(u32), Tmp1High(u32), } - -/// Message type that services can send to communicate with the Host. -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum HostMsg { - /// Notification without data. After receivng a notification, - /// typically the host will request some data from the EC - Notification(NotificationMsg), - /// Response to Host request. - Response(HostRequest), -} - -/// ODP specific command code that can come in from the host. -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum OdpCommand { - /// Battery commands - Battery(acpi::BatteryCmd), - /// Thermal commands - Thermal(mptf::ThermalCmd), - /// Debug commands - Debug(debug::DebugCmd), -} - -/// Standard Battery Service Model Number String Size -pub const STD_BIX_MODEL_SIZE: usize = 8; -/// Standard Battery Service Serial Number String Size -pub const STD_BIX_SERIAL_SIZE: usize = 8; -/// Standard Battery Service Battery Type String Size -pub const STD_BIX_BATTERY_SIZE: usize = 8; -/// Standard Battery Service OEM Info String Size -pub const STD_BIX_OEM_SIZE: usize = 8; -/// Standard Power Policy Service Model Number String Size -pub const STD_PIF_MODEL_SIZE: usize = 8; -/// Standard Power Policy Serial Number String Size -pub const STD_PIF_SERIAL_SIZE: usize = 8; -/// Standard Power Policy Service OEM Info String Size -pub const STD_PIF_OEM_SIZE: usize = 8; -/// Standard Debug Service Log Buffer Size -pub const STD_DEBUG_BUF_SIZE: usize = 128; - -/// Standard ODP Host Payload -pub type StdHostPayload = crate::ec_type::protocols::mctp::Odp< - STD_BIX_MODEL_SIZE, - STD_BIX_SERIAL_SIZE, - STD_BIX_BATTERY_SIZE, - STD_BIX_OEM_SIZE, - STD_PIF_MODEL_SIZE, - STD_PIF_SERIAL_SIZE, - STD_PIF_OEM_SIZE, - STD_DEBUG_BUF_SIZE, ->; - -/// Standard Host Request -pub type StdHostRequest = HostRequest; -/// Standard Host Message -pub type StdHostMsg = HostMsg; - -impl From for OdpCommand { - fn from(value: OdpCommandCode) -> Self { - match value { - OdpCommandCode::BatteryGetBixRequest | OdpCommandCode::BatteryGetBixResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBix) - } - OdpCommandCode::BatteryGetBstRequest | OdpCommandCode::BatteryGetBstResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBst) - } - OdpCommandCode::BatteryGetPsrRequest | OdpCommandCode::BatteryGetPsrResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetPsr) - } - OdpCommandCode::BatteryGetPifRequest | OdpCommandCode::BatteryGetPifResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetPif) - } - OdpCommandCode::BatteryGetBpsRequest | OdpCommandCode::BatteryGetBpsResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBps) - } - OdpCommandCode::BatterySetBtpRequest | OdpCommandCode::BatterySetBtpResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBtp) - } - OdpCommandCode::BatterySetBptRequest | OdpCommandCode::BatterySetBptResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBpt) - } - OdpCommandCode::BatteryGetBpcRequest | OdpCommandCode::BatteryGetBpcResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBpc) - } - OdpCommandCode::BatterySetBmcRequest | OdpCommandCode::BatterySetBmcResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBmc) - } - OdpCommandCode::BatteryGetBmdRequest | OdpCommandCode::BatteryGetBmdResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBmd) - } - OdpCommandCode::BatteryGetBctRequest | OdpCommandCode::BatteryGetBctResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBct) - } - OdpCommandCode::BatteryGetBtmRequest | OdpCommandCode::BatteryGetBtmResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBtm) - } - OdpCommandCode::BatterySetBmsRequest | OdpCommandCode::BatterySetBmsResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBms) - } - OdpCommandCode::BatterySetBmaRequest | OdpCommandCode::BatterySetBmaResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBma) - } - OdpCommandCode::BatteryGetStaRequest | OdpCommandCode::BatteryGetStaResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetSta) - } - OdpCommandCode::ThermalGetTmpRequest | OdpCommandCode::ThermalGetTmpResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::GetTmp) - } - OdpCommandCode::ThermalSetThrsRequest | OdpCommandCode::ThermalSetThrsResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::SetThrs) - } - OdpCommandCode::ThermalGetThrsRequest | OdpCommandCode::ThermalGetThrsResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::GetThrs) - } - OdpCommandCode::ThermalSetScpRequest | OdpCommandCode::ThermalSetScpResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::SetScp) - } - OdpCommandCode::ThermalGetVarRequest | OdpCommandCode::ThermalGetVarResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::GetVar) - } - OdpCommandCode::ThermalSetVarRequest | OdpCommandCode::ThermalSetVarResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::SetVar) - } - OdpCommandCode::DebugGetMsgsRequest | OdpCommandCode::DebugGetMsgsResponse => { - OdpCommand::Debug(debug::DebugCmd::GetMsgs) - } - } - } -} - -// TODO: Maybe map to Response instead? -impl From for OdpCommandCode { - fn from(value: OdpCommand) -> Self { - match value { - OdpCommand::Battery(acpi::BatteryCmd::GetBix) => OdpCommandCode::BatteryGetBixRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBst) => OdpCommandCode::BatteryGetBstRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetPsr) => OdpCommandCode::BatteryGetPsrRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetPif) => OdpCommandCode::BatteryGetPifRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBps) => OdpCommandCode::BatteryGetBpsRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBtp) => OdpCommandCode::BatterySetBtpRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBpt) => OdpCommandCode::BatterySetBptRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBpc) => OdpCommandCode::BatteryGetBpcRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBmc) => OdpCommandCode::BatterySetBmcRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBmd) => OdpCommandCode::BatteryGetBmdRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBct) => OdpCommandCode::BatteryGetBctRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBtm) => OdpCommandCode::BatteryGetBtmRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBms) => OdpCommandCode::BatterySetBmsRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBma) => OdpCommandCode::BatterySetBmaRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetSta) => OdpCommandCode::BatteryGetStaRequest, - OdpCommand::Thermal(mptf::ThermalCmd::GetTmp) => OdpCommandCode::ThermalGetTmpRequest, - OdpCommand::Thermal(mptf::ThermalCmd::SetThrs) => OdpCommandCode::ThermalSetThrsRequest, - OdpCommand::Thermal(mptf::ThermalCmd::GetThrs) => OdpCommandCode::ThermalGetThrsRequest, - OdpCommand::Thermal(mptf::ThermalCmd::SetScp) => OdpCommandCode::ThermalSetScpRequest, - OdpCommand::Thermal(mptf::ThermalCmd::GetVar) => OdpCommandCode::ThermalGetVarRequest, - OdpCommand::Thermal(mptf::ThermalCmd::SetVar) => OdpCommandCode::ThermalSetVarRequest, - OdpCommand::Debug(debug::DebugCmd::GetMsgs) => OdpCommandCode::DebugGetMsgsRequest, - } - } -} diff --git a/embedded-service/src/ec_type/mod.rs b/embedded-service/src/ec_type/mod.rs index 970afd5a..c74b13a2 100644 --- a/embedded-service/src/ec_type/mod.rs +++ b/embedded-service/src/ec_type/mod.rs @@ -2,7 +2,6 @@ use core::mem::offset_of; pub mod message; -pub mod protocols; pub mod structure; /// Error type diff --git a/embedded-service/src/ec_type/protocols/acpi.rs b/embedded-service/src/ec_type/protocols/acpi.rs deleted file mode 100644 index fc234e0f..00000000 --- a/embedded-service/src/ec_type/protocols/acpi.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// ACPI Battery Methods -pub enum BatteryCmd { - /// Battery Information eXtended - GetBix = 1, - /// Battery Status - GetBst = 2, - /// Power Source - GetPsr = 3, - /// Power source InFormation - GetPif = 4, - /// Battery Power State - GetBps = 5, - /// Battery Trip Point - SetBtp = 6, - /// Battery Power Threshold - SetBpt = 7, - /// Battery Power Characteristics - GetBpc = 8, - /// Battery Maintenance Control - SetBmc = 9, - /// Battery Maintenance Data - GetBmd = 10, - /// Battery Charge Time - GetBct = 11, - /// Battery Time - GetBtm = 12, - /// Battery Measurement Sampling Time - SetBms = 13, - /// Battery Measurement Averaging Interval - SetBma = 14, - /// Device Status - GetSta = 15, -} diff --git a/embedded-service/src/ec_type/protocols/debug.rs b/embedded-service/src/ec_type/protocols/debug.rs deleted file mode 100644 index cf299f6a..00000000 --- a/embedded-service/src/ec_type/protocols/debug.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// ODP Specific Debug Commands -pub enum DebugCmd { - /// Get buffer of debug messages, if available. - /// Can be used to poll debug messages. - GetMsgs = 1, -} diff --git a/embedded-service/src/ec_type/protocols/mctp.rs b/embedded-service/src/ec_type/protocols/mctp.rs deleted file mode 100644 index 3f71f4cc..00000000 --- a/embedded-service/src/ec_type/protocols/mctp.rs +++ /dev/null @@ -1,1367 +0,0 @@ -#![allow(missing_docs)] - -use core::ops::{Div, Mul}; - -use embedded_batteries_async::acpi::{ - BCT_RETURN_SIZE_BYTES, BMD_RETURN_SIZE_BYTES, BPC_RETURN_SIZE_BYTES, BPS_RETURN_SIZE_BYTES, BST_RETURN_SIZE_BYTES, - BTM_RETURN_SIZE_BYTES, BatteryState, BmdCapabilityFlags, BmdStatusFlags, PSR_RETURN_SIZE_BYTES, PowerSourceState, - PowerThresholdSupport, PsrReturn, STA_RETURN_SIZE_BYTES, -}; - -use mctp_rs::{ - MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, error::MctpPacketResult, - mctp_completion_code::MctpCompletionCode, -}; - -/// Append an MCTP header to the front of a message. -/// Returns the message and its new total with the appended header. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum MctpError { - /// Header is not at least 9 bytes long. - InvalidHeaderSize, - /// Wrong destination address. - WrongDestinationAddr, - /// Invalid command code. - InvalidCommandCode, - /// Invalid byte count, encoded byte count does not match MCTP message length. - InvalidByteCount, - /// Invalid header version. Should be 1. - InvalidHeaderVersion, - /// Invalid destination endpoint - InvalidDestinationEndpoint, - /// Invalid source endpoint. - InvalidSourceEndpoint, - /// Multi message not supported. - InvalidFlags, -} - -/// Data type for MCTP message underlying data size. -pub type PayloadLen = usize; - -/// Max payload len, due to SMBUS block transaction limits. -pub const MAX_MCTP_BYTE_COUNT: usize = 69; -/// Payload len + bytes for destination target address, command code, and byte count. -pub const MAX_MCTP_PACKET_LEN: usize = MAX_MCTP_BYTE_COUNT + 3; - -fn round_up_to_nearest_mod_4(unrounded: usize) -> usize { - unrounded + (unrounded % 4) -} - -/// Decode a header from and MCTP message. -/// Returns the underlying data and its service endpoint ID and the underlying data size. -pub fn handle_mctp_header( - mctp_msg: &[u8], - data: &mut [u8], -) -> Result<(crate::comms::EndpointID, PayloadLen), MctpError> { - // assert we have at least 9 bytes, minimum - if mctp_msg.len() < 9 { - return Err(MctpError::InvalidHeaderSize); - } - - // EC is at address 2, if we have anything other than 2 reject it. - if mctp_msg[0] != 2 { - return Err(MctpError::WrongDestinationAddr); - } - - // MCTP command code is 0x0F. - if mctp_msg[1] != 0x0F { - return Err(MctpError::InvalidCommandCode); - } - - // Check the byte count is correctly formed and is not larger than the max in the spec. - if usize::from(mctp_msg[2]) > MAX_MCTP_BYTE_COUNT { - return Err(MctpError::InvalidByteCount); - } - // Some eSPI controllers behave oddly if packet sizes aren't multiples of 4, so the MCTP message is padded - // to multiples of 4. - // Byte size + header size (3) + padding to align size to multiple of 4 should equal length of message. - // Unfortunately since padding is variable, there is no way to validate byte count is truly correct. - // There is a chance that the number of valid bytes exceeds the byte count if mctp_msg.len() - // is not a multiple of 4 (and thus has padding bytes) - if ((usize::from(mctp_msg[2]) + 3) + 3).div(4).mul(4) != mctp_msg.len() { - return Err(MctpError::InvalidByteCount); - } - - // Only support header version 1. - if mctp_msg[4] != 1 { - return Err(MctpError::InvalidHeaderVersion); - } - - // Subsystems supported currently are battery (0x02), thermal (0x03), and debug (0x04). - let endpoint_id = match mctp_msg[5] { - 2 => crate::comms::EndpointID::Internal(crate::comms::Internal::Battery), - 3 => crate::comms::EndpointID::Internal(crate::comms::Internal::Thermal), - 4 => crate::comms::EndpointID::Internal(crate::comms::Internal::Debug), - _ => return Err(MctpError::InvalidDestinationEndpoint), - }; - - // Only source endpoint supported currently is host (1). - if mctp_msg[6] != 1 { - return Err(MctpError::InvalidSourceEndpoint); - } - - let som = mctp_msg[7] & (1 << 7) != 0; - let eom = mctp_msg[7] & (1 << 6) != 0; - let seq_num = (mctp_msg[7] & 0b0011_0000) >> 4; - let msg_tag = mctp_msg[7] & 0b0000_0111; - - // Verify flags - if !som || !eom || seq_num != 1 || msg_tag != 3 { - return Err(MctpError::InvalidFlags); - } - - let len = usize::from(mctp_msg[2]) - 5; - // Copy message contents without the padding to a multiple of 4 at the end. - data[..len].copy_from_slice(&mctp_msg[8..8 + len]); - - Ok((endpoint_id, len)) -} - -/// Append an MCTP header to the front of a message. -/// Returns the message and its new total with the appended header. -pub fn build_mctp_header( - data: &[u8], - data_len: usize, - src_endpoint: crate::comms::EndpointID, - start_of_msg: bool, - end_of_msg: bool, -) -> Result<([u8; MAX_MCTP_PACKET_LEN], usize), MctpError> { - let mut ret = [0u8; MAX_MCTP_PACKET_LEN]; - let padding = [0u8; 3]; - - // Host is at address 0. - ret[0] = 0; - - // MCTP command code is 0x0F. - ret[1] = 0x0F; - - // Size of the payload length + header size, without padding - ret[2] = (data_len + 5) as u8; - - // Source is EC (upper 7 bits = 0x01 | hardcoded LSB of 0x01) - ret[3] = 3; - - // Header version is 1 - ret[4] = 1; - - // Destination endpoint ID is Host (0x01) - ret[5] = 1; - - // Subsystems supported currently are battery (0x02), thermal (0x03), and debug (0x04). - match src_endpoint { - crate::comms::EndpointID::Internal(crate::comms::Internal::Battery) => ret[6] = 2, - crate::comms::EndpointID::Internal(crate::comms::Internal::Thermal) => ret[6] = 3, - crate::comms::EndpointID::Internal(crate::comms::Internal::Debug) => ret[6] = 4, - _ => return Err(MctpError::InvalidDestinationEndpoint), - } - - // Seq num 1 + Msg tag 3 - ret[7] = 0x13; - if start_of_msg { - ret[7] |= 1 << 7; - } - if end_of_msg { - ret[7] |= 1 << 6; - } - - // True packet size must be a multple of 4. Header is 8 bytes which is already a multiple of 4, - // so we don't need to include it here. - let data_len_padded = round_up_to_nearest_mod_4(data_len); - - ret[8..data_len + 8].copy_from_slice(&data[..data_len]); - - // Add padding to align to 4 bytes - ret[data_len + 8..data_len_padded + 8].copy_from_slice(&padding[..data_len_padded - data_len]); - - Ok((ret, data_len_padded + 8)) -} - -// 5 bits total -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum OdpService { - Battery = 0x01, - Thermal = 0x02, - Debug = 0x03, -} - -// 10 bits total -// TODO: Fully define offsets for subsystem, temporarily it is every 32 entries -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u16)] -pub enum OdpCommandCode { - BatteryGetBixRequest = 0x01, - BatteryGetBstRequest = 0x02, - BatteryGetPsrRequest = 0x03, - BatteryGetPifRequest = 0x04, - BatteryGetBpsRequest = 0x05, - BatterySetBtpRequest = 0x06, - BatterySetBptRequest = 0x07, - BatteryGetBpcRequest = 0x08, - BatterySetBmcRequest = 0x09, - BatteryGetBmdRequest = 0x0A, - BatteryGetBctRequest = 0x0B, - BatteryGetBtmRequest = 0x0C, - BatterySetBmsRequest = 0x0D, - BatterySetBmaRequest = 0x0E, - BatteryGetStaRequest = 0x0F, - BatteryGetBixResponse = 0x11, - BatteryGetBstResponse = 0x12, - BatteryGetPsrResponse = 0x13, - BatteryGetPifResponse = 0x14, - BatteryGetBpsResponse = 0x15, - BatterySetBtpResponse = 0x16, - BatterySetBptResponse = 0x17, - BatteryGetBpcResponse = 0x18, - BatterySetBmcResponse = 0x19, - BatteryGetBmdResponse = 0x1A, - BatteryGetBctResponse = 0x1B, - BatteryGetBtmResponse = 0x1C, - BatterySetBmsResponse = 0x1D, - BatterySetBmaResponse = 0x1E, - BatteryGetStaResponse = 0x1F, - ThermalGetTmpRequest = 0x20, - ThermalSetThrsRequest = 0x21, - ThermalGetThrsRequest = 0x22, - ThermalSetScpRequest = 0x23, - ThermalGetVarRequest = 0x24, - ThermalSetVarRequest = 0x25, - ThermalGetTmpResponse = 0x30, - ThermalSetThrsResponse = 0x31, - ThermalGetThrsResponse = 0x32, - ThermalSetScpResponse = 0x33, - ThermalGetVarResponse = 0x34, - ThermalSetVarResponse = 0x35, - DebugGetMsgsRequest = 0x40, - DebugGetMsgsResponse = 0x50, -} - -// 3 byte header -#[derive(Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OdpHeader { - pub request_bit: bool, // [23:23] (1 bit) - pub datagram_bit: bool, // [22:22] (1 bit) - pub service: OdpService, // [18:21] (4 bits) - pub command_code: OdpCommandCode, // [8:17] (10 bits) - pub completion_code: MctpCompletionCode, // [0:7] (8 bits) -} - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BixFixedStrings< - const MODEL_SIZE: usize, - const SERIAL_SIZE: usize, - const BATTERY_SIZE: usize, - const OEM_SIZE: usize, -> { - /// Revision of the BIX structure. Current revision is 1. - pub revision: u32, - /// Unit used for capacity and rate values. - pub power_unit: embedded_batteries_async::acpi::PowerUnit, - /// Design capacity of the battery (in mWh or mAh). - pub design_capacity: u32, - /// Last full charge capacity (in mWh or mAh). - pub last_full_charge_capacity: u32, - /// Battery technology type. - pub battery_technology: embedded_batteries_async::acpi::BatteryTechnology, - /// Design voltage (in mV). - pub design_voltage: u32, - /// Warning capacity threshold (in mWh or mAh). - pub design_cap_of_warning: u32, - /// Low capacity threshold (in mWh or mAh). - pub design_cap_of_low: u32, - /// Number of charge/discharge cycles. - pub cycle_count: u32, - /// Measurement accuracy in thousandths of a percent (e.g., 80000 = 80.000%). - pub measurement_accuracy: u32, - /// Maximum supported sampling time (in ms). - pub max_sampling_time: u32, - /// Minimum supported sampling time (in ms). - pub min_sampling_time: u32, - /// Maximum supported averaging interval (in ms). - pub max_averaging_interval: u32, - /// Minimum supported averaging interval (in ms). - pub min_averaging_interval: u32, - /// Capacity granularity between low and warning (in mWh or mAh). - pub battery_capacity_granularity_1: u32, - /// Capacity granularity between warning and full (in mWh or mAh). - pub battery_capacity_granularity_2: u32, - /// OEM-specific model number (ASCIIZ). - pub model_number: [u8; MODEL_SIZE], - /// OEM-specific serial number (ASCIIZ). - pub serial_number: [u8; SERIAL_SIZE], - /// OEM-specific battery type (ASCIIZ). - pub battery_type: [u8; BATTERY_SIZE], - /// OEM-specific information (ASCIIZ). - pub oem_info: [u8; OEM_SIZE], - /// Battery swapping capability. - pub battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Error type when serializing ODP return types with fixed size strings. -pub enum OdpSerializeErr { - /// Input slice is too small to encapsulate all the fields. - InputSliceTooSmall, -} - -impl - BixFixedStrings -{ - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), OdpSerializeErr> { - const MODEL_NUM_START_IDX: usize = 64; - let model_num_end_idx: usize = MODEL_NUM_START_IDX + MODEL_SIZE; - let serial_num_start_idx = model_num_end_idx; - let serial_num_end_idx = serial_num_start_idx + SERIAL_SIZE; - let battery_type_start_idx = serial_num_end_idx; - let battery_type_end_idx = battery_type_start_idx + BATTERY_SIZE; - let oem_info_start_idx = battery_type_end_idx; - let oem_info_end_idx = oem_info_start_idx + OEM_SIZE; - - if dst_slice.len() < oem_info_end_idx { - return Err(OdpSerializeErr::InputSliceTooSmall); - } - - dst_slice[..4].copy_from_slice(&u32::to_le_bytes(self.revision)); - dst_slice[4..8].copy_from_slice(&u32::to_le_bytes(self.power_unit.into())); - dst_slice[8..12].copy_from_slice(&u32::to_le_bytes(self.design_capacity)); - dst_slice[12..16].copy_from_slice(&u32::to_le_bytes(self.last_full_charge_capacity)); - dst_slice[16..20].copy_from_slice(&u32::to_le_bytes(self.battery_technology.into())); - dst_slice[20..24].copy_from_slice(&u32::to_le_bytes(self.design_voltage)); - dst_slice[24..28].copy_from_slice(&u32::to_le_bytes(self.design_cap_of_warning)); - dst_slice[28..32].copy_from_slice(&u32::to_le_bytes(self.design_cap_of_low)); - dst_slice[32..36].copy_from_slice(&u32::to_le_bytes(self.cycle_count)); - dst_slice[36..40].copy_from_slice(&u32::to_le_bytes(self.measurement_accuracy)); - dst_slice[40..44].copy_from_slice(&u32::to_le_bytes(self.max_sampling_time)); - dst_slice[44..48].copy_from_slice(&u32::to_le_bytes(self.min_sampling_time)); - dst_slice[48..52].copy_from_slice(&u32::to_le_bytes(self.max_averaging_interval)); - dst_slice[52..56].copy_from_slice(&u32::to_le_bytes(self.min_averaging_interval)); - dst_slice[56..60].copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_1)); - dst_slice[60..64].copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_2)); - dst_slice[MODEL_NUM_START_IDX..model_num_end_idx].copy_from_slice(&self.model_number); - dst_slice[serial_num_start_idx..serial_num_end_idx].copy_from_slice(&self.serial_number); - dst_slice[battery_type_start_idx..battery_type_end_idx].copy_from_slice(&self.battery_type); - dst_slice[oem_info_start_idx..oem_info_end_idx].copy_from_slice(&self.oem_info); - dst_slice[oem_info_end_idx..oem_info_end_idx + 4] - .copy_from_slice(&u32::to_le_bytes(self.battery_swapping_capability.into())); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PifFixedStrings { - /// Bitfield describing the state and characteristics of the power source. - pub power_source_state: embedded_batteries_async::acpi::PowerSourceState, - /// Maximum rated output power in milliwatts (mW). - /// - /// 0xFFFFFFFF indicates the value is unavailable. - pub max_output_power: u32, - /// Maximum rated input power in milliwatts (mW). - /// - /// 0xFFFFFFFF indicates the value is unavailable. - pub max_input_power: u32, - /// OEM-specific model number (ASCIIZ). Empty string if not supported. - pub model_number: [u8; MODEL_SIZE], - /// OEM-specific serial number (ASCIIZ). Empty string if not supported. - pub serial_number: [u8; SERIAL_SIZE], - /// OEM-specific information (ASCIIZ). Empty string if not supported. - pub oem_info: [u8; OEM_SIZE], -} - -impl - PifFixedStrings -{ - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), OdpSerializeErr> { - const MODEL_NUM_START_IDX: usize = 12; - let model_num_end_idx: usize = MODEL_NUM_START_IDX + MODEL_SIZE; - let serial_num_start_idx = model_num_end_idx; - let serial_num_end_idx = serial_num_start_idx + SERIAL_SIZE; - let oem_info_start_idx = serial_num_end_idx; - let oem_info_end_idx = oem_info_start_idx + OEM_SIZE; - - if dst_slice.len() < oem_info_end_idx { - return Err(OdpSerializeErr::InputSliceTooSmall); - } - - dst_slice[..4].copy_from_slice(&u32::to_le_bytes(self.power_source_state.bits())); - dst_slice[4..8].copy_from_slice(&u32::to_le_bytes(self.max_output_power)); - dst_slice[8..12].copy_from_slice(&u32::to_le_bytes(self.max_input_power)); - dst_slice[MODEL_NUM_START_IDX..model_num_end_idx].copy_from_slice(&self.model_number); - dst_slice[serial_num_start_idx..serial_num_end_idx].copy_from_slice(&self.serial_number); - dst_slice[oem_info_start_idx..oem_info_end_idx].copy_from_slice(&self.oem_info); - Ok(()) - } -} - -/// Standard 32-bit DWORD -pub type Dword = u32; - -/// 16-bit variable length -pub type VarLen = u16; - -/// Instance ID -pub type InstanceId = u8; - -/// Time in milliseconds -pub type Milliseconds = Dword; - -/// MPTF expects temperatures in tenth Kelvins -pub type DeciKelvin = Dword; - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Odp< - const BIX_MODEL_SIZE: usize, - const BIX_SERIAL_SIZE: usize, - const BIX_BATTERY_SIZE: usize, - const BIX_OEM_SIZE: usize, - const PIF_MODEL_SIZE: usize, - const PIF_SERIAL_SIZE: usize, - const PIF_OEM_SIZE: usize, - const DEBUG_BUF_SIZE: usize, -> { - BatteryGetBixRequest { - battery_id: u8, - }, - BatteryGetBstRequest { - battery_id: u8, - }, - BatteryGetPsrRequest { - battery_id: u8, - }, - BatteryGetPifRequest { - battery_id: u8, - }, - BatteryGetBpsRequest { - battery_id: u8, - }, - BatterySetBtpRequest { - battery_id: u8, - btp: embedded_batteries_async::acpi::Btp, - }, - BatterySetBptRequest { - battery_id: u8, - bpt: embedded_batteries_async::acpi::Bpt, - }, - BatteryGetBpcRequest { - battery_id: u8, - }, - BatterySetBmcRequest { - battery_id: u8, - bmc: embedded_batteries_async::acpi::Bmc, - }, - BatteryGetBmdRequest { - battery_id: u8, - }, - BatteryGetBctRequest { - battery_id: u8, - bct: embedded_batteries_async::acpi::Bct, - }, - BatteryGetBtmRequest { - battery_id: u8, - btm: embedded_batteries_async::acpi::Btm, - }, - BatterySetBmsRequest { - battery_id: u8, - bms: embedded_batteries_async::acpi::Bms, - }, - BatterySetBmaRequest { - battery_id: u8, - bma: embedded_batteries_async::acpi::Bma, - }, - BatteryGetStaRequest { - battery_id: u8, - }, - BatteryGetBixResponse { - bix: BixFixedStrings, - }, - BatteryGetBstResponse { - bst: embedded_batteries_async::acpi::BstReturn, - }, - BatteryGetPsrResponse { - psr: embedded_batteries_async::acpi::PsrReturn, - }, - BatteryGetPifResponse { - pif: PifFixedStrings, - }, - BatteryGetBpsResponse { - bps: embedded_batteries_async::acpi::Bps, - }, - BatterySetBtpResponse {}, - BatterySetBptResponse {}, - BatteryGetBpcResponse { - bpc: embedded_batteries_async::acpi::Bpc, - }, - BatterySetBmcResponse {}, - BatteryGetBmdResponse { - bmd: embedded_batteries_async::acpi::Bmd, - }, - BatteryGetBctResponse { - bct_response: embedded_batteries_async::acpi::BctReturnResult, - }, - BatteryGetBtmResponse { - btm_response: embedded_batteries_async::acpi::BtmReturnResult, - }, - BatterySetBmsResponse { - status: Dword, - }, - BatterySetBmaResponse { - status: Dword, - }, - BatteryGetStaResponse { - sta: embedded_batteries_async::acpi::StaReturn, - }, - - ThermalGetTmpRequest { - instance_id: u8, - }, - ThermalSetThrsRequest { - instance_id: u8, - timeout: Milliseconds, - low: DeciKelvin, - high: DeciKelvin, - }, - ThermalGetThrsRequest { - instance_id: u8, - }, - ThermalSetScpRequest { - instance_id: u8, - policy_id: Dword, - acoustic_lim: Dword, - power_lim: Dword, - }, - ThermalGetVarRequest { - instance_id: u8, - len: VarLen, - var_uuid: uuid::Bytes, - }, - ThermalSetVarRequest { - instance_id: u8, - len: VarLen, - var_uuid: uuid::Bytes, - set_var: Dword, - }, - DebugGetMsgsRequest, - - ThermalGetTmpResponse { - temperature: DeciKelvin, - }, - ThermalSetThrsResponse { - status: Dword, - }, - ThermalGetThrsResponse { - status: Dword, - timeout: Milliseconds, - low: DeciKelvin, - high: DeciKelvin, - }, - ThermalSetScpResponse { - status: Dword, - }, - ThermalGetVarResponse { - status: Dword, - val: Dword, - }, - ThermalSetVarResponse { - status: Dword, - }, - DebugGetMsgsResponse { - debug_buf: [u8; DEBUG_BUF_SIZE], - }, - ErrorResponse {}, -} - -impl MctpMessageHeaderTrait for OdpHeader { - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - check_header_length(buffer)?; - let command_code: u16 = self.command_code as u16; - buffer[0] = (self.request_bit as u8) << 7 - | (self.datagram_bit as u8) << 6 - | ((self.service as u8) & 0b0000_1111) << 2 - | ((command_code >> 8) as u8 & 0b0000_0011); - buffer[1] = (command_code & 0x00FF) as u8; - buffer[2] = self.completion_code.into(); - Ok(3) - } - - fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { - check_header_length(buffer)?; - let request_bit = buffer[0] & 0b1000_0000 != 0; - let datagram_bit = buffer[0] & 0b0100_0000 != 0; - let service = (buffer[0] & 0b0011_1100) >> 2; - let command_code = ((buffer[0] & 0b0000_0011) as u16) << 8 | (buffer[1] as u16); - - let completion_code = buffer[2] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("invalid completion code"))?; - let service = service - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service"))?; - let command_code = command_code - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp command code"))?; - - Ok(( - OdpHeader { - request_bit, - datagram_bit, - service, - command_code, - completion_code, - }, - &buffer[3..], - )) - } -} - -impl< - const BIX_MODEL_SIZE: usize, - const BIX_SERIAL_SIZE: usize, - const BIX_BATTERY_SIZE: usize, - const BIX_OEM_SIZE: usize, - const PIF_MODEL_SIZE: usize, - const PIF_SERIAL_SIZE: usize, - const PIF_OEM_SIZE: usize, - const DEBUG_BUF_SIZE: usize, -> MctpMessageTrait<'_> - for Odp< - BIX_MODEL_SIZE, - BIX_SERIAL_SIZE, - BIX_BATTERY_SIZE, - BIX_OEM_SIZE, - PIF_MODEL_SIZE, - PIF_SERIAL_SIZE, - PIF_OEM_SIZE, - DEBUG_BUF_SIZE, - > -{ - const MESSAGE_TYPE: u8 = 0x7D; - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - Self::BatteryGetBixRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetBstRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetPsrRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetPifRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetBpsRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatterySetBtpRequest { battery_id, btp } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(btp.trip_point)); - - Ok(5) - } - Self::BatterySetBptRequest { battery_id, bpt } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bpt.revision)); - buffer[5..9].copy_from_slice(&u32::to_le_bytes(match bpt.threshold_id { - embedded_batteries_async::acpi::ThresholdId::ClearAll => 0, - embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower => 1, - embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower => 2, - })); - buffer[9..13].copy_from_slice(&u32::to_le_bytes(bpt.threshold_value)); - - Ok(13) - } - Self::BatteryGetBpcRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatterySetBmcRequest { battery_id, bmc } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bmc.maintenance_control_flags.bits())); - - Ok(5) - } - Self::BatteryGetBmdRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetBctRequest { battery_id, bct } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bct.charge_level_percent)); - - Ok(5) - } - Self::BatteryGetBtmRequest { battery_id, btm } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(btm.discharge_rate)); - - Ok(5) - } - Self::BatterySetBmsRequest { battery_id, bms } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bms.sampling_time_ms)); - - Ok(5) - } - Self::BatterySetBmaRequest { battery_id, bma } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bma.averaging_interval_ms)); - - Ok(5) - } - Self::BatteryGetStaRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::ThermalGetTmpRequest { instance_id } => write_to_buffer(buffer, [instance_id]), - Self::ThermalSetThrsRequest { - instance_id, - timeout, - low, - high, - } => { - buffer[0] = instance_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(timeout)); - buffer[5..9].copy_from_slice(&u32::to_le_bytes(low)); - buffer[9..13].copy_from_slice(&u32::to_le_bytes(high)); - - Ok(13) - } - Self::ThermalGetThrsRequest { instance_id } => write_to_buffer(buffer, [instance_id]), - Self::ThermalSetScpRequest { - instance_id, - policy_id, - acoustic_lim, - power_lim, - } => { - buffer[0] = instance_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(policy_id)); - buffer[5..9].copy_from_slice(&u32::to_le_bytes(acoustic_lim)); - buffer[9..13].copy_from_slice(&u32::to_le_bytes(power_lim)); - - Ok(13) - } - Self::ThermalGetVarRequest { - instance_id, - len, - var_uuid, - } => { - buffer[0] = instance_id; - buffer[1..3].copy_from_slice(&u16::to_le_bytes(len)); - buffer[3..19].copy_from_slice(&var_uuid); - - Ok(19) - } - Self::ThermalSetVarRequest { - instance_id, - len, - var_uuid, - set_var, - } => { - buffer[0] = instance_id; - buffer[1..3].copy_from_slice(&u16::to_le_bytes(len)); - buffer[3..19].copy_from_slice(&var_uuid); - buffer[19..23].copy_from_slice(&u32::to_le_bytes(set_var)); - - Ok(23) - } - Self::DebugGetMsgsRequest => Ok(0), - Self::BatteryGetBixResponse { bix } => bix - .to_bytes(buffer) - .map(|_| 100) - .map_err(|_| mctp_rs::MctpPacketError::HeaderParseError("Bix parse failed")), - Self::BatteryGetBstResponse { bst } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bst.battery_state.bits())); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bst.battery_remaining_capacity)); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bst.battery_present_rate)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bst.battery_present_voltage)); - - Ok(BST_RETURN_SIZE_BYTES) - } - Self::BatteryGetPsrResponse { psr } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(psr.power_source.into())); - - Ok(PSR_RETURN_SIZE_BYTES) - } - Self::BatteryGetPifResponse { pif } => pif - .to_bytes(buffer) - .map(|_| 36) - .map_err(|_| mctp_rs::MctpPacketError::HeaderParseError("Pif parse failed")), - Self::BatteryGetBpsResponse { bps } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bps.revision)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_level)); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_period)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_level)); - buffer[16..20].copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_period)); - - Ok(BPS_RETURN_SIZE_BYTES) - } - Self::BatterySetBtpResponse {} => Ok(0), - Self::BatterySetBptResponse {} => Ok(0), - Self::BatteryGetBpcResponse { bpc } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bpc.revision)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bpc.power_threshold_support.bits())); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bpc.max_instantaneous_peak_power_threshold)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bpc.max_sustainable_peak_power_threshold)); - - Ok(BPC_RETURN_SIZE_BYTES) - } - Self::BatterySetBmcResponse {} => Ok(0), - Self::BatteryGetBmdResponse { bmd } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bmd.status_flags.bits())); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bmd.capability_flags.bits())); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bmd.recalibrate_count)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bmd.quick_recalibrate_time)); - buffer[16..20].copy_from_slice(&u32::to_le_bytes(bmd.slow_recalibrate_time)); - - Ok(BMD_RETURN_SIZE_BYTES) - } - Self::BatteryGetBctResponse { bct_response } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bct_response.into())); - - Ok(BCT_RETURN_SIZE_BYTES) - } - Self::BatteryGetBtmResponse { btm_response } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(btm_response.into())); - - Ok(BTM_RETURN_SIZE_BYTES) - } - Self::BatterySetBmsResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::BatterySetBmaResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::BatteryGetStaResponse { sta } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(sta.bits())); - - Ok(STA_RETURN_SIZE_BYTES) - } - Self::ThermalGetTmpResponse { temperature } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(temperature)); - - Ok(4) - } - Self::ThermalSetThrsResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::ThermalGetThrsResponse { - status, - timeout, - low, - high, - } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(timeout)); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(low)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(high)); - - Ok(16) - } - Self::ThermalSetScpResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::ThermalGetVarResponse { status, val } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(val)); - - Ok(8) - } - Self::ThermalSetVarResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::DebugGetMsgsResponse { debug_buf } => { - buffer[..debug_buf.len()].copy_from_slice(&debug_buf); - Ok(debug_buf.len()) - } - Self::ErrorResponse {} => Ok(0), - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.command_code { - OdpCommandCode::BatteryGetBixRequest => Self::BatteryGetBixRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetBstRequest => Self::BatteryGetBstRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetPsrRequest => Self::BatteryGetPsrRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetPifRequest => Self::BatteryGetPifRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetBpsRequest => Self::BatteryGetBpsRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatterySetBtpRequest => Self::BatterySetBtpRequest { - battery_id: safe_get_u8(buffer, 0)?, - btp: embedded_batteries_async::acpi::Btp { - trip_point: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatterySetBptRequest => Self::BatterySetBptRequest { - battery_id: safe_get_u8(buffer, 0)?, - bpt: embedded_batteries_async::acpi::Bpt { - revision: safe_get_dword(buffer, 1)?, - threshold_id: match safe_get_dword(buffer, 5)? { - 0 => embedded_batteries_async::acpi::ThresholdId::ClearAll, - 1 => embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower, - 2 => embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower, - _ => { - return Err(MctpPacketError::HeaderParseError("Unsupported threshold id")); - } - }, - threshold_value: safe_get_dword(buffer, 9)?, - }, - }, - OdpCommandCode::BatteryGetBpcRequest => Self::BatteryGetBpcRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatterySetBmcRequest => Self::BatterySetBmcRequest { - battery_id: safe_get_u8(buffer, 0)?, - bmc: embedded_batteries_async::acpi::Bmc { - maintenance_control_flags: embedded_batteries_async::acpi::BmcControlFlags::from_bits_retain( - safe_get_dword(buffer, 1)?, - ), - }, - }, - OdpCommandCode::BatteryGetBmdRequest => Self::BatteryGetBmdRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetBctRequest => Self::BatteryGetBctRequest { - battery_id: safe_get_u8(buffer, 0)?, - bct: embedded_batteries_async::acpi::Bct { - charge_level_percent: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatteryGetBtmRequest => Self::BatteryGetBtmRequest { - battery_id: safe_get_u8(buffer, 0)?, - btm: embedded_batteries_async::acpi::Btm { - discharge_rate: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatterySetBmsRequest => Self::BatterySetBmsRequest { - battery_id: safe_get_u8(buffer, 0)?, - bms: embedded_batteries_async::acpi::Bms { - sampling_time_ms: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatterySetBmaRequest => Self::BatterySetBmaRequest { - battery_id: safe_get_u8(buffer, 0)?, - bma: embedded_batteries_async::acpi::Bma { - averaging_interval_ms: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatteryGetStaRequest => Self::BatteryGetStaRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::ThermalGetTmpRequest => Self::ThermalGetTmpRequest { - instance_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::ThermalSetThrsRequest => Self::ThermalSetThrsRequest { - instance_id: safe_get_u8(buffer, 0)?, - timeout: safe_get_dword(buffer, 1)?, - low: safe_get_dword(buffer, 5)?, - high: safe_get_dword(buffer, 9)?, - }, - OdpCommandCode::ThermalGetThrsRequest => Self::ThermalGetThrsRequest { - instance_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::ThermalSetScpRequest => Self::ThermalSetScpRequest { - instance_id: safe_get_u8(buffer, 0)?, - policy_id: safe_get_dword(buffer, 1)?, - acoustic_lim: safe_get_dword(buffer, 5)?, - power_lim: safe_get_dword(buffer, 9)?, - }, - OdpCommandCode::ThermalGetVarRequest => Self::ThermalGetVarRequest { - instance_id: safe_get_u8(buffer, 0)?, - len: safe_get_u16(buffer, 1)?, - var_uuid: safe_get_uuid(buffer, 3)?, - }, - OdpCommandCode::ThermalSetVarRequest => Self::ThermalSetVarRequest { - instance_id: safe_get_u8(buffer, 0)?, - len: safe_get_u16(buffer, 1)?, - var_uuid: safe_get_uuid(buffer, 3)?, - set_var: safe_get_dword(buffer, 19)?, - }, - OdpCommandCode::DebugGetMsgsRequest => Self::DebugGetMsgsRequest, - OdpCommandCode::BatteryGetBixResponse => Self::BatteryGetBixResponse { - bix: BixFixedStrings { - revision: safe_get_dword(buffer, 0)?, - power_unit: match safe_get_dword(buffer, 4)? { - 0 => embedded_batteries_async::acpi::PowerUnit::MilliWatts, - 1 => embedded_batteries_async::acpi::PowerUnit::MilliAmps, - _ => { - return Err(MctpPacketError::HeaderParseError("BIX deserialize failed")); - } - }, - design_capacity: safe_get_dword(buffer, 8)?, - last_full_charge_capacity: safe_get_dword(buffer, 12)?, - battery_technology: match safe_get_dword(buffer, 16)? { - 0 => embedded_batteries_async::acpi::BatteryTechnology::Primary, - 1 => embedded_batteries_async::acpi::BatteryTechnology::Secondary, - _ => { - return Err(MctpPacketError::HeaderParseError("BIX deserialize failed")); - } - }, - design_voltage: safe_get_dword(buffer, 20)?, - design_cap_of_warning: safe_get_dword(buffer, 24)?, - design_cap_of_low: safe_get_dword(buffer, 28)?, - cycle_count: safe_get_dword(buffer, 32)?, - measurement_accuracy: safe_get_dword(buffer, 36)?, - max_sampling_time: safe_get_dword(buffer, 40)?, - min_sampling_time: safe_get_dword(buffer, 44)?, - max_averaging_interval: safe_get_dword(buffer, 48)?, - min_averaging_interval: safe_get_dword(buffer, 52)?, - battery_capacity_granularity_1: safe_get_dword(buffer, 56)?, - battery_capacity_granularity_2: safe_get_dword(buffer, 60)?, - model_number: buffer[64..72] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - serial_number: buffer[72..80] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - battery_type: buffer[80..88] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - oem_info: buffer[88..96] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - battery_swapping_capability: match safe_get_dword(buffer, 100)? { - 0 => embedded_batteries_async::acpi::BatterySwapCapability::NonSwappable, - 1 => embedded_batteries_async::acpi::BatterySwapCapability::ColdSwappable, - 2 => embedded_batteries_async::acpi::BatterySwapCapability::HotSwappable, - _ => { - return Err(MctpPacketError::HeaderParseError("BIX deserialize failed")); - } - }, - }, - }, - OdpCommandCode::BatteryGetBstResponse => Self::BatteryGetBstResponse { - bst: embedded_batteries_async::acpi::BstReturn { - battery_state: BatteryState::from_bits_retain(safe_get_dword(buffer, 0)?), - battery_remaining_capacity: safe_get_dword(buffer, 4)?, - battery_present_rate: safe_get_dword(buffer, 8)?, - battery_present_voltage: safe_get_dword(buffer, 12)?, - }, - }, - OdpCommandCode::BatteryGetPsrResponse => Self::BatteryGetPsrResponse { - psr: PsrReturn { - power_source: match safe_get_dword(buffer, 0)? { - 0 => embedded_batteries_async::acpi::PowerSource::Offline, - 1 => embedded_batteries_async::acpi::PowerSource::Online, - _ => { - return Err(MctpPacketError::HeaderParseError("PSR deserialize failed")); - } - }, - }, - }, - OdpCommandCode::BatteryGetPifResponse => Self::BatteryGetPifResponse { - pif: PifFixedStrings { - power_source_state: PowerSourceState::from_bits_retain(safe_get_dword(buffer, 0)?), - max_output_power: safe_get_dword(buffer, 4)?, - max_input_power: safe_get_dword(buffer, 8)?, - model_number: buffer[12..20] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Pif deserialize failed"))?, - serial_number: buffer[20..28] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Pif deserialize failed"))?, - oem_info: buffer[28..36] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Pif deserialize failed"))?, - }, - }, - OdpCommandCode::BatteryGetBpsResponse => Self::BatteryGetBpsResponse { - bps: embedded_batteries_async::acpi::Bps { - revision: safe_get_dword(buffer, 0)?, - instantaneous_peak_power_level: safe_get_dword(buffer, 4)?, - instantaneous_peak_power_period: safe_get_dword(buffer, 8)?, - sustainable_peak_power_level: safe_get_dword(buffer, 12)?, - sustainable_peak_power_period: safe_get_dword(buffer, 16)?, - }, - }, - OdpCommandCode::BatterySetBtpResponse => Self::BatterySetBtpResponse {}, - OdpCommandCode::BatterySetBptResponse => Self::BatterySetBptResponse {}, - OdpCommandCode::BatteryGetBpcResponse => Self::BatteryGetBpcResponse { - bpc: embedded_batteries_async::acpi::Bpc { - revision: safe_get_dword(buffer, 0)?, - power_threshold_support: PowerThresholdSupport::from_bits_retain(safe_get_dword(buffer, 4)?), - max_instantaneous_peak_power_threshold: safe_get_dword(buffer, 8)?, - max_sustainable_peak_power_threshold: safe_get_dword(buffer, 12)?, - }, - }, - OdpCommandCode::BatterySetBmcResponse => Self::BatterySetBmcResponse {}, - OdpCommandCode::BatteryGetBmdResponse => Self::BatteryGetBmdResponse { - bmd: embedded_batteries_async::acpi::Bmd { - status_flags: BmdStatusFlags::from_bits_retain(safe_get_dword(buffer, 0)?), - capability_flags: BmdCapabilityFlags::from_bits_retain(safe_get_dword(buffer, 4)?), - recalibrate_count: safe_get_dword(buffer, 8)?, - quick_recalibrate_time: safe_get_dword(buffer, 12)?, - slow_recalibrate_time: safe_get_dword(buffer, 16)?, - }, - }, - OdpCommandCode::BatteryGetBctResponse => Self::BatteryGetBctResponse { - bct_response: embedded_batteries_async::acpi::BctReturnResult::from(safe_get_dword(buffer, 0)?), - }, - OdpCommandCode::BatteryGetBtmResponse => Self::BatteryGetBtmResponse { - btm_response: embedded_batteries_async::acpi::BtmReturnResult::from(safe_get_dword(buffer, 0)?), - }, - OdpCommandCode::BatterySetBmsResponse => Self::BatterySetBmsResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::BatterySetBmaResponse => Self::BatterySetBmaResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::BatteryGetStaResponse => Self::BatteryGetStaResponse { - sta: embedded_batteries_async::acpi::StaReturn::from_bits_retain(safe_get_dword(buffer, 0)?), - }, - OdpCommandCode::ThermalGetTmpResponse => Self::ThermalGetTmpResponse { - temperature: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::ThermalSetThrsResponse => Self::ThermalSetThrsResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::ThermalGetThrsResponse => Self::ThermalGetThrsResponse { - status: safe_get_dword(buffer, 0)?, - timeout: safe_get_dword(buffer, 4)?, - low: safe_get_dword(buffer, 8)?, - high: safe_get_dword(buffer, 12)?, - }, - OdpCommandCode::ThermalSetScpResponse => Self::ThermalSetScpResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::ThermalGetVarResponse => Self::ThermalGetVarResponse { - status: safe_get_dword(buffer, 0)?, - val: safe_get_dword(buffer, 4)?, - }, - OdpCommandCode::ThermalSetVarResponse => Self::ThermalSetVarResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::DebugGetMsgsResponse => Self::DebugGetMsgsResponse { - debug_buf: buffer[..DEBUG_BUF_SIZE] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("MCTP buf not large enough"))?, - }, - }) - } -} - -fn safe_get_u8(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 1 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - Ok(buffer[index]) -} - -fn safe_get_u16(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 2 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - // Safe from panics as length is verified above. - Ok(u16::from_le_bytes(buffer[index..index + 2].try_into().unwrap())) -} - -fn safe_get_dword(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 4 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - // Safe from panics as length is verified above. - Ok(u32::from_le_bytes(buffer[index..index + 4].try_into().unwrap())) -} - -fn safe_get_uuid(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 16 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - // Safe from panics as length is verified above. - Ok(buffer[index..index + 16].try_into().unwrap()) -} - -fn write_to_buffer(buffer: &mut [u8], data: [u8; N]) -> MctpPacketResult { - if buffer.len() < N { - return Err(MctpPacketError::SerializeError("buffer too small for odp message")); - } - buffer[..N].copy_from_slice(&data); - Ok(N) -} - -fn check_header_length(buffer: &[u8]) -> MctpPacketResult<(), M> { - if buffer.len() < 3 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp header")); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[allow(dead_code)] - mod test_util { - use mctp_rs::{MctpMedium, MctpMediumFrame, MctpPacketError, error::MctpPacketResult}; - - #[derive(Debug, PartialEq, Eq, Copy, Clone)] - pub struct TestMedium { - header: &'static [u8], - trailer: &'static [u8], - mtu: usize, - } - impl TestMedium { - pub fn new() -> Self { - Self { - header: &[], - trailer: &[], - mtu: 32, - } - } - pub fn with_headers(mut self, header: &'static [u8], trailer: &'static [u8]) -> Self { - self.header = header; - self.trailer = trailer; - self - } - pub fn with_mtu(mut self, mtu: usize) -> Self { - self.mtu = mtu; - self - } - } - - #[derive(Debug, PartialEq, Eq, Copy, Clone)] - pub struct TestMediumFrame(usize); - - impl MctpMedium for TestMedium { - type Frame = TestMediumFrame; - type Error = &'static str; - type ReplyContext = (); - - fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { - let packet_len = packet.len(); - - // check that header / trailer is present and correct - if packet.len() < self.header.len() + self.trailer.len() { - return Err(MctpPacketError::MediumError("packet too short")); - } - if packet[0..self.header.len()] != *self.header { - return Err(MctpPacketError::MediumError("header mismatch")); - } - if packet[packet_len - self.trailer.len()..packet_len] != *self.trailer { - return Err(MctpPacketError::MediumError("trailer mismatch")); - } - - let packet = &packet[self.header.len()..packet_len - self.trailer.len()]; - Ok((TestMediumFrame(packet_len), packet)) - } - fn max_message_body_size(&self) -> usize { - self.mtu - } - fn serialize<'buf, F>( - &self, - _: Self::ReplyContext, - buffer: &'buf mut [u8], - message_writer: F, - ) -> MctpPacketResult<&'buf [u8], Self> - where - F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, - { - let header_len = self.header.len(); - let trailer_len = self.trailer.len(); - - // Ensure buffer can fit at least headers and trailers - if buffer.len() < header_len + trailer_len { - return Err(MctpPacketError::MediumError("Buffer too small for headers")); - } - - // Calculate available space for message (respecting MTU) - let max_packet_size = self.mtu.min(buffer.len()); - if max_packet_size < header_len + trailer_len { - return Err(MctpPacketError::MediumError("MTU too small for headers")); - } - let max_message_size = max_packet_size - header_len - trailer_len; - - buffer[0..header_len].copy_from_slice(self.header); - let size = message_writer(&mut buffer[header_len..header_len + max_message_size])?; - let len = header_len + size; - buffer[len..len + trailer_len].copy_from_slice(self.trailer); - Ok(&buffer[..len + trailer_len]) - } - } - - impl MctpMediumFrame for TestMediumFrame { - fn packet_size(&self) -> usize { - self.0 - } - fn reply_context(&self) -> ::ReplyContext {} - } - } - - use test_util::TestMedium; - - #[rstest::rstest] - #[case(OdpHeader { - request_bit: true, - datagram_bit: false, - service: OdpService::Battery, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::Success - })] - #[case( - OdpHeader { - request_bit: false, - datagram_bit: true, - service: OdpService::Debug, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::ErrorUnsupportedCmd - })] - #[case( - OdpHeader { - request_bit: true, - datagram_bit: true, - service: OdpService::Battery, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::CommandSpecific(0x80) - })] - #[case( - OdpHeader { - request_bit: false, - datagram_bit: false, - service: OdpService::Debug, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::Success - })] - fn odp_header_roundtrip_happy_path(#[case] header: OdpHeader) { - let mut buf = [0u8; 3]; - let size = header.serialize::(&mut buf).unwrap(); - assert_eq!(size, 3); - - let (parsed, rest) = OdpHeader::deserialize::(&buf).unwrap(); - assert_eq!(parsed, header); - assert_eq!(rest.len(), 0); - } - - #[test] - #[allow(clippy::panic)] - fn odp_header_error_on_short_buffer() { - let header = OdpHeader { - request_bit: false, - datagram_bit: false, - service: OdpService::Battery, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::Success, - }; - - // Serialize works with correct buffer - let mut buf_ok = [0u8; 3]; - header.serialize::(&mut buf_ok).unwrap(); - - // Deserialize should fail on too-small buffer - let err = OdpHeader::deserialize::(&buf_ok[..2]).unwrap_err(); - match err { - MctpPacketError::HeaderParseError(msg) => { - assert_eq!(msg, "buffer too small for odp header") - } - other => panic!("unexpected error: {:?}", other), - } - } -} diff --git a/embedded-service/src/ec_type/protocols/mod.rs b/embedded-service/src/ec_type/protocols/mod.rs deleted file mode 100644 index aafb1e63..00000000 --- a/embedded-service/src/ec_type/protocols/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Primitive serde and helper fns for protocols used by the EC. - -/// ACPI (Advanced Configuration and Power Interface). -pub mod acpi; - -/// ODP Specific Debug Protocol. -pub mod debug; - -/// MCTP (Management Component Transport Protocol). -#[allow(clippy::indexing_slicing)] //panic safety: no external client, not being deployed -#[allow(clippy::unwrap_used)] //panic safety: no external client, not being deployed -pub mod mctp; - -/// MTPF (Modern Thermal and Power Framework). -pub mod mptf; diff --git a/embedded-service/src/ec_type/protocols/mptf.rs b/embedded-service/src/ec_type/protocols/mptf.rs deleted file mode 100644 index 53331185..00000000 --- a/embedded-service/src/ec_type/protocols/mptf.rs +++ /dev/null @@ -1,17 +0,0 @@ -/// Standard MPTF requests expected by the thermal subsystem -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ThermalCmd { - /// EC_THM_GET_TMP = 0x1 - GetTmp = 1, - /// EC_THM_SET_THRS = 0x2 - SetThrs = 2, - /// EC_THM_GET_THRS = 0x3 - GetThrs = 3, - /// EC_THM_SET_SCP = 0x4 - SetScp = 4, - /// EC_THM_GET_VAR = 0x5 - GetVar = 5, - /// EC_THM_SET_VAR = 0x6 - SetVar = 6, -} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 4ce50d61..e43b6348 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -23,6 +23,7 @@ pub mod init; pub mod ipc; pub mod keyboard; pub mod power; +pub mod relay; pub mod sync; pub mod type_c; diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs new file mode 100644 index 00000000..6531fcf6 --- /dev/null +++ b/embedded-service/src/relay/mod.rs @@ -0,0 +1,97 @@ +//! 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 SerializableResponse - 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 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 + type SuccessType: SerializableMessage; + + /// The type of the response when the operation being responsed to failed + type ErrorType: SerializableMessage; + + /// Returns true if the response 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. + /// Discriminants can be reused for success and error messages. + fn discriminant(&self) -> u16; + + /// Writes the response 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. + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; +} + +impl SerializableResponse 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/espi-service/Cargo.toml b/espi-service/Cargo.toml index 0734a57e..7f0975d2 100644 --- a/espi-service/Cargo.toml +++ b/espi-service/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" workspace = true [dependencies] +bitfield.workspace = true embedded-services.workspace = true defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } @@ -19,6 +20,13 @@ embassy-sync.workspace = true embassy-imxrt = { workspace = true, features = ["mimxrt633s"] } embassy-futures.workspace = true mctp-rs = { workspace = true, features = ["espi"] } +num_enum.workspace = true + +# TODO Service message type crates are a temporary dependency until we can parameterize +# the supported messages types at eSPI service creation time. +battery-service-messages.workspace = true +debug-service-messages.workspace = true +thermal-service-messages.workspace = true [target.'cfg(target_os = "none")'.dependencies] cortex-m-rt.workspace = true @@ -36,12 +44,15 @@ embassy-imxrt = { workspace = true, features = [ default = [] defmt = [ "dep:defmt", + "battery-service-messages/defmt", + "debug-service-messages/defmt", "embedded-services/defmt", "embassy-time/defmt", "embassy-time/defmt-timestamp-uptime", "embassy-sync/defmt", "embassy-imxrt/defmt", "mctp-rs/defmt", + "thermal-service-messages/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 74d9c0e8..82f22ddb 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -1,15 +1,13 @@ -use core::mem::offset_of; use core::slice; +use crate::mctp::{HostRequest, HostResponse, OdpHeader, OdpMessageType, OdpService}; use core::borrow::BorrowMut; use embassy_imxrt::espi; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embedded_services::buffer::OwnedRef; -use embedded_services::comms::{self, EndpointID, External, Internal}; -use embedded_services::ec_type::message::{HostMsg, NotificationMsg, StdHostMsg, StdHostPayload, StdHostRequest}; -use embedded_services::ec_type::protocols::mctp; +use embedded_services::comms::{self, EndpointID, External}; use embedded_services::{GlobalRawMutex, debug, ec_type, error, info, trace}; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -20,12 +18,17 @@ const HOST_TX_QUEUE_SIZE: usize = 5; // REVISIT: When adding support for other platforms, refactor this as they don't have a notion of port IDs const OOB_PORT_ID: usize = 1; -// Should be as large as the largest possible MCTP packet and it's metadata. +// Should be as large as the largest possible MCTP packet and its metadata. const ASSEMBLY_BUF_SIZE: usize = 256; embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; ASSEMBLY_BUF_SIZE]); -type HostMsgInternal = (EndpointID, StdHostMsg); +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct HostResponseMessage { + pub source_endpoint: EndpointID, + pub message: HostResponse, +} #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -36,8 +39,8 @@ pub enum Error { pub struct Service<'a> { endpoint: comms::Endpoint, - ec_memory: Mutex, - host_tx_queue: Channel, + _ec_memory: Mutex, + host_tx_queue: Channel, assembly_buf_owned_ref: OwnedRef<'a, u8>, } @@ -45,143 +48,39 @@ impl Service<'_> { pub fn new(ec_memory: &'static mut ec_type::structure::ECMemory) -> Self { Service { endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), - ec_memory: Mutex::new(ec_memory), + _ec_memory: Mutex::new(ec_memory), host_tx_queue: Channel::new(), assembly_buf_owned_ref: assembly_buf::get_mut().unwrap(), } } - async fn route_to_service(&self, offset: usize, length: usize) -> Result<(), ec_type::Error> { - let mut offset = offset; - let mut length = length; - - if offset + length > size_of::() { - return Err(ec_type::Error::InvalidLocation); - } - - while length > 0 { - if (offset >= offset_of!(ec_type::structure::ECMemory, ver) - && offset < offset_of!(ec_type::structure::ECMemory, ver) + size_of::()) - || (offset >= offset_of!(ec_type::structure::ECMemory, caps) - && offset - < offset_of!(ec_type::structure::ECMemory, caps) - + size_of::()) - { - // This is a read-only section. eSPI master should not write to it. - return Err(ec_type::Error::InvalidLocation); - } else if offset >= offset_of!(ec_type::structure::ECMemory, batt) - && offset < offset_of!(ec_type::structure::ECMemory, batt) + size_of::() - { - self.route_to_battery_service(&mut offset, &mut length).await?; - } else if offset >= offset_of!(ec_type::structure::ECMemory, therm) - && offset < offset_of!(ec_type::structure::ECMemory, therm) + size_of::() - { - self.route_to_thermal_service(&mut offset, &mut length).await?; - } else if offset >= offset_of!(ec_type::structure::ECMemory, alarm) - && offset < offset_of!(ec_type::structure::ECMemory, alarm) + size_of::() - { - self.route_to_time_alarm_service(&mut offset, &mut length).await?; - } - } - - Ok(()) - } - - async fn route_to_battery_service(&self, offset: &mut usize, length: &mut usize) -> Result<(), ec_type::Error> { - let msg = { - let memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - ec_type::mem_map_to_battery_msg(&memory_map, offset, length)? - }; - - comms::send( - EndpointID::External(External::Host), - EndpointID::Internal(Internal::Battery), - &msg, - ) - .await - .unwrap(); - - Ok(()) - } - - async fn route_to_thermal_service(&self, offset: &mut usize, length: &mut usize) -> Result<(), ec_type::Error> { - let msg = { - let memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - ec_type::mem_map_to_thermal_msg(&memory_map, offset, length)? - }; - - comms::send( - EndpointID::External(External::Host), - EndpointID::Internal(Internal::Thermal), - &msg, - ) - .await - .unwrap(); - - Ok(()) - } - - async fn route_to_time_alarm_service(&self, offset: &mut usize, length: &mut usize) -> Result<(), ec_type::Error> { - let msg = { - let memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - ec_type::mem_map_to_time_alarm_msg(&memory_map, offset, length)? - }; - - comms::send( - EndpointID::External(External::Host), - EndpointID::Internal(Internal::TimeAlarm), - &msg, - ) - .await - .unwrap(); - - Ok(()) - } - - pub(crate) async fn wait_for_subsystem_msg(&self) -> HostMsgInternal { + pub(crate) async fn wait_for_response(&self) -> HostResponseMessage { self.host_tx_queue.receive().await } - pub(crate) async fn process_subsystem_msg(&self, espi: &mut espi::Espi<'static>, host_msg: HostMsgInternal) { - let (endpoint, host_msg) = host_msg; - match host_msg { - HostMsg::Notification(notification_msg) => self.process_notification_to_host(espi, ¬ification_msg).await, - HostMsg::Response(acpi_msg_comms) => self.process_response_to_host(espi, &acpi_msg_comms, endpoint).await, - } - } - - async fn process_notification_to_host(&self, espi: &mut espi::Espi<'_>, notification: &NotificationMsg) { - espi.irq_push(notification.offset).await; - info!("espi: Notification id {} sent to Host!", notification.offset); - } + // TODO The notification system was not actually used, so this is currently dead code. + // We need to implement some interface for triggering notifications from other subsystems, and it may do something like this: + // + // async fn process_notification_to_host(&self, espi: &mut espi::Espi<'_>, notification: &NotificationMsg) { + // espi.irq_push(notification.offset).await; + // info!("espi: Notification id {} sent to Host!", notification.offset); + // } async fn serialize_packet_from_subsystem( &self, espi: &mut espi::Espi<'static>, - response: &StdHostRequest, - endpoint: EndpointID, + response: &HostResponseMessage, ) -> 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 reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { source_endpoint_id: mctp_rs::EndpointId::Id(0x80), - destination_endpoint_id: match endpoint { - EndpointID::Internal(Internal::Battery) => mctp_rs::EndpointId::Id(8), - EndpointID::Internal(Internal::Thermal) => mctp_rs::EndpointId::Id(9), - EndpointID::Internal(Internal::Debug) => mctp_rs::EndpointId::Id(10), - _ => mctp_rs::EndpointId::Id(0x80), - }, + destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), // TODO We're currently using this incorrectly - it should be the bus address of the host. Revisit once we have assigned a bus address to the host. packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); @@ -193,21 +92,17 @@ impl Service<'_> { }, // Medium-specific context }; - let header = mctp::OdpHeader { - request_bit: false, - datagram_bit: false, - service: match endpoint { - EndpointID::Internal(Internal::Battery) => mctp::OdpService::Battery, - EndpointID::Internal(Internal::Thermal) => mctp::OdpService::Thermal, - EndpointID::Internal(Internal::Debug) => mctp::OdpService::Debug, - _ => mctp::OdpService::Debug, + let header = OdpHeader { + message_type: OdpMessageType::Response { + is_error: !response.message.is_ok(), }, - command_code: response.command.into(), - completion_code: Default::default(), + is_datagram: false, + service: source_service, + message_id: response.message.discriminant(), }; let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, response.payload)) + .serialize_packet(reply_context, (header, response.message.clone())) .map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); Error::Serialize @@ -245,28 +140,25 @@ impl Service<'_> { espi.oob_write_data(OOB_PORT_ID, packet.len() as u8) } - fn send_mctp_error_response(&self, endpoint: EndpointID, espi: &mut espi::Espi<'static>) { - // SAFETY: Unwrap is safe here as battery will always be supported. - // Data is ACPI payload [version, instance, reserved (error status), command] - let (final_packet, final_packet_size) = mctp::build_mctp_header(&[0, 0, 0, 1], 4, endpoint, true, true) - .expect("Unexpected error building MCTP header"); - - if let Err(e) = self.write_to_hw(espi, &final_packet[..final_packet_size]) { - error!("Critical error sending error response: {:?}", e); - } + 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 { + source_endpoint: endpoint, + message: HostResponse::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), + }; + self.serialize_packet_from_subsystem(espi, &error_msg) + .await + .unwrap_or_else(|_| { + error!("Critical error reporting MCTP protocol error to host!"); + }); } - async fn process_response_to_host( - &self, - espi: &mut espi::Espi<'static>, - response: &StdHostRequest, - endpoint: EndpointID, - ) { - match self.serialize_packet_from_subsystem(espi, response, endpoint).await { + pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResponseMessage) { + match self.serialize_packet_from_subsystem(espi, &response).await { Err(e) => { error!("Packet serialize error {:?}", e); - self.send_mctp_error_response(endpoint, espi); + self.send_mctp_error_response(response.source_endpoint, espi).await; } Ok(()) => { trace!("Full packet successfully sent to host!") @@ -277,35 +169,29 @@ impl Service<'_> { pub(crate) fn endpoint(&self) -> &comms::Endpoint { &self.endpoint } + + fn queue_response_to_host( + &self, + source_endpoint: EndpointID, + message: HostResponse, + ) -> Result<(), comms::MailboxDelegateError> { + debug!("Espi service: recvd response"); + self.host_tx_queue + .try_send(HostResponseMessage { + source_endpoint, + message, + }) + .map_err(|_| comms::MailboxDelegateError::BufferFull)?; + + Ok(()) + } } impl comms::MailboxDelegate for Service<'_> { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(msg) = message.data.get::() { - let host_msg = (message.from, *msg); - debug!("Espi service: recvd acpi response"); - if self.host_tx_queue.try_send(host_msg).is_err() { - return Err(comms::MailboxDelegateError::BufferFull); - } - } else { - let mut memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - if let Some(msg) = message.data.get::() { - ec_type::update_capabilities_section(msg, &mut memory_map); - } else if let Some(msg) = message.data.get::() { - ec_type::update_battery_section(msg, &mut memory_map); - } else if let Some(msg) = message.data.get::() { - ec_type::update_thermal_section(msg, &mut memory_map); - } else if let Some(msg) = message.data.get::() { - ec_type::update_time_alarm_section(msg, &mut memory_map); - } else { - return Err(comms::MailboxDelegateError::MessageNotFound); - } - } - - Ok(()) + crate::mctp::try_route_request_to_comms(message, |source_endpoint, message| { + self.queue_response_to_host(source_endpoint, message) + }) } } @@ -323,19 +209,7 @@ pub(crate) async fn process_controller_event( port_event.port, port_event.direction, port_event.offset, port_event.base_addr, port_event.length, ); - // If it is a peripheral channel write, then we need to notify the service - if port_event.direction { - let res = espi_service - .route_to_service(port_event.offset, port_event.length) - .await; - - if res.is_err() { - error!( - "eSPI master send invalid offset: {} length: {}", - port_event.offset, port_event.length - ); - } - } + // We're not handling these - communication is all through OOB espi.complete_port(port_event.port); } @@ -358,99 +232,64 @@ pub(crate) async fn process_controller_event( #[cfg(feature = "defmt")] debug!("OOB message: {:02X}", &src_slice[0..]); - let host_request: StdHostRequest; - let endpoint: EndpointID; - - { - let mut assembly_access = espi_service - .assembly_buf_owned_ref - .borrow_mut() - .map_err(Error::Buffer)?; - // let mut comms_access = espi_service.comms_buf_owned_ref.borrow_mut(); - let mut mctp_ctx = mctp_rs::MctpPacketContext::::new( - SmbusEspiMedium, - assembly_access.borrow_mut(), - ); - - match mctp_ctx.deserialize_packet(with_pec) { - Ok(Some(message)) => { - #[cfg(feature = "defmt")] - trace!("MCTP packet successfully deserialized"); - - match message.parse_as::() { - Ok((header, body)) => { - host_request = StdHostRequest { - command: header.command_code.into(), - status: header.completion_code.into(), - payload: body, - }; - endpoint = match header.service { - mctp::OdpService::Battery => { - EndpointID::Internal(embedded_services::comms::Internal::Battery) - } - mctp::OdpService::Thermal => { - EndpointID::Internal(embedded_services::comms::Internal::Thermal) - } - mctp::OdpService::Debug => { - EndpointID::Internal(embedded_services::comms::Internal::Debug) - } - }; - #[cfg(feature = "defmt")] - trace!( - "Host Request: Service {:?}, Command {:?}, Status {:?}", - endpoint, host_request.command, host_request.status, - ); - } - Err(_e) => { - #[cfg(feature = "defmt")] - error!("MCTP ODP type malformed"); - espi.complete_port(port_event.port); - - // REVISIT: An error here means that we couldn't decode the incoming message, - // thus we don't know what subsystem the message was meant for. For now, - // hardcode Debug but we might need a special endpoint for error. - espi_service.send_mctp_error_response( - EndpointID::Internal(embedded_services::comms::Internal::Debug), - espi, - ); - return Err(Error::Serialize); - } + let mut assembly_access = espi_service + .assembly_buf_owned_ref + .borrow_mut() + .map_err(Error::Buffer)?; + let mut mctp_ctx = + mctp_rs::MctpPacketContext::::new(SmbusEspiMedium, assembly_access.borrow_mut()); + + match mctp_ctx.deserialize_packet(with_pec) { + Ok(Some(message)) => { + #[cfg(feature = "defmt")] + trace!("MCTP packet successfully deserialized"); + + match message.parse_as::() { + Ok((header, body)) => { + let target_endpoint = header.service.get_endpoint_id(); + #[cfg(feature = "defmt")] + trace!( + "Host Request: Service {:?}, Command {:?}", + target_endpoint, header.message_id, + ); + + drop(assembly_access); + + espi.complete_port(port_event.port); + + body.send_to_endpoint(&espi_service.endpoint, target_endpoint) + .await + .expect("result error type is infallible"); + info!("MCTP packet forwarded to service: {:?}", target_endpoint); + } + Err(_e) => { + #[cfg(feature = "defmt")] + error!("MCTP ODP type malformed: {}", _e); + + espi.complete_port(port_event.port); + + return Err(Error::Serialize); } - } - Ok(None) => { - // Partial message, waiting for more packets - error!("Partial msg, should not happen"); - espi.complete_port(OOB_PORT_ID); - - // REVISIT: An error here means that we couldn't decode the incoming message, - // thus we don't know what subsystem the message was meant for. For now, - // hardcode Debug but we might need a special endpoint for error. - espi_service.send_mctp_error_response( - EndpointID::Internal(embedded_services::comms::Internal::Debug), - espi, - ); - return Err(Error::Serialize); - } - Err(_e) => { - // Handle protocol or medium error - error!("MCTP packet malformed"); - espi.complete_port(OOB_PORT_ID); - - // REVISIT: An error here means that we couldn't decode the incoming message, - // thus we don't know what subsystem the message was meant for. For now, - // hardcode Debug but we might need a special endpoint for error. - espi_service.send_mctp_error_response( - EndpointID::Internal(embedded_services::comms::Internal::Debug), - espi, - ); - return Err(Error::Serialize); } } - } + Ok(None) => { + // Partial message, waiting for more packets + error!("Partial msg, should not happen"); + espi.complete_port(OOB_PORT_ID); - espi.complete_port(port_event.port); - espi_service.endpoint.send(endpoint, &host_request).await.unwrap(); - info!("MCTP packet forwarded to service: {:?}", endpoint); + return Err(Error::Serialize); + } + Err(_e) => { + // Handle protocol or medium error + error!("MCTP packet malformed"); + + #[cfg(feature = "defmt")] + error!("error code: {:?}", _e); + espi.complete_port(OOB_PORT_ID); + + return Err(Error::Serialize); + } + } } else { espi.complete_port(port_event.port); } diff --git a/espi-service/src/lib.rs b/espi-service/src/lib.rs index 658cb1c0..88a69717 100644 --- a/espi-service/src/lib.rs +++ b/espi-service/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::unwrap_used)] mod espi_service; +mod mctp; pub mod task; pub use espi_service::*; diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs new file mode 100644 index 00000000..9820f58d --- /dev/null +++ b/espi-service/src/mctp.rs @@ -0,0 +1,354 @@ +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"))?, + )) + } +} diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs index ad1ee7fa..dc7ac53c 100644 --- a/espi-service/src/task.rs +++ b/espi-service/src/task.rs @@ -34,14 +34,14 @@ pub async fn espi_service( .unwrap(); loop { - let event = select(espi.wait_for_event(), espi_service.wait_for_subsystem_msg()).await; + let event = select(espi.wait_for_event(), espi_service.wait_for_response()).await; match event { embassy_futures::select::Either::First(controller_event) => { process_controller_event(&mut espi, espi_service, controller_event).await? } embassy_futures::select::Either::Second(host_msg) => { - espi_service.process_subsystem_msg(&mut espi, host_msg).await + espi_service.process_response_to_host(&mut espi, host_msg).await } } } diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 8cba7169..c1215bae 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -102,6 +102,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ + "battery-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -114,6 +115,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + [[package]] name = "bincode" version = "2.0.1" @@ -123,14 +134,6 @@ dependencies = [ "unty", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -351,6 +354,15 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "debug-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -779,9 +791,9 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags 2.9.1", "num-traits", "num_enum", @@ -793,8 +805,11 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ + "battery-service-messages", + "bitfield 0.17.0", "cortex-m", "cortex-m-rt", + "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-imxrt", @@ -802,6 +817,8 @@ dependencies = [ "embassy-time", "embedded-services", "mctp-rs", + "num_enum", + "thermal-service-messages", ] [[package]] @@ -1033,9 +1050,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1506,6 +1523,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "thermal-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", + "uuid", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index fc36a246..d9e539e4 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -117,14 +117,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -769,9 +761,9 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags 2.9.4", "num-traits", "num_enum", @@ -1008,9 +1000,9 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1353,6 +1345,7 @@ dependencies = [ "embedded-cfu-protocol", "embedded-hal 1.0.0", "embedded-hal-async", + "embedded-mcu-hal", "embedded-services", "embedded-usb-pd", "futures", diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 2bfd797b..c27b34f6 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -74,6 +74,7 @@ platform-service = { path = "../../platform-service", features = [ "defmt", "imxrt685", ] } +embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } # Needed otherwise cargo will pull from git [patch."https://github.com/OpenDevicePartnership/embedded-services"] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 0a917ced..38bbfdb8 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -116,6 +116,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ + "battery-service-messages", "embassy-futures", "embassy-sync", "embassy-time", @@ -128,6 +129,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + [[package]] name = "bbq2" version = "0.4.2" @@ -389,6 +399,7 @@ version = "0.1.0" dependencies = [ "bbq2", "critical-section", + "debug-service-messages", "defmt 0.3.100", "embassy-sync", "embassy-time", @@ -397,6 +408,14 @@ dependencies = [ "rtt-target", ] +[[package]] +name = "debug-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-services", + "num_enum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -1514,6 +1533,7 @@ dependencies = [ "cfu-service", "critical-section", "debug-service", + "debug-service-messages", "defmt 0.3.100", "embassy-executor", "embassy-futures", @@ -1534,6 +1554,7 @@ dependencies = [ "power-policy-service", "static_cell", "thermal-service", + "thermal-service-messages", "type-c-service", ] @@ -1607,6 +1628,16 @@ dependencies = [ "heapless", "log", "mctp-rs", + "thermal-service-messages", + "uuid", +] + +[[package]] +name = "thermal-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-services", + "num_enum", "uuid", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 4140ec02..39ffeb39 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -39,6 +39,7 @@ type-c-service = { path = "../../type-c-service", features = ["log"] } embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log"] } +thermal-service-messages = { path = "../../thermal-service-messages" } env_logger = "0.9.0" log = "0.4.14" @@ -50,6 +51,7 @@ embedded-hal-mock = { version = "0.11.1", features = ["embedded-hal-async"] } critical-section = { version = "1.1", features = ["std"] } debug-service = { path = "../../debug-service" } +debug-service-messages = { path = "../../debug-service-messages" } [lib] name = "std_examples" diff --git a/examples/std/src/bin/debug.rs b/examples/std/src/bin/debug.rs index 9944d98d..8899d371 100644 --- a/examples/std/src/bin/debug.rs +++ b/examples/std/src/bin/debug.rs @@ -11,13 +11,11 @@ defmt::timestamp!("{=u64}", { 0u64 }); // Mock eSPI transport service mod espi_service { use core::borrow::BorrowMut; + use debug_service_messages::{DebugRequest, DebugResponse, DebugResult, STD_DEBUG_BUF_SIZE}; use embassy_sync::{once_lock::OnceLock, signal::Signal}; use embedded_services::GlobalRawMutex; use embedded_services::buffer::OwnedRef; use embedded_services::comms::{self, EndpointID, External, Internal}; - use embedded_services::ec_type::message::{ - HostMsg, NotificationMsg, STD_DEBUG_BUF_SIZE, StdHostMsg, StdHostPayload, StdHostRequest, - }; use log::{info, trace}; // Max defmt payload we expect to shuttle in this mock @@ -26,6 +24,9 @@ mod espi_service { // Static request buffer used to build the "GetDebugBuffer" payload embedded_services::define_static_buffer!(debug_req_buf, u8, [0u8; 32]); + // TODO Notifications are not currently implemented. Remove this and replace it with the real notification struct when we do implement it. + pub struct NotificationMsg; + pub struct Service { endpoint: comms::Endpoint, notify: &'static Signal, @@ -48,30 +49,31 @@ mod espi_service { impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(host_msg) = message.data.get::() { - match host_msg { - HostMsg::Notification(n) => { - info!( - "mock eSPI got Host Notification: offset={} from {:?}", - n.offset, message.from - ); - // Defer to async host task via signal (receive is not async) - self.notify.signal(*n); - Ok(()) - } - HostMsg::Response(acpi) => { - // Stage the response bytes into the mock OOB buffer for the host - let mut access = self.resp_owned.borrow_mut().unwrap(); - let buf: &mut [u8] = core::borrow::BorrowMut::borrow_mut(&mut access); - if let StdHostPayload::DebugGetMsgsResponse { debug_buf } = acpi.payload { - let copy_len = core::cmp::min(debug_buf.len(), buf.len()); - buf[..copy_len].copy_from_slice(&debug_buf[..copy_len]); - trace!("mock eSPI staged {copy_len} response bytes for host"); - self.resp_len.signal(copy_len); - } - Ok(()) + if let Some(debug_result) = message.data.get::() { + // Stage the response bytes into the mock OOB buffer for the host + let mut access = self.resp_owned.borrow_mut().unwrap(); + let buf: &mut [u8] = core::borrow::BorrowMut::borrow_mut(&mut access); + + let debug_response = debug_result.map_err(|_| comms::MailboxDelegateError::Other)?; + match debug_response { + DebugResponse::DebugGetMsgsResponse { debug_buf } => { + let copy_len = core::cmp::min(debug_buf.len(), buf.len()); + buf[..copy_len].copy_from_slice(&debug_buf[..copy_len]); + trace!("mock eSPI staged {copy_len} response bytes for host"); + self.resp_len.signal(copy_len); } } + + Ok(()) + // TODO notification functionality is currently not implemented. Restore this when we implement it. + // } else if let Some(debug_notification) = message.data.get::() { + // info!( + // "mock eSPI got Host Notification: offset={} from {:?}", + // n.offset, message.from + // ); + // // Defer to async host task via signal (receive is not async) + // self.notify.signal(*n); + // Ok(()) } else { Err(comms::MailboxDelegateError::MessageNotFound) } @@ -111,11 +113,8 @@ mod espi_service { loop { // Wait for a device notification via the mock eSPI transport - let n: NotificationMsg = wait_host_notification().await; - info!( - "eSPI: got Host Notification (offset={}), sending OOB request/ACK to Debug", - n.offset - ); + let _n: NotificationMsg = wait_host_notification().await; + info!("eSPI: got Host Notification, sending OOB request/ACK to Debug",); // Build the ACPI/MCTP-style request payload for the Debug service let request = b"GetDebugBuffer"; @@ -130,13 +129,7 @@ mod espi_service { let _ = comms::send( EndpointID::External(External::Host), EndpointID::Internal(Internal::Debug), - &StdHostRequest { - command: embedded_services::ec_type::message::OdpCommand::Debug( - embedded_services::ec_type::protocols::debug::DebugCmd::GetMsgs, - ), - status: 0, - payload: StdHostPayload::DebugGetMsgsRequest, - }, + &DebugRequest::DebugGetMsgsRequest {}, ) .await; diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 0cc765cc..653ca820 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -12,6 +12,7 @@ use static_cell::StaticCell; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use thermal_service as ts; +use thermal_service_messages::ThermalRequest; use ts::mptf; // Mock host service @@ -20,6 +21,7 @@ mod host { use embedded_services::comms::{self, Endpoint, EndpointID, External, MailboxDelegate}; use log::{info, warn}; use thermal_service as ts; + use thermal_service_messages::{ThermalResponse, ThermalResult}; use ts::mptf; pub struct Host { @@ -35,13 +37,13 @@ mod host { } } - fn handle_response(&self, response: mptf::Response) { - match response.data { - mptf::ResponseData::GetTmp(tmp) => { - info!("Host received temperature: {} °C", ts::utils::dk_to_c(tmp)) + fn handle_response(&self, response: ThermalResponse) { + match response { + ThermalResponse::ThermalGetTmpResponse { temperature } => { + info!("Host received temperature: {} °C", ts::utils::dk_to_c(temperature)) } - mptf::ResponseData::GetVar(_status, value) => { - info!("Host received fan RPM: {value}") + ThermalResponse::ThermalGetVarResponse { val } => { + info!("Host received fan RPM: {val}") } _ => info!("Received MPTF response: {response:?}"), } @@ -50,8 +52,8 @@ mod host { impl MailboxDelegate for Host { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(&response) = message.data.get::() { - self.handle_response(response); + if let Some(&result) = message.data.get::() { + self.handle_response(result.map_err(|_| comms::MailboxDelegateError::Other)?); Ok(()) } else if let Some(¬ification) = message.data.get::() { warn!("Received notification: {notification:?}"); @@ -215,7 +217,15 @@ async fn host() { // Set thresholds to 40 °C (3131 deciKelvin) host.tp - .send(thermal_id, &mptf::Request::SetThrs(0, 0, 0, 3131)) + .send( + thermal_id, + &ThermalRequest::ThermalSetThrsRequest { + instance_id: 0, + timeout: 0, + low: 0, + high: 3131, + }, + ) .await .unwrap(); Timer::after_millis(100).await; @@ -224,7 +234,12 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::SetVar(0, 4, mptf::uuid_standard::FAN_ON_TEMP, 3131), + &ThermalRequest::ThermalSetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_ON_TEMP, + set_var: 3131, + }, ) .await .unwrap(); @@ -234,7 +249,12 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::SetVar(0, 4, mptf::uuid_standard::FAN_RAMP_TEMP, 3231), + &ThermalRequest::ThermalSetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_RAMP_TEMP, + set_var: 3231, + }, ) .await .unwrap(); @@ -244,7 +264,12 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::SetVar(0, 4, mptf::uuid_standard::FAN_MAX_TEMP, 3531), + &ThermalRequest::ThermalSetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_MAX_TEMP, + set_var: 3531, + }, ) .await .unwrap(); @@ -255,7 +280,10 @@ async fn host() { host.alert.wait().await; info!("Host requesting temperature in response to threshold alert"); - host.tp.send(thermal_id, &mptf::Request::GetTmp(0)).await.unwrap(); + host.tp + .send(thermal_id, &ThermalRequest::ThermalGetTmpRequest { instance_id: 0 }) + .await + .unwrap(); // Need to wait briefly before send is fixed to propagate errors and we can handle retries Timer::after_millis(100).await; @@ -264,7 +292,11 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::GetVar(0, 4, mptf::uuid_standard::FAN_CURRENT_RPM), + &ThermalRequest::ThermalGetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_CURRENT_RPM, + }, ) .await .unwrap(); diff --git a/keyboard-service/src/lib.rs b/keyboard-service/src/lib.rs index 30d936a2..7f5f8612 100644 --- a/keyboard-service/src/lib.rs +++ b/keyboard-service/src/lib.rs @@ -20,6 +20,7 @@ use embedded_services::hid; pub const HID_KB_ID: hid::DeviceId = hid::DeviceId(0); /// HID keyboard error. +#[derive(Debug)] pub enum KeyboardError { /// Rollover occurred Rollover, diff --git a/thermal-service-messages/Cargo.toml b/thermal-service-messages/Cargo.toml new file mode 100644 index 00000000..2d9dbc37 --- /dev/null +++ b/thermal-service-messages/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "thermal-service-messages" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embedded-services.workspace = true +num_enum.workspace = true +uuid.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt"] diff --git a/thermal-service-messages/src/lib.rs b/thermal-service-messages/src/lib.rs new file mode 100644 index 00000000..d0325a2f --- /dev/null +++ b/thermal-service-messages/src/lib.rs @@ -0,0 +1,277 @@ +#![no_std] + +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// 16-bit variable length +pub type VarLen = u16; + +/// Instance ID +pub type InstanceId = u8; + +/// Time in milliseconds +pub type Milliseconds = u32; + +/// MPTF expects temperatures in tenth Kelvins +pub type DeciKelvin = u32; + +/// Standard MPTF requests expected by the thermal subsystem +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum ThermalCmd { + /// EC_THM_GET_TMP = 0x1 + GetTmp = 1, + /// EC_THM_SET_THRS = 0x2 + SetThrs = 2, + /// EC_THM_GET_THRS = 0x3 + GetThrs = 3, + /// EC_THM_SET_SCP = 0x4 + SetScp = 4, + /// EC_THM_GET_VAR = 0x5 + GetVar = 5, + /// EC_THM_SET_VAR = 0x6 + SetVar = 6, +} + +impl From<&ThermalRequest> for ThermalCmd { + fn from(request: &ThermalRequest) -> Self { + match request { + ThermalRequest::ThermalGetTmpRequest { .. } => ThermalCmd::GetTmp, + ThermalRequest::ThermalSetThrsRequest { .. } => ThermalCmd::SetThrs, + ThermalRequest::ThermalGetThrsRequest { .. } => ThermalCmd::GetThrs, + ThermalRequest::ThermalSetScpRequest { .. } => ThermalCmd::SetScp, + ThermalRequest::ThermalGetVarRequest { .. } => ThermalCmd::GetVar, + ThermalRequest::ThermalSetVarRequest { .. } => ThermalCmd::SetVar, + } + } +} + +impl From<&ThermalResponse> for ThermalCmd { + fn from(response: &ThermalResponse) -> Self { + match response { + ThermalResponse::ThermalGetTmpResponse { .. } => ThermalCmd::GetTmp, + ThermalResponse::ThermalSetThrsResponse => ThermalCmd::SetThrs, + ThermalResponse::ThermalGetThrsResponse { .. } => ThermalCmd::GetThrs, + ThermalResponse::ThermalSetScpResponse => ThermalCmd::SetScp, + ThermalResponse::ThermalGetVarResponse { .. } => ThermalCmd::GetVar, + ThermalResponse::ThermalSetVarResponse => ThermalCmd::SetVar, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ThermalRequest { + ThermalGetTmpRequest { + instance_id: u8, + }, + ThermalSetThrsRequest { + instance_id: u8, + timeout: Milliseconds, + low: DeciKelvin, + high: DeciKelvin, + }, + ThermalGetThrsRequest { + instance_id: u8, + }, + ThermalSetScpRequest { + instance_id: u8, + policy_id: u32, + acoustic_lim: u32, + power_lim: u32, + }, + ThermalGetVarRequest { + instance_id: u8, + len: VarLen, // TODO why is there a len here? as far as I can tell we're always discarding it, and I think values are only u32? + var_uuid: uuid::Bytes, + }, + ThermalSetVarRequest { + instance_id: u8, + len: VarLen, // TODO why is there a len here? as far as I can tell we're always discarding it, and I think values are only u32? + var_uuid: uuid::Bytes, + set_var: u32, + }, +} + +// TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? +impl SerializableMessage for ThermalRequest { + fn serialize(self, _buffer: &mut [u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match ThermalCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + ThermalCmd::GetTmp => Self::ThermalGetTmpRequest { + instance_id: safe_get_u8(buffer, 0)?, + }, + ThermalCmd::SetThrs => Self::ThermalSetThrsRequest { + instance_id: safe_get_u8(buffer, 0)?, + timeout: safe_get_dword(buffer, 1)?, + low: safe_get_dword(buffer, 5)?, + high: safe_get_dword(buffer, 9)?, + }, + ThermalCmd::GetThrs => Self::ThermalGetThrsRequest { + instance_id: safe_get_u8(buffer, 0)?, + }, + ThermalCmd::SetScp => Self::ThermalSetScpRequest { + instance_id: safe_get_u8(buffer, 0)?, + policy_id: safe_get_dword(buffer, 1)?, + acoustic_lim: safe_get_dword(buffer, 5)?, + power_lim: safe_get_dword(buffer, 9)?, + }, + ThermalCmd::GetVar => Self::ThermalGetVarRequest { + instance_id: safe_get_u8(buffer, 0)?, + len: safe_get_u16(buffer, 1)?, + var_uuid: safe_get_uuid(buffer, 3)?, + }, + ThermalCmd::SetVar => Self::ThermalSetVarRequest { + instance_id: safe_get_u8(buffer, 0)?, + len: safe_get_u16(buffer, 1)?, + var_uuid: safe_get_uuid(buffer, 3)?, + set_var: safe_get_dword(buffer, 19)?, + }, + }, + ) + } + + fn discriminant(&self) -> u16 { + let cmd: ThermalCmd = self.into(); + cmd.into() + } +} + +#[derive(PartialEq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ThermalResponse { + ThermalGetTmpResponse { + temperature: DeciKelvin, + }, + ThermalSetThrsResponse, + ThermalGetThrsResponse { + timeout: Milliseconds, + low: DeciKelvin, + high: DeciKelvin, + }, + ThermalSetScpResponse, + ThermalGetVarResponse { + val: u32, + }, + ThermalSetVarResponse, +} + +impl SerializableMessage for ThermalResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::ThermalGetTmpResponse { temperature } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(temperature)); + + Ok(4) + } + Self::ThermalGetThrsResponse { timeout, low, high } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(timeout)); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(low)); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(high)); + + Ok(12) + } + + Self::ThermalGetVarResponse { val } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(val)); + Ok(4) + } + Self::ThermalSetVarResponse | Self::ThermalSetScpResponse | Self::ThermalSetThrsResponse => Ok(0), + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + ThermalCmd::from(self).into() + } +} + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum ThermalError { + InvalidParameter = 1, + UnsupportedRevision = 2, + HardwareError = 3, +} + +impl SerializableMessage for ThermalError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + Self::UnsupportedRevision | Self::InvalidParameter | Self::HardwareError => Ok(0), + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +pub type ThermalResult = Result; + +fn safe_get_u8(buffer: &[u8], index: usize) -> Result { + buffer + .get(index) + .copied() + .ok_or(MessageSerializationError::BufferTooSmall) +} + +fn safe_get_u16(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 2) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u16::from_le_bytes(bytes)) +} + +fn safe_get_dword(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u32::from_le_bytes(bytes)) +} + +fn safe_get_uuid(buffer: &[u8], index: usize) -> Result { + buffer + .get(index..index + 16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall) +} diff --git a/thermal-service/Cargo.toml b/thermal-service/Cargo.toml index df9dd26f..b5414768 100644 --- a/thermal-service/Cargo.toml +++ b/thermal-service/Cargo.toml @@ -17,6 +17,7 @@ embedded-hal-async.workspace = true embedded-hal.workspace = true embedded-services.workspace = true heapless.workspace = true +thermal-service-messages.workspace = true uuid.workspace = true embedded-fans-async = "0.2.0" embedded-sensors-hal-async = "0.3.0" @@ -32,6 +33,7 @@ defmt = [ "embedded-fans-async/defmt", "embedded-sensors-hal-async/defmt", "mctp-rs/defmt", + "thermal-service-messages/defmt", ] log = [ "dep:log", diff --git a/thermal-service/src/context.rs b/thermal-service/src/context.rs index 93e17135..35ed3a12 100644 --- a/thermal-service/src/context.rs +++ b/thermal-service/src/context.rs @@ -1,37 +1,26 @@ //! Thermal service context -use crate::mptf; use crate::{Error, Event, fan, sensor}; use embassy_sync::channel::Channel; use embedded_services::GlobalRawMutex; -use embedded_services::buffer::OwnedRef; -use embedded_services::ec_type::message::StdHostRequest; use embedded_services::{error, intrusive_list}; -embedded_services::define_static_buffer!(mctp_buf, u8, [0u8; 69]); - -pub(crate) struct Context<'a> { +pub(crate) struct Context { // Registered temperature sensors sensors: intrusive_list::IntrusiveList, // Registered fans fans: intrusive_list::IntrusiveList, - // MPTF Request Queue - mptf: Channel, - // Raw MCTP Payload Queue - mctp: Channel, - // MCTP message buffer - mctp_buf: OwnedRef<'a, u8>, + // Pending MPTF request queue + mptf: Channel, // Event queue events: Channel, } -impl<'a> Context<'a> { +impl Context { pub(crate) fn new() -> Self { Self { sensors: intrusive_list::IntrusiveList::new(), fans: intrusive_list::IntrusiveList::new(), mptf: Channel::new(), - mctp: Channel::new(), - mctp_buf: mctp_buf::get_mut().unwrap(), events: Channel::new(), } } @@ -102,28 +91,14 @@ impl<'a> Context<'a> { fan.execute_request(request).await } - pub(crate) fn send_mptf_request(&self, msg: mptf::Request) -> Result<(), Error> { - self.mptf.try_send(msg).map_err(|_| Error)?; - Ok(()) + pub(crate) fn queue_mptf_request(&self, msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { + self.mptf.try_send(msg).map_err(|_| Error) } - pub(crate) async fn wait_mptf_request(&self) -> mptf::Request { + pub(crate) async fn wait_mptf_request(&self) -> thermal_service_messages::ThermalRequest { self.mptf.receive().await } - pub(crate) fn send_mctp_payload(&self, msg: StdHostRequest) -> Result<(), Error> { - self.mctp.try_send(msg).map_err(|_| Error)?; - Ok(()) - } - - pub(crate) async fn wait_mctp_payload(&self) -> StdHostRequest { - self.mctp.receive().await - } - - pub(crate) fn get_mctp_buf(&self) -> &OwnedRef<'a, u8> { - &self.mctp_buf - } - pub(crate) async fn send_event(&self, event: Event) { self.events.send(event).await } diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index b43c7d7d..a0d07519 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -5,8 +5,6 @@ use embassy_sync::once_lock::OnceLock; use embedded_sensors_hal_async::temperature::DegreesCelsius; -use embedded_services::buffer::OwnedRef; -use embedded_services::ec_type::message::StdHostRequest; use embedded_services::{comms, error, info, intrusive_list}; mod context; @@ -35,12 +33,12 @@ pub enum Event { FanFailure(fan::DeviceId, fan::Error), } -struct Service<'a> { - context: context::Context<'a>, +struct Service { + context: context::Context, endpoint: comms::Endpoint, } -impl<'a> Service<'a> { +impl Service { fn new() -> Self { Self { context: context::Context::new(), @@ -49,16 +47,12 @@ impl<'a> Service<'a> { } } -impl<'a> comms::MailboxDelegate for Service<'a> { +impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { // Queue for later processing - if let Some(msg) = message.data.get::() { + if let Some(msg) = message.data.get::() { self.context - .send_mctp_payload(*msg) - .map_err(|_| comms::MailboxDelegateError::BufferFull) - } else if let Some(&msg) = message.data.get::() { - self.context - .send_mptf_request(msg) + .queue_mptf_request(*msg) .map_err(|_| comms::MailboxDelegateError::BufferFull) } else { Err(comms::MailboxDelegateError::InvalidData) @@ -93,24 +87,15 @@ pub async fn send_service_msg(to: comms::EndpointID, data: &impl embedded_servic } /// Send a MPTF request -pub async fn queue_mptf_request(msg: mptf::Request) -> Result<(), Error> { - SERVICE.get().await.context.send_mptf_request(msg) +pub async fn queue_mptf_request(msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { + SERVICE.get().await.context.queue_mptf_request(msg) } /// Wait for a MPTF request -pub async fn wait_mptf_request() -> mptf::Request { +pub(crate) async fn wait_mptf_request() -> thermal_service_messages::ThermalRequest { SERVICE.get().await.context.wait_mptf_request().await } -/// Wait for a MCTP payload -pub async fn wait_mctp_payload() -> StdHostRequest { - SERVICE.get().await.context.wait_mctp_payload().await -} - -pub fn get_mctp_buf<'a>() -> &'a OwnedRef<'a, u8> { - SERVICE.try_get().unwrap().context.get_mctp_buf() -} - /// Send a thermal event pub async fn send_event(event: Event) { SERVICE.get().await.context.send_event(event).await diff --git a/thermal-service/src/mptf.rs b/thermal-service/src/mptf.rs index ea119500..dc9e3940 100644 --- a/thermal-service/src/mptf.rs +++ b/thermal-service/src/mptf.rs @@ -4,8 +4,9 @@ //! //! This interface is subject to change as the eSPI OOB service is developed use crate::{self as ts, fan, sensor, utils}; -use embedded_services::ec_type::message::{StdHostPayload, StdHostRequest}; -use embedded_services::{ec_type::protocols::mctp, error}; +use thermal_service_messages::{DeciKelvin, Milliseconds}; + +use embedded_services::error; /// MPTF Standard UUIDs which the thermal service understands pub mod uuid_standard { @@ -20,131 +21,6 @@ pub mod uuid_standard { pub const FAN_CURRENT_RPM: uuid::Bytes = uuid::uuid!("adf95492-0776-4ffc-84f3-b6c8b5269683").to_bytes_le(); } -/// Standard 32-bit DWORD -pub type Dword = u32; - -/// 16-bit variable length -pub type VarLen = u16; - -/// Instance ID -pub type InstanceId = u8; - -/// Time in milliseconds -pub type Milliseconds = Dword; - -/// MPTF expects temperatures in tenth Kelvins -pub type DeciKelvin = Dword; - -/// MPTF Response -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Response { - // Status code (not necessarily related to Status code in response) - // This is used because some commands can fail but don't contain Status output as part of MPTF spec - pub status: Status, - // Response data - pub data: ResponseData, -} - -impl Response { - fn new(status: Status, data: ResponseData) -> Self { - Self { status, data } - } -} - -/// MPTF Status -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Status { - /// Success - Success, - /// Invalid parameter was used - InvalidParameter, - /// Revision is not supported - UnsupportedRevision, - /// A hardware error occurred - HardwareError, -} - -impl From for u32 { - fn from(status: Status) -> Self { - match status { - Status::Success => 0, - Status::InvalidParameter => 1, - Status::UnsupportedRevision => 2, - Status::HardwareError => 3, - } - } -} - -impl From for u8 { - fn from(status: Status) -> Self { - u32::from(status) as u8 - } -} - -/// Standard MPTF requests expected by the thermal subsystem -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Request { - /// EC_THM_GET_TMP = 0x1 - GetTmp(InstanceId), - /// EC_THM_SET_THRS = 0x2 - SetThrs(InstanceId, Milliseconds, DeciKelvin, DeciKelvin), - /// EC_THM_GET_THRS = 0x3 - GetThrs(InstanceId), - /// EC_THM_SET_SCP = 0x4 - SetScp(InstanceId, Dword, Dword, Dword), - /// EC_THM_GET_VAR = 0x5 - GetVar(InstanceId, VarLen, uuid::Bytes), - /// EC_THM_SET_VAR = 0x6 - SetVar(InstanceId, VarLen, uuid::Bytes, Dword), -} - -impl From for u8 { - fn from(request: Request) -> Self { - match request { - Request::GetTmp(_) => 1, - Request::SetThrs(_, _, _, _) => 2, - Request::GetThrs(_) => 3, - Request::SetScp(_, _, _, _) => 4, - Request::GetVar(_, _, _) => 5, - Request::SetVar(_, _, _, _) => 6, - } - } -} - -/// Data returned by thermal subsystem in response to MPTF requests -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// EC_THM_GET_TMP = 0x1 - GetTmp(DeciKelvin), - /// EC_THM_SET_THRS = 0x2 - SetThrs(Status), - /// EC_THM_GET_THRS = 0x3 - GetThrs(Status, Milliseconds, DeciKelvin, DeciKelvin), - /// EC_THM_SET_SCP = 0x4 - SetScp(Status), - /// EC_THM_GET_VAR = 0x5 - GetVar(Status, Dword), - /// EC_THM_SET_VAR = 0x6 - SetVar(Status), -} - -impl From for u8 { - fn from(response: ResponseData) -> Self { - match response { - ResponseData::GetTmp(_) => 1, - ResponseData::SetThrs(_) => 2, - ResponseData::GetThrs(_, _, _, _) => 3, - ResponseData::SetScp(_) => 4, - ResponseData::GetVar(_, _) => 5, - ResponseData::SetVar(_) => 6, - } - } -} - /// Notifications to Host #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -157,410 +33,211 @@ pub enum Notify { Critical, } -async fn sensor_get_tmp(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalGetTmpRequest { instance_id } => { - match ts::execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp).await { - Ok(ts::sensor::ResponseData::Temp(temp)) => { - request.payload = StdHostPayload::ThermalGetTmpResponse { - temperature: utils::c_to_dk(temp), - }; - request.status = 0; - } - _ => { - request.payload = StdHostPayload::ErrorResponse {}; - request.status = 1; - } - } - } - _ => error!("Thermal Service: Host message header and payload mismatch"), +async fn sensor_get_tmp(instance_id: u8) -> thermal_service_messages::ThermalResult { + if let Ok(ts::sensor::ResponseData::Temp(temp)) = + ts::execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp).await + { + Ok(thermal_service_messages::ThermalResponse::ThermalGetTmpResponse { + temperature: utils::c_to_dk(temp), + }) + } else { + Err(thermal_service_messages::ThermalError::InvalidParameter) } } -async fn get_var_handler(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalGetVarRequest { - instance_id, - len: _, - var_uuid, - } => match var_uuid { - uuid_standard::CRT_TEMP => { - let Response { status: _, data } = sensor_get_thrs(instance_id, sensor::ThresholdType::Critical).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::PROC_HOT_TEMP => { - let Response { status: _, data } = sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - // TODO: Add a SetProfileId request type? But for sensor or fan? - uuid_standard::PROFILE_TYPE => { - todo!() - } - uuid_standard::FAN_ON_TEMP => { - let Response { status: _, data } = fan_get_temp(instance_id, fan::Request::GetOnTemp).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_RAMP_TEMP => { - let Response { status: _, data } = fan_get_temp(instance_id, fan::Request::GetRampTemp).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_MAX_TEMP => { - let Response { status: _, data } = fan_get_temp(instance_id, fan::Request::GetMaxTemp).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_MIN_RPM => { - let Response { status: _, data } = fan_get_rpm(instance_id, fan::Request::GetMinRpm).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_MAX_RPM => { - let Response { status: _, data } = fan_get_rpm(instance_id, fan::Request::GetMaxRpm).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.status = error.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_CURRENT_RPM => { - let Response { status: _, data } = fan_get_rpm(instance_id, fan::Request::GetRpm).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.status = error.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - // TODO: Allow OEM to handle these? - uuid => { - error!("Received GetVar for unrecognized UUID: {:?}", uuid); - request.status = Status::InvalidParameter.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::InvalidParameter.into(), - val: 0, - } - } - }, - _ => error!("Thermal Service: Host message header and payload mismatch"), +async fn get_var_handler(instance_id: u8, var_uuid: uuid::Bytes) -> thermal_service_messages::ThermalResult { + match var_uuid { + uuid_standard::CRT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Critical).await, + uuid_standard::PROC_HOT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot).await, + // TODO: Add a SetProfileId request type? But for sensor or fan? + uuid_standard::PROFILE_TYPE => { + todo!() + } + uuid_standard::FAN_ON_TEMP => fan_get_temp(instance_id, fan::Request::GetOnTemp).await, + + uuid_standard::FAN_RAMP_TEMP => fan_get_temp(instance_id, fan::Request::GetRampTemp).await, + uuid_standard::FAN_MAX_TEMP => fan_get_temp(instance_id, fan::Request::GetMaxTemp).await, + uuid_standard::FAN_MIN_RPM => fan_get_rpm(instance_id, fan::Request::GetMinRpm).await, + uuid_standard::FAN_MAX_RPM => fan_get_rpm(instance_id, fan::Request::GetMaxRpm).await, + uuid_standard::FAN_CURRENT_RPM => fan_get_rpm(instance_id, fan::Request::GetRpm).await, + // TODO: Allow OEM to handle these? + uuid => { + error!("Received GetVar for unrecognized UUID: {:?}", uuid); + Err(thermal_service_messages::ThermalError::InvalidParameter) + } } } -async fn set_var_handler(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalSetVarRequest { - instance_id, - len: _, - var_uuid, - set_var, - } => match var_uuid { - uuid_standard::CRT_TEMP => { - let Response { status: _, data } = - sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - uuid_standard::PROC_HOT_TEMP => { - let Response { status: _, data } = - sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - // TODO: Add a SetProfileId request type? But for sensor or fan? - uuid_standard::PROFILE_TYPE => { - todo!() - } - uuid_standard::FAN_ON_TEMP => { - let Response { status: _, data } = - fan_set_var(instance_id, fan::Request::SetOnTemp(utils::dk_to_c(set_var))).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - uuid_standard::FAN_RAMP_TEMP => { - let Response { status: _, data } = - fan_set_var(instance_id, fan::Request::SetRampTemp(utils::dk_to_c(set_var))).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - uuid_standard::FAN_MAX_TEMP => { - let Response { status: _, data } = - fan_set_var(instance_id, fan::Request::SetMaxTemp(utils::dk_to_c(set_var))).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? - uuid_standard::FAN_MIN_RPM => { - todo!() - } - // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? - uuid_standard::FAN_MAX_RPM => { - todo!() - } - uuid_standard::FAN_CURRENT_RPM => { - let Response { status: _, data } = fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16)).await; - if let ResponseData::SetVar(Status::Success) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.status = error.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - // TODO: Allow OEM to handle these? - uuid => { - error!("Received SetVar for unrecognized UUID: {:?}", uuid); - request.status = Status::InvalidParameter.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::InvalidParameter.into(), - } - } - }, - _ => error!("Thermal Service: Host message header and payload mismatch"), +async fn set_var_handler( + instance_id: u8, + var_uuid: uuid::Bytes, + set_var: u32, +) -> thermal_service_messages::ThermalResult { + match var_uuid { + uuid_standard::CRT_TEMP => sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var).await, + uuid_standard::PROC_HOT_TEMP => sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var).await, + // TODO: Add a SetProfileId request type? But for sensor or fan? + uuid_standard::PROFILE_TYPE => { + todo!() + } + uuid_standard::FAN_ON_TEMP => fan_set_var(instance_id, fan::Request::SetOnTemp(utils::dk_to_c(set_var))).await, + uuid_standard::FAN_RAMP_TEMP => { + fan_set_var(instance_id, fan::Request::SetRampTemp(utils::dk_to_c(set_var))).await + } + uuid_standard::FAN_MAX_TEMP => { + fan_set_var(instance_id, fan::Request::SetMaxTemp(utils::dk_to_c(set_var))).await + } + // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? + uuid_standard::FAN_MIN_RPM => { + todo!() + } + // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? + uuid_standard::FAN_MAX_RPM => { + todo!() + } + uuid_standard::FAN_CURRENT_RPM => fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16)).await, + // TODO: Allow OEM to handle these? + uuid => { + error!("Received SetVar for unrecognized UUID: {:?}", uuid); + Err(thermal_service_messages::ThermalError::InvalidParameter) + } } } -async fn sensor_get_warn_thrs(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalGetThrsRequest { instance_id } => { - let low = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), - ) - .await; - let high = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), - ) - .await; - - match (low, high) { - (Ok(sensor::ResponseData::Threshold(low)), Ok(sensor::ResponseData::Threshold(high))) => { - request.payload = StdHostPayload::ThermalGetThrsResponse { - status: 0, - timeout: 0, - low: utils::c_to_dk(low), - high: utils::c_to_dk(high), - }; - request.status = 0; - } - _ => { - request.payload = StdHostPayload::ThermalGetThrsResponse { - status: 1, - timeout: 0, - low: 0, - high: 0, - }; - request.status = 1; - } - } +async fn sensor_get_warn_thrs(instance_id: u8) -> thermal_service_messages::ThermalResult { + let low = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), + ) + .await; + let high = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), + ) + .await; + + match (low, high) { + (Ok(sensor::ResponseData::Threshold(low)), Ok(sensor::ResponseData::Threshold(high))) => { + Ok(thermal_service_messages::ThermalResponse::ThermalGetThrsResponse { + timeout: 0, + low: utils::c_to_dk(low), + high: utils::c_to_dk(high), + }) } - _ => error!("Thermal Service: Host message header and payload mismatch"), + _ => Err(thermal_service_messages::ThermalError::InvalidParameter), } } -async fn sensor_set_warn_thrs(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalSetThrsRequest { - instance_id, - timeout: _, - low, - high, - } => { - let low_res = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), - ) - .await; - let high_res = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), - ) - .await; +async fn sensor_set_warn_thrs( + instance_id: u8, + _timeout: Milliseconds, + low: DeciKelvin, + high: DeciKelvin, +) -> thermal_service_messages::ThermalResult { + let low_res = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), + ) + .await; + let high_res = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), + ) + .await; - if low_res.is_ok() && high_res.is_ok() { - request.payload = mctp::Odp::ThermalSetThrsResponse { status: 0 }; - request.status = 0; - } else { - request.payload = mctp::Odp::ThermalSetThrsResponse { status: 1 }; - request.status = 1; - } - } - _ => error!("Thermal Service: Host message header and payload mismatch"), + if low_res.is_ok() && high_res.is_ok() { + Ok(thermal_service_messages::ThermalResponse::ThermalSetThrsResponse) + } else { + Err(thermal_service_messages::ThermalError::InvalidParameter) } } -async fn sensor_get_thrs(instance: u8, threshold_type: sensor::ThresholdType) -> Response { +async fn sensor_get_thrs( + instance: u8, + threshold_type: sensor::ThresholdType, +) -> thermal_service_messages::ThermalResult { match ts::execute_sensor_request( sensor::DeviceId(instance), sensor::Request::GetThreshold(threshold_type), ) .await { - Ok(sensor::ResponseData::Temp(temp)) => Response::new( - Status::Success, - ResponseData::GetVar(Status::Success, utils::c_to_dk(temp)), - ), - _ => Response::new(Status::Success, ResponseData::GetVar(Status::HardwareError, 0)), + Ok(sensor::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { + val: utils::c_to_dk(temp), + }), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_get_temp(instance: u8, fan_request: fan::Request) -> Response { +async fn fan_get_temp(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { - Ok(fan::ResponseData::Temp(temp)) => Response::new( - Status::Success, - ResponseData::GetVar(Status::Success, utils::c_to_dk(temp)), - ), - _ => Response::new(Status::Success, ResponseData::GetVar(Status::HardwareError, 0)), + Ok(fan::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { + val: utils::c_to_dk(temp), + }), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_get_rpm(instance: u8, fan_request: fan::Request) -> Response { +async fn fan_get_rpm(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { Ok(fan::ResponseData::Rpm(rpm)) => { - Response::new(Status::Success, ResponseData::GetVar(Status::Success, rpm as u32)) + Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) } - _ => Response::new(Status::Success, ResponseData::GetVar(Status::HardwareError, 0)), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn sensor_set_thrs(instance: u8, threshold_type: sensor::ThresholdType, threshold_dk: Dword) -> Response { +async fn sensor_set_thrs( + instance: u8, + threshold_type: sensor::ThresholdType, + threshold_dk: u32, +) -> thermal_service_messages::ThermalResult { match ts::execute_sensor_request( sensor::DeviceId(instance), sensor::Request::SetThreshold(threshold_type, utils::dk_to_c(threshold_dk)), ) .await { - Ok(sensor::ResponseData::Success) => Response::new(Status::Success, ResponseData::SetVar(Status::Success)), - _ => Response::new(Status::Success, ResponseData::SetVar(Status::HardwareError)), + Ok(sensor::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_set_var(instance: u8, fan_request: fan::Request) -> Response { +async fn fan_set_var(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { - Ok(fan::ResponseData::Success) => Response::new(Status::Success, ResponseData::SetVar(Status::Success)), - _ => Response::new(Status::Success, ResponseData::SetVar(Status::HardwareError)), + Ok(fan::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -pub(crate) async fn process_request(request: &mut StdHostRequest) { - match request.command { - embedded_services::ec_type::message::OdpCommand::Thermal(thermal_msg) => match thermal_msg { - embedded_services::ec_type::protocols::mptf::ThermalCmd::GetTmp => sensor_get_tmp(request).await, - embedded_services::ec_type::protocols::mptf::ThermalCmd::SetThrs => sensor_set_warn_thrs(request).await, - embedded_services::ec_type::protocols::mptf::ThermalCmd::GetThrs => sensor_get_warn_thrs(request).await, - // TODO: How do we handle this genericly? - embedded_services::ec_type::protocols::mptf::ThermalCmd::SetScp => todo!(), - embedded_services::ec_type::protocols::mptf::ThermalCmd::GetVar => get_var_handler(request).await, - embedded_services::ec_type::protocols::mptf::ThermalCmd::SetVar => set_var_handler(request).await, - }, - _ => error!("Thermal Service: Recvd other subsystem host message"), +pub(crate) async fn process_request( + request: &thermal_service_messages::ThermalRequest, +) -> thermal_service_messages::ThermalResult { + match request { + thermal_service_messages::ThermalRequest::ThermalGetTmpRequest { instance_id } => { + sensor_get_tmp(*instance_id).await + } + thermal_service_messages::ThermalRequest::ThermalSetThrsRequest { + instance_id, + timeout, + low, + high, + } => sensor_set_warn_thrs(*instance_id, *timeout, *low, *high).await, + thermal_service_messages::ThermalRequest::ThermalGetThrsRequest { instance_id } => { + sensor_get_warn_thrs(*instance_id).await + } + // TODO: How do we handle this generically? + thermal_service_messages::ThermalRequest::ThermalSetScpRequest { .. } => todo!(), + thermal_service_messages::ThermalRequest::ThermalGetVarRequest { + instance_id, + len: _len, + var_uuid, + } => get_var_handler(*instance_id, *var_uuid).await, + thermal_service_messages::ThermalRequest::ThermalSetVarRequest { + instance_id, + len: _len, + var_uuid, + set_var, + } => set_var_handler(*instance_id, *var_uuid, *set_var).await, } } diff --git a/thermal-service/src/task.rs b/thermal-service/src/task.rs index 82d21427..d66bc95b 100644 --- a/thermal-service/src/task.rs +++ b/thermal-service/src/task.rs @@ -4,11 +4,12 @@ use crate::{self as ts, mptf::process_request}; pub async fn handle_requests() { loop { - let mut request = ts::wait_mctp_payload().await; - process_request(&mut request).await; + let request = ts::wait_mptf_request().await; + let result = process_request(&request).await; let send_result = ts::send_service_msg( + // TODO we should probably respond to the endpoint that requested us rather than hardcoding the return address like this comms::EndpointID::External(comms::External::Host), - &embedded_services::ec_type::message::HostMsg::Response(request), + &result, ) .await; diff --git a/thermal-service/src/utils.rs b/thermal-service/src/utils.rs index 4497fc98..58c70c12 100644 --- a/thermal-service/src/utils.rs +++ b/thermal-service/src/utils.rs @@ -1,5 +1,4 @@ //! Helpful utilities for the thermal service -use crate::mptf; use heapless::Deque; /// Buffer for storing samples @@ -42,11 +41,11 @@ impl SampleBuf { } /// Convert deciKelvin to degrees Celsius -pub const fn dk_to_c(dk: mptf::DeciKelvin) -> f32 { +pub const fn dk_to_c(dk: thermal_service_messages::DeciKelvin) -> f32 { (dk as f32 / 10.0) - 273.15 } /// Convert degrees Celsius to deciKelvin -pub const fn c_to_dk(c: f32) -> mptf::DeciKelvin { - ((c + 273.15) * 10.0) as mptf::DeciKelvin +pub const fn c_to_dk(c: f32) -> thermal_service_messages::DeciKelvin { + ((c + 273.15) * 10.0) as thermal_service_messages::DeciKelvin }