Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ version = "0.8.6"
criteria = "safe-to-deploy"

[[exemptions.crossbeam-epoch]]
version = "0.9.18"
version = "0.9.20"
criteria = "safe-to-deploy"

[[exemptions.crossbeam-utils]]
Expand Down
4 changes: 2 additions & 2 deletions ec/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 50 additions & 1 deletion ec/test-lib/src/acpi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{BatterySource, ErrorType, RtcSource, ThermalSource, Threshold, common};
use crate::ucsi::{self, UcsiCapability, UcsiConnectorCapability, UcsiConnectorStatus, UcsiVersion};
use crate::{BatterySource, ErrorType, RtcSource, ThermalSource, Threshold, UcsiSource, common};
use battery_service_interface::{
BatteryState, BatterySwapCapability, BatteryTechnology, BixFixedStrings, BstReturn, PowerUnit,
};
Expand Down Expand Up @@ -148,6 +149,8 @@ pub enum Error {
OperationFailed,
/// Data validation failed (invalid enum discriminant, malformed field, etc.)
InvalidData,
/// Decoding a UCSI mailbox response failed
Ucsi(ucsi::MailboxError),
}

impl std::fmt::Display for Error {
Expand All @@ -158,6 +161,7 @@ impl std::fmt::Display for Error {
Self::UnexpectedArgumentType(t) => write!(f, "Unexpected argument type: {t}"),
Self::OperationFailed => write!(f, "Operation failed"),
Self::InvalidData => write!(f, "Invalid data"),
Self::Ucsi(e) => write!(f, "UCSI mailbox error: {e}"),
}
}
}
Expand All @@ -172,10 +176,17 @@ impl crate::Error for Error {
Self::UnexpectedArgumentType(_) => crate::ErrorKind::UnexpectedResponse,
Self::OperationFailed => crate::ErrorKind::Other,
Self::InvalidData => crate::ErrorKind::InvalidData,
Self::Ucsi(_) => crate::ErrorKind::InvalidData,
}
}
}

impl From<ucsi::MailboxError> for Error {
fn from(e: ucsi::MailboxError) -> Self {
Self::Ucsi(e)
}
}

