Skip to content

Commit f3e4550

Browse files
authored
type-c-service/wrapper: Disable sink path during voltage transition (#861)
The existing behavior can cause overcurrent/overvoltage conditions during the renegotiation. Disconnect power during the transition and introduce flags to help users of this code detect this condition.
1 parent 6034c6b commit f3e4550

11 files changed

Lines changed: 139 additions & 30 deletions

File tree

battery-service/src/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ impl Context {
527527
let psu_state = guard.deref_mut();
528528

529529
match power_info {
530-
embedded_services::power::policy::CommsData::ConsumerDisconnected(_) => {
530+
embedded_services::power::policy::CommsData::ConsumerDisconnected(..) => {
531531
*psu_state = PsuState {
532532
psu_connected: false,
533533
power_capability: None,

embedded-service/src/power/policy/action/device.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Device state machine actions
22
use super::*;
3-
use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device, policy};
3+
use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device, flags, policy};
44
use crate::{info, trace};
55

66
/// Device state machine control
@@ -55,12 +55,12 @@ impl<'a, S: Kind> Device<'a, S> {
5555
}
5656

5757
/// Disconnect this device
58-
async fn disconnect_internal(&self) -> Result<(), Error> {
58+
async fn disconnect_internal(&self, flags: flags::ConsumerDisconnect) -> Result<(), Error> {
5959
info!("Device {} disconnecting", self.device.id().0);
6060
self.device.update_consumer_capability(None).await;
6161
self.device.update_requested_provider_capability(None).await;
6262
self.device.set_state(device::State::Idle).await;
63-
policy::send_request(self.device.id(), policy::RequestData::NotifyDisconnect)
63+
policy::send_request(self.device.id(), policy::RequestData::NotifyDisconnect(flags))
6464
.await?
6565
.complete_or_err()
6666
}
@@ -136,8 +136,8 @@ impl Device<'_, Idle> {
136136

137137
impl<'a> Device<'a, ConnectedConsumer> {
138138
/// Disconnect this device
139-
pub async fn disconnect(self) -> Result<Device<'a, Idle>, Error> {
140-
self.disconnect_internal().await?;
139+
pub async fn disconnect(self, flags: flags::ConsumerDisconnect) -> Result<Device<'a, Idle>, Error> {
140+
self.disconnect_internal(flags).await?;
141141
Ok(Device::new(self.device))
142142
}
143143

@@ -152,8 +152,8 @@ impl<'a> Device<'a, ConnectedConsumer> {
152152

153153
impl<'a> Device<'a, ConnectedProvider> {
154154
/// Disconnect this device
155-
pub async fn disconnect(self) -> Result<Device<'a, Idle>, Error> {
156-
self.disconnect_internal().await?;
155+
pub async fn disconnect(self, flags: flags::ConsumerDisconnect) -> Result<Device<'a, Idle>, Error> {
156+
self.disconnect_internal(flags).await?;
157157
Ok(Device::new(self.device))
158158
}
159159

embedded-service/src/power/policy/flags.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,72 @@ impl Provider {
127127
}
128128
}
129129

130+
bitfield! {
131+
/// Flags for disconnect events
132+
#[derive(Copy, Clone, PartialEq, Eq)]
133+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
134+
struct ConsumerDisconnectRaw(u32);
135+
impl Debug;
136+
/// Renegotiation
137+
///
138+
/// When set this flag indicates that the current consumer is attempting to negotiate a new power capability.
139+
pub bool, renegotiation, set_renegotiation: 0;
140+
/// Switching
141+
///
142+
/// When set this flag indicates that the service is switching to a different PSU.
143+
pub bool, switching, set_switching: 1;
144+
}
145+
146+
/// Type safe wrapper for consumer disconnect flags
147+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
148+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
149+
pub struct ConsumerDisconnect(ConsumerDisconnectRaw);
150+
151+
impl ConsumerDisconnect {
152+
/// Create new consumer disconnect flags with no flags set
153+
pub const fn none() -> Self {
154+
Self(ConsumerDisconnectRaw(0))
155+
}
156+
157+
/// Builder method to set the renegotiation flag
158+
pub fn with_renegotiation(mut self, value: bool) -> Self {
159+
self.set_renegotiation(value);
160+
self
161+
}
162+
163+
/// Set the value of the renegotiation flag
164+
pub fn set_renegotiation(&mut self, value: bool) {
165+
self.0.set_renegotiation(value);
166+
}
167+
168+
/// Get the value of the renegotiation flag
169+
pub fn renegotiation(&self) -> bool {
170+
self.0.renegotiation()
171+
}
172+
173+
/// Builder method to set the switching flag
174+
pub fn with_switching(mut self, value: bool) -> Self {
175+
self.set_switching(value);
176+
self
177+
}
178+
179+
/// Set the value of the switching flag
180+
pub fn set_switching(&mut self, value: bool) {
181+
self.0.set_switching(value);
182+
}
183+
184+
/// Get the value of the switching flag
185+
pub fn switching(&self) -> bool {
186+
self.0.switching()
187+
}
188+
}
189+
190+
impl Default for ConsumerDisconnect {
191+
fn default() -> Self {
192+
Self::none()
193+
}
194+
}
195+
130196
#[cfg(test)]
131197
mod tests {
132198
use super::*;
@@ -180,4 +246,20 @@ mod tests {
180246
provider.set_psu_type(PsuType::Unknown);
181247
assert_eq!(provider.0.0, 0x0);
182248
}
249+
250+
#[test]
251+
fn test_consumer_disconnect_renegotiation() {
252+
let mut disconnect = ConsumerDisconnect::none().with_renegotiation(true);
253+
assert_eq!(disconnect.0.0, 0x1);
254+
disconnect.set_renegotiation(false);
255+
assert_eq!(disconnect.0.0, 0x0);
256+
}
257+
258+
#[test]
259+
fn test_consumer_disconnect_switching() {
260+
let mut disconnect = ConsumerDisconnect::none().with_switching(true);
261+
assert_eq!(disconnect.0.0, 0x2);
262+
disconnect.set_switching(false);
263+
assert_eq!(disconnect.0.0, 0x0);
264+
}
183265
}

embedded-service/src/power/policy/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ impl UnconstrainedState {
142142
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
143143
pub enum CommsData {
144144
/// Consumer disconnected
145-
ConsumerDisconnected(DeviceId),
145+
ConsumerDisconnected(DeviceId, flags::ConsumerDisconnect),
146146
/// Consumer connected
147147
ConsumerConnected(DeviceId, ConsumerPowerCapability),
148148
/// Provider disconnected

embedded-service/src/power/policy/policy.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core::sync::atomic::{AtomicBool, Ordering};
33

44
use crate::GlobalRawMutex;
55
use crate::broadcaster::immediate as broadcaster;
6-
use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability};
6+
use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability, flags};
77
use embassy_sync::channel::Channel;
88

99
use super::charger::ChargerResponse;
@@ -26,7 +26,7 @@ pub enum RequestData {
2626
/// Request the given amount of power to provider
2727
RequestProviderCapability(ProviderPowerCapability),
2828
/// Notify that a device cannot consume or provide power anymore
29-
NotifyDisconnect,
29+
NotifyDisconnect(flags::ConsumerDisconnect),
3030
/// Notify that a device has detached
3131
NotifyDetached,
3232
}

power-policy-service/src/consumer.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,10 @@ impl PowerPolicy {
218218
self.disconnect_chargers().await?;
219219

220220
self.comms_notify(CommsMessage {
221-
data: CommsData::ConsumerDisconnected(current_consumer.device_id),
221+
data: CommsData::ConsumerDisconnected(
222+
current_consumer.device_id,
223+
flags::ConsumerDisconnect::default().with_switching(true),
224+
),
222225
})
223226
.await;
224227

@@ -268,7 +271,10 @@ impl PowerPolicy {
268271
if let Some(consumer_state) = state.current_consumer_state {
269272
self.disconnect_chargers().await?;
270273
self.comms_notify(CommsMessage {
271-
data: CommsData::ConsumerDisconnected(consumer_state.device_id),
274+
data: CommsData::ConsumerDisconnected(
275+
consumer_state.device_id,
276+
flags::ConsumerDisconnect::default(),
277+
),
272278
})
273279
.await;
274280
}

power-policy-service/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ impl PowerPolicy {
7777
Ok(())
7878
}
7979

80-
async fn process_notify_disconnect(&self, device: &device::Device) -> Result<(), Error> {
80+
async fn process_notify_disconnect(
81+
&self,
82+
device: &device::Device,
83+
flags: flags::ConsumerDisconnect,
84+
) -> Result<(), Error> {
8185
self.context.send_response(Ok(policy::ResponseData::Complete)).await;
8286

8387
self.remove_connected_provider(device.id()).await;
@@ -93,7 +97,7 @@ impl PowerPolicy {
9397
self.disconnect_chargers().await?;
9498

9599
self.comms_notify(CommsMessage {
96-
data: CommsData::ConsumerDisconnected(consumer.device_id),
100+
data: CommsData::ConsumerDisconnected(consumer.device_id, flags),
97101
})
98102
.await;
99103

@@ -175,9 +179,9 @@ impl PowerPolicy {
175179
);
176180
self.process_request_provider_power_capabilities(device.id()).await
177181
}
178-
policy::RequestData::NotifyDisconnect => {
182+
policy::RequestData::NotifyDisconnect(flags) => {
179183
info!("Received notify disconnect from device {}", device.id().0);
180-
self.process_notify_disconnect(device).await
184+
self.process_notify_disconnect(device, flags).await
181185
}
182186
}
183187
}

type-c-service/src/service/power.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ impl<'a> Service<'a> {
1616
power_policy::CommsData::Unconstrained(state) => {
1717
return Event::PowerPolicy(PowerPolicyEvent::Unconstrained(state));
1818
}
19-
power_policy::CommsData::ConsumerDisconnected(_) => {
19+
power_policy::CommsData::ConsumerDisconnected(..) => {
2020
return Event::PowerPolicy(PowerPolicyEvent::ConsumerDisconnected);
2121
}
2222
power_policy::CommsData::ConsumerConnected(_, _) => {

type-c-service/src/wrapper/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use embassy_time::Instant;
2929
use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion};
3030
use embedded_services::GlobalRawMutex;
3131
use embedded_services::power::policy::device::StateKind;
32-
use embedded_services::power::policy::{self, action};
32+
use embedded_services::power::policy::{self, action, flags};
3333
use embedded_services::sync::Lockable;
3434
use embedded_services::type_c::controller::{self, Controller, PortStatus};
3535
use embedded_services::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged};
@@ -267,15 +267,21 @@ where
267267
),
268268
_ => {}
269269
}
270-
if let Err(e) = connected_consumer.disconnect().await {
270+
if let Err(e) = connected_consumer
271+
.disconnect(flags::ConsumerDisconnect::default())
272+
.await
273+
{
271274
error!(
272275
"Port{}: Error disconnecting from ConnectedConsumer after PD hard reset: {:#?}",
273276
global_port_id.0, e
274277
);
275278
}
276279
} else if let Ok(connected_provider) = power.try_device_action::<action::ConnectedProvider>().await {
277280
info!("Port{}: Disconnecting provider after hard reset", global_port_id.0);
278-
if let Err(e) = connected_provider.disconnect().await {
281+
if let Err(e) = connected_provider
282+
.disconnect(flags::ConsumerDisconnect::default())
283+
.await
284+
{
279285
error!(
280286
"Port{}: Error disconnecting from ConnectedProvider after PD hard reset: {:#?}",
281287
global_port_id.0, e

type-c-service/src/wrapper/pd.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,31 @@ where
144144
debug!("Port{}: Current state: {:#?}", local_port.0, state);
145145
if let Ok(connected_consumer) = power_device.try_device_action::<action::ConnectedConsumer>().await {
146146
debug!("Port{}: Set max sink voltage, connected consumer found", local_port.0);
147-
if voltage_mv.is_some()
148-
&& voltage_mv
149-
< power_device
147+
148+
// If the new max voltage is None (remove max voltage limit) or different from the current consumer capability then
149+
// we need to disconnect because a change in max voltage requires renegotiation
150+
if voltage_mv.is_none()
151+
|| voltage_mv
152+
!= power_device
150153
.consumer_capability()
151154
.await
152155
.map(|c| c.capability.voltage_mv)
153156
{
154-
// New max voltage is lower than current consumer capability which will trigger a renegociation
155-
// So disconnect first
156157
debug!(
157158
"Port{}: Disconnecting consumer before setting max sink voltage",
158159
local_port.0
159160
);
160-
let _ = connected_consumer.disconnect().await;
161+
162+
match controller.enable_sink_path(local_port, false).await {
163+
Ok(()) => Ok(controller::PortResponseData::Complete),
164+
Err(e) => match e {
165+
Error::Bus(_) => Err(PdError::Failed),
166+
Error::Pd(e) => Err(e),
167+
},
168+
}?;
169+
let _ = connected_consumer
170+
.disconnect(flags::ConsumerDisconnect::default().with_renegotiation(true))
171+
.await;
161172
}
162173
}
163174

0 commit comments

Comments
 (0)