blob: a6700c1c822f41ba71130cf1d94887da50eb8029 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/devices/power/drivers/fusb302/pd-sink-state-machine.h"
#include <lib/driver/logging/cpp/logger.h>
#include <lib/inspect/cpp/vmo/types.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <cinttypes>
#include <cstdint>
#include <utility>
#include "src/devices/power/drivers/fusb302/fusb302.h"
#include "src/devices/power/drivers/fusb302/registers.h"
#include "src/devices/power/drivers/fusb302/usb-pd-defs.h"
#include "src/devices/power/drivers/fusb302/usb-pd-message-objects.h"
#include "src/devices/power/drivers/fusb302/usb-pd-message-type.h"
#include "src/devices/power/drivers/fusb302/usb-pd-message.h"
namespace fusb302 {
namespace {
// If we're still waiting for Sink_Capabilities after this time, Soft Reset.
//
// We use a timeout significantly larger than recommended by the USB PD spec.
// This lets us do the PD re-negotiation after most Fuchsia components have
// started up, which maximizes the chance that we'll meet the
// Request-to-Source_Capabilities timing request.
constexpr zx::duration kWaitForSourceCapabilitiesTimeout = zx::msec(10'000);
} // namespace
void SinkPolicyEngineStateMachine::Reset() {
ForceStateTransition(SinkPolicyEngineState::kStartup);
}
void SinkPolicyEngineStateMachine::DidReceiveSoftReset() {
ForceStateTransition(SinkPolicyEngineState::kSoftReset);
}
// The implementation follows Section 8.3.3.3 "Policy Engine Sink Port State
// Diagram" and Section 8.3.3.4.2 "SOP Sink Port Soft Reset and Protocol Error
// State Diagram" of the USB PD spec.
void SinkPolicyEngineStateMachine::EnterState(SinkPolicyEngineState state) {
switch (state) {
case SinkPolicyEngineState::kStartup:
CreateTimersIfNeeded();
InitializeProtocolLayer();
return;
case SinkPolicyEngineState::kDiscovery:
// No setup needed.
return;
case SinkPolicyEngineState::kWaitForCapabilities:
ArmWaitForSourceCapabilitiesTimer();
return;
case SinkPolicyEngineState::kEvaluateCapability:
return;
case SinkPolicyEngineState::kSelectCapability: {
const uint32_t data_objects[] = {static_cast<uint32_t>(policy_.GetPowerRequest())};
usb_pd::Message power_request(usb_pd::MessageType::kRequestPower,
device_.protocol().next_transmitted_message_id(),
device_.controls().power_role(), usb_pd::SpecRevision::kRev2,
device_.controls().data_role(), data_objects);
[[maybe_unused]] zx::result<> result = device_.protocol().Transmit(power_request);
return;
}
case SinkPolicyEngineState::kTransitionSink:
case SinkPolicyEngineState::kReady:
// No setup needed.
return;
case SinkPolicyEngineState::kGiveSinkCapabilities: {
ZX_DEBUG_ASSERT(device_.protocol().HasUnreadMessage());
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
usb_pd::Message sink_capabilities(
usb_pd::MessageType::kSinkCapabilities, device_.protocol().next_transmitted_message_id(),
device_.controls().power_role(), device_.controls().spec_revision(),
device_.controls().data_role(), policy_.GetSinkCapabilities());
result = device_.protocol().Transmit(sink_capabilities);
return;
}
case SinkPolicyEngineState::kGetSourceCapabilities: {
usb_pd::Message get_source_capabilities(
usb_pd::MessageType::kGetSourceCapabilities,
device_.protocol().next_transmitted_message_id(), device_.controls().power_role(),
device_.controls().spec_revision(), device_.controls().data_role(), {});
[[maybe_unused]] zx::result<> result = device_.protocol().Transmit(get_source_capabilities);
return;
}
case SinkPolicyEngineState::kSendSoftReset:
ResetProtocolLayer();
device_.protocol().FullReset();
{
usb_pd::Message soft_reset(
usb_pd::MessageType::kSoftReset, device_.protocol().next_transmitted_message_id(),
device_.controls().power_role(), device_.controls().spec_revision(),
device_.controls().data_role(), {});
[[maybe_unused]] zx::result<> result = device_.protocol().Transmit(soft_reset);
}
return;
case SinkPolicyEngineState::kSoftReset:
ResetProtocolLayer();
device_.protocol().DidReceiveSoftReset();
{
usb_pd::Message accept_reset(
usb_pd::MessageType::kAccept, device_.protocol().next_transmitted_message_id(),
device_.controls().power_role(), device_.controls().spec_revision(),
device_.controls().data_role(), {});
[[maybe_unused]] zx::result<> result = device_.protocol().Transmit(accept_reset);
}
return;
}
FDF_LOG(ERROR, "Invalid state: %" PRId32, static_cast<int32_t>(state));
}
SinkPolicyEngineState SinkPolicyEngineStateMachine::NextState(SinkPolicyEngineInput input,
SinkPolicyEngineState current_state) {
switch (current_state) {
case SinkPolicyEngineState::kStartup:
return SinkPolicyEngineState::kDiscovery;
case SinkPolicyEngineState::kDiscovery:
if (device_.sensors().vbus_power_good()) {
return SinkPolicyEngineState::kWaitForCapabilities;
}
return current_state;
case SinkPolicyEngineState::kWaitForCapabilities:
if (input == SinkPolicyEngineInput::kMessageReceived &&
device_.protocol().HasUnreadMessage()) {
const usb_pd::Message& message = device_.protocol().FirstUnreadMessage();
if (message.header().message_type() == usb_pd::MessageType::kSourceCapabilities) {
policy_.DidReceiveSourceCapabilities(message);
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
return SinkPolicyEngineState::kEvaluateCapability;
}
}
if (input == SinkPolicyEngineInput::kTimerFired) {
FDF_LOG(TRACE, "Checking if timer signal is from the source capabilities timer");
const bool done_waiting = wait_for_source_capabilities_timer_.wait_one(
ZX_TIMER_SIGNALED, zx::time(0), nullptr) == ZX_OK;
if (done_waiting) {
FDF_LOG(WARNING, "Timed out waiting for Source_Capabilities. Trying a reset");
return SinkPolicyEngineState::kSendSoftReset;
}
}
return current_state;
case SinkPolicyEngineState::kEvaluateCapability:
// This state could be used to track an asynchronous FIDL exchange.
return SinkPolicyEngineState::kSelectCapability;
case SinkPolicyEngineState::kSelectCapability:
if (input == SinkPolicyEngineInput::kMessageReceived &&
device_.protocol().HasUnreadMessage()) {
const usb_pd::Message& message = device_.protocol().FirstUnreadMessage();
if (message.header().message_type() == usb_pd::MessageType::kAccept) {
// Accept doesn't need a reply. We're done processing this message.
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
return SinkPolicyEngineState::kTransitionSink;
}
}
return current_state;
case SinkPolicyEngineState::kTransitionSink:
if (input == SinkPolicyEngineInput::kMessageReceived &&
device_.protocol().HasUnreadMessage()) {
const usb_pd::Message& message = device_.protocol().FirstUnreadMessage();
if (message.header().message_type() == usb_pd::MessageType::kPowerSupplyReady) {
// PS_RDY doesn't need a reply. We're done processing this message.
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
return SinkPolicyEngineState::kReady;
}
}
return current_state;
case SinkPolicyEngineState::kReady:
// TODO(rdzhuang): also accept requests from FIDL
if (input == SinkPolicyEngineInput::kMessageReceived &&
device_.protocol().HasUnreadMessage()) {
return ProcessMessageInReady();
}
return current_state;
case SinkPolicyEngineState::kGiveSinkCapabilities:
return SinkPolicyEngineState::kReady;
case SinkPolicyEngineState::kGetSourceCapabilities:
return SinkPolicyEngineState::kWaitForCapabilities;
case SinkPolicyEngineState::kSendSoftReset:
if (input == SinkPolicyEngineInput::kMessageReceived &&
device_.protocol().HasUnreadMessage()) {
const usb_pd::Message& message = device_.protocol().FirstUnreadMessage();
if (message.header().message_type() == usb_pd::MessageType::kAccept) {
// Accept doesn't need a reply. We're done processing this message.
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
return SinkPolicyEngineState::kWaitForCapabilities;
}
}
return current_state;
case SinkPolicyEngineState::kSoftReset:
return SinkPolicyEngineState::kWaitForCapabilities;
}
FDF_LOG(ERROR, "Invalid state: %" PRId32, static_cast<int32_t>(current_state));
return current_state;
}
void SinkPolicyEngineStateMachine::ExitState(SinkPolicyEngineState state) {
switch (state) {
case SinkPolicyEngineState::kStartup:
case SinkPolicyEngineState::kDiscovery:
// No cleanup needed.
return;
case SinkPolicyEngineState::kWaitForCapabilities:
wait_for_source_capabilities_timer_.cancel();
return;
case SinkPolicyEngineState::kEvaluateCapability:
case SinkPolicyEngineState::kSelectCapability:
case SinkPolicyEngineState::kTransitionSink:
case SinkPolicyEngineState::kReady:
case SinkPolicyEngineState::kGiveSinkCapabilities:
case SinkPolicyEngineState::kGetSourceCapabilities:
case SinkPolicyEngineState::kSendSoftReset:
case SinkPolicyEngineState::kSoftReset:
// No cleanup needed.
return;
}
FDF_LOG(ERROR, "Invalid state: %" PRId32, static_cast<int32_t>(state));
}
void SinkPolicyEngineStateMachine::InitializeProtocolLayer() {
// Revision 2.0 is always safe for GoodCRC packets.
const usb_pd::SpecRevision spec_revision = usb_pd::SpecRevision::kRev2;
const usb_pd::PowerRole power_role = device_.sensors().detected_power_role();
const usb_pd::DataRole data_role = (power_role == usb_pd::PowerRole::kSource)
? usb_pd::DataRole::kDownstreamFacingPort
: usb_pd::DataRole::kUpstreamFacingPort;
[[maybe_unused]] zx::result<> result = device_.controls().ConfigureAllRoles(
device_.sensors().detected_wired_cc_pin(), device_.sensors().detected_power_role(), data_role,
spec_revision);
device_.protocol().FullReset();
device_.protocol().SetGoodCrcTemplate(usb_pd::Header(
usb_pd::MessageType::kGoodCrc,
/*data_object_count=*/0, usb_pd::MessageId(0), power_role, spec_revision, data_role));
}
void SinkPolicyEngineStateMachine::ResetProtocolLayer() {
[[maybe_unused]] zx::result<> result = device_.controls().ConfigureAllRoles(
device_.controls().wired_cc_pin(), device_.controls().power_role(),
device_.controls().data_role(), device_.controls().spec_revision());
}
void SinkPolicyEngineStateMachine::CreateTimersIfNeeded() {
if (!wait_for_source_capabilities_timer_.is_valid()) {
zx_status_t status = zx::timer::create(ZX_TIMER_SLACK_CENTER, ZX_CLOCK_MONOTONIC,
&wait_for_source_capabilities_timer_);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to create Source_Capabilities wait timer: %s",
zx_status_get_string(status));
}
}
// New timer initialization goes above this line.
}
void SinkPolicyEngineStateMachine::ArmWaitForSourceCapabilitiesTimer() {
wait_for_source_capabilities_timer_.cancel();
zx_status_t status =
wait_for_source_capabilities_timer_.set(zx::deadline_after(kWaitForSourceCapabilitiesTimeout),
/*slack=*/zx::duration(0));
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to arm timer: %s", zx_status_get_string(status));
return;
}
[[maybe_unused]] zx::result<> result =
device_.WaitAsyncForTimer(wait_for_source_capabilities_timer_);
}
SinkPolicyEngineState SinkPolicyEngineStateMachine::ProcessMessageInReady() {
ZX_DEBUG_ASSERT(device_.protocol().HasUnreadMessage());
const usb_pd::Message& message = device_.protocol().FirstUnreadMessage();
const usb_pd::MessageType message_type = message.header().message_type();
if (message_type == usb_pd::MessageType::kSourceCapabilities) {
// The message will be read when entering this state.
ForceStateTransition(SinkPolicyEngineState::kWaitForCapabilities);
}
// The MacBookPro M1 USB-C ports issue a Hard Reset if this isn't handled.
if (message_type == usb_pd::MessageType::kGetSinkCapabilities) {
// The message will be read when entering this state.
return SinkPolicyEngineState::kGiveSinkCapabilities;
}
// For now, handle every other message as an unsupported message.
FDF_LOG(WARNING, "Received unsupported PD message type %s in Ready state",
usb_pd::MessageTypeToString(message_type));
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
// This is an intentional USB PD spec deviation.
//
// Section 6.13.5 "Applicability of Structured VDM Commands" states that DFPs
// and UFPs (Downward / Upward Facing Ports) that don't support Structured
// VDM Commands reply with a Not_Supported message.
//
// However, the MacBookPro M1 USB-C ports issue a Soft Reset (and eventually
// Hard Reset, after a few loops) if we reply with Not_Supported to
// a Discover_Identity Structured VDM (Vendor_Defined_Message).
if (message_type != usb_pd::MessageType::kVendorDefined) {
usb_pd::Message not_supported(usb_pd::MessageType::kNotSupported,
device_.protocol().next_transmitted_message_id(),
device_.controls().power_role(), usb_pd::SpecRevision::kRev2,
device_.controls().data_role(), {});
result = device_.protocol().Transmit(not_supported);
}
return SinkPolicyEngineState::kReady;
}
void SinkPolicyEngineStateMachine::ProcessUnexpectedMessage() {
// This is an intentional USB PD spec deviation.
//
// Section 6.13 "Message Applicability" states that unexpected messages may
// only occur in the Ready state (PE_SNK_Ready for Sinks). In other states,
// unexpected messages are protocol errors. Section 6.8.1 "Soft Reset and
// Protocol Error" states that protocol errors are handled via a Soft reset in
// most cases.
//
// We prefer to avoid Soft Reset, because the bug that caused us to receive
// this message may be deterministic, in which case we'd be stuck in a Soft
// Reset loop, which eventually leads to a Hard Reset.
ZX_DEBUG_ASSERT(device_.protocol().HasUnreadMessage());
const usb_pd::Message& message = device_.protocol().FirstUnreadMessage();
const usb_pd::MessageType message_type = message.header().message_type();
FDF_LOG(WARNING, "Received unexpected PD message type %s in state %s",
usb_pd::MessageTypeToString(message_type), StateToString(current_state()));
if (message_type == usb_pd::MessageType::kSourceCapabilities) {
// The message will be read when entering this state.
ForceStateTransition(SinkPolicyEngineState::kWaitForCapabilities);
return;
}
if (message_type == usb_pd::MessageType::kGetSinkCapabilities) {
// The message will be read when entering this state.
ForceStateTransition(SinkPolicyEngineState::kGiveSinkCapabilities);
return;
}
// The state transitions below assume that the message has been read.
[[maybe_unused]] zx::result<> result = device_.protocol().MarkMessageAsRead();
// See ProcessMessageInReady() for the rationale for ignoring VDM
// (vendor-defined messages).
if (message_type != usb_pd::MessageType::kVendorDefined) {
usb_pd::Message not_supported(usb_pd::MessageType::kNotSupported,
device_.protocol().next_transmitted_message_id(),
device_.controls().power_role(), usb_pd::SpecRevision::kRev2,
device_.controls().data_role(), {});
result = device_.protocol().Transmit(not_supported);
}
}
const char* SinkPolicyEngineStateMachine::StateToString(SinkPolicyEngineState state) const {
switch (state) {
case SinkPolicyEngineState::kStartup:
return "Startup";
case SinkPolicyEngineState::kDiscovery:
return "Discovery";
case SinkPolicyEngineState::kWaitForCapabilities:
return "WaitForCapabilities";
case SinkPolicyEngineState::kEvaluateCapability:
return "EvaluateCapability";
case SinkPolicyEngineState::kSelectCapability:
return "SelectCapability";
case SinkPolicyEngineState::kTransitionSink:
return "TransitionSink";
case SinkPolicyEngineState::kReady:
return "Ready";
case SinkPolicyEngineState::kGiveSinkCapabilities:
return "GiveSourceCapabilities";
case SinkPolicyEngineState::kGetSourceCapabilities:
return "GetSourceCapabilities";
case SinkPolicyEngineState::kSendSoftReset:
return "SendSoftReset";
case SinkPolicyEngineState::kSoftReset:
return "SoftReset";
}
ZX_DEBUG_ASSERT_MSG(false, "Invalid SinkPolicyEngineState: %" PRId32,
static_cast<int32_t>(state));
return "(invalid)";
}
} // namespace fusb302