impl From<AcpiParseError> for Error {
fn from(e: AcpiParseError) -> Self {
Self::Parse(e)
Expand Down Expand Up @@ -694,3 +705,41 @@ impl RtcSource for Acpi {
Ok(())
}
}

impl Acpi {
/// Issue one UCSI command by writing the 8-byte CONTROL buffer to
/// `\_SB.ECT0.USND` and returning the raw 48-byte mailbox response.
fn ucsi_command(&self, control: [u8; ucsi::CONTROL_LEN]) -> Result<Vec<u8>, Error> {
let output = self.evaluate("\\_SB.ECT0.USND", Some(&[AcpiMethodArgument::Buffer(control.to_vec())]))?;
if output.count != 1 {
return Err(Error::UnexpectedResponse);
}
let arg = output.arg(0)?;
if arg.type_ != AcpiArgumentType::Buffer as u16 {
return Err(Error::UnexpectedArgumentType(arg.type_));
}
Ok(arg.data.clone())
}
}

impl UcsiSource for Acpi {
fn get_version(&self) -> Result<UcsiVersion, Self::Error> {
let mailbox = self.ucsi_command(ucsi::control(ucsi::opcode::GET_CAPABILITY, 0))?;
Ok(ucsi::decode_version(&mailbox)?)
}

fn get_capability(&self) -> Result<UcsiCapability, Self::Error> {
let mailbox = self.ucsi_command(ucsi::control(ucsi::opcode::GET_CAPABILITY, 0))?;
Ok(ucsi::decode_capability(&mailbox)?)
}

fn get_connector_capability(&self, connector: u8) -> Result<UcsiConnectorCapability, Self::Error> {
let mailbox = self.ucsi_command(ucsi::control(ucsi::opcode::GET_CONNECTOR_CAPABILITY, connector))?;
Ok(ucsi::decode_connector_capability(&mailbox)?)
}

fn get_connector_status(&self, connector: u8) -> Result<UcsiConnectorStatus, Self::Error> {
let mailbox = self.ucsi_command(ucsi::control(ucsi::opcode::GET_CONNECTOR_STATUS, connector))?;
Ok(ucsi::decode_connector_status(&mailbox)?)
}
}
37 changes: 35 additions & 2 deletions ec/test-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ use time_alarm_service_interface::{
AcpiTimerId, AcpiTimestamp, AlarmExpiredWakePolicy, AlarmTimerSeconds, TimeAlarmDeviceCapabilities, TimerStatus,
};

use crate::ucsi::{UcsiCapability, UcsiConnectorCapability, UcsiConnectorStatus, UcsiVersion};

pub(crate) mod common;

#[cfg(target_os = "windows")]
pub mod acpi;

pub mod mock;
pub mod serial;
pub mod ucsi;

/// EC data source error.
///
Expand Down Expand Up @@ -153,9 +156,24 @@ pub trait RtcSource: ErrorType {
fn clear_wake_status(&self, timer_id: AcpiTimerId) -> Result<(), Self::Error>;
}

/// Trait for host-side UCSI data sources (read-only PPM/connector queries).
pub trait UcsiSource: ErrorType {
/// Get the UCSI interface version - see UCSI mailbox VERSION.
fn get_version(&self) -> Result<UcsiVersion, Self::Error>;

/// Get PPM capabilities - see GET_CAPABILITY.
fn get_capability(&self) -> Result<UcsiCapability, Self::Error>;

/// Get per-connector capabilities - see GET_CONNECTOR_CAPABILITY.
fn get_connector_capability(&self, connector: u8) -> Result<UcsiConnectorCapability, Self::Error>;

/// Get connector status - see GET_CONNECTOR_STATUS.
fn get_connector_status(&self, connector: u8) -> Result<UcsiConnectorStatus, Self::Error>;
}

/// Marker trait implemented by all EC data sources.
pub trait Source: ThermalSource + BatterySource + RtcSource {}
impl<T: ThermalSource + BatterySource + RtcSource> Source for T {}
pub trait Source: ThermalSource + BatterySource + RtcSource + UcsiSource {}
impl<T: ThermalSource + BatterySource + RtcSource + UcsiSource> Source for T {}

// Blanket impls so that Arc<S> can be used anywhere a source trait is required.
// This lets modules share one source instance via Arc instead of each owning a clone.
Expand Down Expand Up @@ -235,6 +253,21 @@ impl<T: RtcSource> RtcSource for Arc<T> {
}
}

impl<T: UcsiSource> UcsiSource for Arc<T> {
fn get_version(&self) -> Result<UcsiVersion, Self::Error> {
self.as_ref().get_version()
}
fn get_capability(&self) -> Result<UcsiCapability, Self::Error> {
self.as_ref().get_capability()
}
fn get_connector_capability(&self, connector: u8) -> Result<UcsiConnectorCapability, Self::Error> {
self.as_ref().get_connector_capability(connector)
}
fn get_connector_status(&self, connector: u8) -> Result<UcsiConnectorStatus, Self::Error> {
self.as_ref().get_connector_status(connector)
}
}

/// Fan threshold type
pub enum Threshold {
/// On threshold temperature
Expand Down
32 changes: 31 additions & 1 deletion ec/test-lib/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{BatterySource, ErrorType, RtcSource, ThermalSource, Threshold};
use crate::ucsi::{PowerDirection, UcsiCapability, UcsiConnectorCapability, UcsiConnectorStatus, UcsiVersion};
use crate::{BatterySource, ErrorType, RtcSource, ThermalSource, Threshold, UcsiSource};
use battery_service_interface::{
BatteryState, BatterySwapCapability, BatteryTechnology, BixFixedStrings, BstReturn, PowerUnit,
};
Expand Down Expand Up @@ -444,3 +445,32 @@ impl RtcSource for Mock {
Ok(())
}
}

impl UcsiSource for Mock {
fn get_version(&self) -> Result<UcsiVersion, Self::Error> {
Ok(UcsiVersion(0x0120))
}
fn get_capability(&self) -> Result<UcsiCapability, Self::Error> {
Ok(UcsiCapability {
num_connectors: 1,
usb_pd_supported: true,
bcd_pd_version: 0x0300,
})
}
fn get_connector_capability(&self, _connector: u8) -> Result<UcsiConnectorCapability, Self::Error> {
Ok(UcsiConnectorCapability {
drp: true,
usb2: true,
usb3: true,
provider: true,
consumer: true,
})
}
fn get_connector_status(&self, _connector: u8) -> Result<UcsiConnectorStatus, Self::Error> {
Ok(UcsiConnectorStatus {
connected: true,
power_direction: PowerDirection::Sink,
partner_usb: true,
})
}
}
37 changes: 36 additions & 1 deletion ec/test-lib/src/serial.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{BatterySource, ErrorType, RtcSource, ThermalSource, Threshold, common};
use crate::{BatterySource, ErrorType, RtcSource, ThermalSource, Threshold, UcsiSource, common};
use battery_service_interface::{BixFixedStrings, BstReturn, Btp};
use battery_service_relay::{AcpiBatteryRequest, AcpiBatteryResponse};
use embedded_services::relay::SerializableMessage;
Expand All @@ -17,6 +17,8 @@ use time_alarm_service_interface::{
};
use time_alarm_service_relay::{AcpiTimeAlarmRequest, AcpiTimeAlarmResponse};

use crate::ucsi::{UcsiCapability, UcsiConnectorCapability, UcsiConnectorStatus, UcsiVersion};

/// Errors produced by serial data source operations.
#[derive(Debug)]
pub enum Error {
Expand All @@ -28,6 +30,8 @@ pub enum Error {
Serialization(String),
/// Response had an unexpected format
UnexpectedResponse,
/// Operation is not supported by the serial backend (no EC-side peer)
Unsupported(&'static str),
}

impl std::fmt::Display for Error {
Expand All @@ -37,6 +41,7 @@ impl std::fmt::Display for Error {
Self::Protocol(msg) => write!(f, "serial protocol error: {msg}"),
Self::Serialization(msg) => write!(f, "serialization error: {msg}"),
Self::UnexpectedResponse => write!(f, "unexpected response"),
Self::Unsupported(what) => write!(f, "unsupported over serial: {what}"),
}
}
}
Expand All @@ -50,6 +55,7 @@ impl crate::Error for Error {
Self::Protocol(_) => crate::ErrorKind::Protocol,
Self::Serialization(_) => crate::ErrorKind::Serialization,
Self::UnexpectedResponse => crate::ErrorKind::UnexpectedResponse,
Self::Unsupported(_) => crate::ErrorKind::Other,
}
}
}
Expand Down Expand Up @@ -545,3 +551,32 @@ impl RtcSource for Serial {
}
}
}

/// The serial backend has no EC-side UCSI relay peer, so every UCSI read is
/// explicitly unsupported (mapped to [`crate::ErrorKind::Other`]) rather than
/// faking success.
impl UcsiSource for Serial {
fn get_version(&self) -> Result<UcsiVersion, Self::Error> {
Err(Error::Unsupported("UCSI get_version"))
}
fn get_capability(&self) -> Result<UcsiCapability, Self::Error> {
Err(Error::Unsupported("UCSI get_capability"))
}
fn get_connector_capability(&self, _connector: u8) -> Result<UcsiConnectorCapability, Self::Error> {
Err(Error::Unsupported("UCSI get_connector_capability"))
}
fn get_connector_status(&self, _connector: u8) -> Result<UcsiConnectorStatus, Self::Error> {
Err(Error::Unsupported("UCSI get_connector_status"))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::Error as _;

#[test]
fn unsupported_maps_to_other_kind() {
assert_eq!(Error::Unsupported("UCSI get_version").kind(), crate::ErrorKind::Other);
}
}
Loading
Loading