blob: 30369416f7ad8d5e8ca3aa34c003b4d1cf8fd6cb [file] [log] [blame]
// Copyright 2023 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.
#ifndef SRC_DEVICES_POWER_DRIVERS_FUSB302_FUSB302_PROTOCOL_H_
#define SRC_DEVICES_POWER_DRIVERS_FUSB302_FUSB302_PROTOCOL_H_
// The comments in this file reference the USB Power Delivery Specification,
// downloadable at https://usb.org/document-library/usb-power-delivery
//
// usbpd3.1 is Revision 3.1, Version 1.7, published January 2023.
#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <cstdint>
#include <fbl/ring_buffer.h>
#include "src/devices/power/drivers/fusb302/fusb302-fifos.h"
#include "src/devices/power/drivers/fusb302/usb-pd-message.h"
namespace fusb302 {
// Tracks the acknowledgement of the last transmitted message.
enum class TransmissionState : uint8_t {
// Waiting for GoodCRC on last transmitted message.
//
// The message may be re-transmitted, according to the PD spec. Transmit()
// must not be called in this state.
kPending,
// Timed out waiting for GoodCRC on last transmitted message.
kTimedOut,
// Last transmitted message was received successfully.
kSuccess,
};
// Method used to generate or track GoodCRC replies to incoming USB PD packets.
enum class GoodCrcGenerationMode : uint8_t {
// No hardware-accelerated GoodCRC generation is available.
//
// In this mode, the software USB PD Protocol Layer implementation generates
// GoodCRC packets and drives the hardware to transmit them.
//
// This mode assumes that the hardware is not configured to generate any
// packets. For example, the FUSB302B's AutoCRC functionality would be
// disabled.
//
// This mode has proven to be too slow when the fusb302 Fuchsia driver is used
// with the FUSB302B chip on the Khadas VIM3 board. The logic may be useful in
// an RTOS environment.
kSoftware,
// Hardware-accelerated GoodCRC generation blocks PD packet transmission.
//
// In this mode, the software USB PD Protocol Layer implementation waits to be
// notified that the hardware-accelerated GoodCRC generation and transmission
// is done, before driving the transmission of a PD packet.
//
// Waiting ensures that all PD packets are transmitted after the GoodCRCs
// acknowledging any packets that they may reply to. Waiting comes at the cost
// of delaying transmissions until the hardware-accelerated GoodCRC
// notifications are delivered to the driver.
kTracked,
// Hardware-accelerated GoodCRC generation is not tracked by software.
//
// In this mode, the software USB PD Protocol Layer implementation assumes
// that hardware-accelerated GoodCRC generation is enabled, and that it
// completes before the software attempts to transmit a new PD packet.
kAssumed,
};
// FUSB302-specific implementation of the USB PD Protocol Layer.
//
// The FUSB302 hardware can take on some aspects of the PD Protocol Layer. This
// class is responsible for a complete implementation, while delegating some
// parts to hardware.
//
// Receiving messages works as follows:
// * The hardware control logic is responsible for calling DrainReceiveFifo()
// when the hardware signals that its receive FIFO is not empty.
// * The USB PD implementation uses HasUnreadMessage() and FirstUnreadMessage()
// to process received messages. MarkMessageAsRead() must be called after a
// message is processed, before transmitting any reply to the message.
// * The hardware control logic is responsible for calling
// DidTransmitHardwareGeneratedGoodCrc() when the hardware signals that it
// generated and transmitted a GoodCRC reply for a received PD packet.
//
// Transmitting messages works as follows:
// * next_transmitted_message_id() produces the MessageID to be used in the
// usb_pd::Header constructor for a usb_pd::Message.
// * Transmit() drives the hardware to transmit a usb_pd::Message over the PD
// connection.
// * DrainReceiveFifo(), documented in the reception section above, tracks
// the USB PD connection partner's acknowledgement of Transmit() messages.
// * The hardware control logic is responsible for calling
// DidTimeoutWaitingForGoodCrc() when a message transmitted via Transmit()
// is not acknowledged (via GoodCRC) by the other side of the USB PD
// connection within the time mandated by the USB PD specification.
//
// The implementation supports 3 integration models with a hardware-accelerated
// GoodCRC generation module, which map to the 3 member variables of the
// `GoodCrcGenerationMode` enum.
//
// * kSoftware - MarkMessageAsRead() generates a GoodCRC packet for the returned
// packet, and drives the hardware to transmit it
// * kTracked - DidTransmitHardwareGeneratedGoodCrc() is called when the
// hardware generates and transmits a GoodCRC packet; Transmit() queues its
// argument for transmission if any previously received packet lacks a GoodCRC
// reply; DidTransmitHardwareGeneratedGoodCrc() transmits any queued packet
// * kAssumed - GoodCRC generation is completely ignored by the software
// implementation
class Fusb302Protocol {
public:
// `fifos` must remain alive throughout the new instance's lifetime.
explicit Fusb302Protocol(GoodCrcGenerationMode good_crc_generation_mode, Fusb302Fifos& fifos);
Fusb302Protocol(const Fusb302Protocol&) = delete;
Fusb302Protocol& operator=(const Fusb302Protocol&) = delete;
// Trivially destructible.
~Fusb302Protocol() = default;
// See `TransmissionState` member comments.
TransmissionState transmission_state() const { return transmission_state_; }
// True if the unread queue is not empty.
//
// All the messages in the unread queue must be processed and acknowledged via
// `MarkMessageAsRead()` before `DrainReceiveFifo()` is called.
bool HasUnreadMessage() const { return !received_message_queue_.empty(); }
// Returns the first message in the unread messages queue.
//
// `HasUnreadMessage()` must be true.
const usb_pd::Message& FirstUnreadMessage() {
ZX_DEBUG_ASSERT(HasUnreadMessage());
return received_message_queue_.front();
}
// Removes a message from the unread queue. Transmits a GoodCRC if neceesary.
//
// `HasUnreadMessage()` must be true.
//
// Returns an error if an I/O error occurred while transmitting a GoodCRC
// acknowledging the read message.
zx::result<> MarkMessageAsRead();
// Not meaningful while `transmission_state()` is `kPending`.
usb_pd::MessageId next_transmitted_message_id() const {
ZX_DEBUG_ASSERT(transmission_state_ != TransmissionState::kPending);
return next_transmitted_message_id_;
}
// Reads any PD messages that may be pending in the Rx (receive) FIFO.
//
// Returns an error if retrieving the PD message from the PHY layer encounters
// an I/O error. Otherwise, performs PD Protocol Layer processing (mostly
// MessageID validation), and updates (TBD: queue name).
zx::result<> DrainReceiveFifo();
// Transmits a PD message.
//
// `message` must not be a GoodCRC. MarkMessageAsRead() dispatches any
// necessary GoodCRC message internally.
//
// `message`'s MessageID header field must equal
// `next_transmitted_message_id()`.
//
// Must not be called while `transmission_status()` is `kPending`. This is
// because PD messages (with the excepton of GoodCRC) form a synchronous
// stream that is blocked on the other side's GoodCRC acknowledgements.
zx::result<> Transmit(const usb_pd::Message& message);
// The template will be used as-is (modulo MessageID) for GoodCRC messages.
void SetGoodCrcTemplate(usb_pd::Header good_crc_template) {
good_crc_template_ = good_crc_template;
}
// PD protocol layer reset.
//
// This method can be used when a new Type C connection is established, or
// right before sending a Soft Reset message.
//
// `DidReceiveSoftReset()` must be called instead of this method when a Soft
// Reset message is received, because that situation requires different
// initial values for MessageID counters.
void FullReset();
// PD protocol layer reset, used after receiving a Soft Reset packet.
//
// This must only be used for a soft reset initiated by a port partner
// message. Incorrect use will result in incorrect initial MessageID values,
// which will break future communication.
void DidReceiveSoftReset();
// Hardware-side PD protocol layer says it gave up waiting for a GoodCRC.
//
// This signal comes from the interrupt unit.
void DidTimeoutWaitingForGoodCrc();
// Hardware-side PD protocol layer says it replied with a GoodCRC message.
//
// This signal comes from the interrupt unit.
void DidTransmitHardwareGeneratedGoodCrc();
// If false, `DidTransmitHardwareGeneratedGoodCrc()` must never be called.
bool UsesHardwareAcceleratedGoodCrcNotifications() const {
return good_crc_generation_mode_ == GoodCrcGenerationMode::kTracked;
}
private:
// Prepare `good_crc_template_` for transmission.
//
// The `good_crc_transmission_pending_` flag will be consumed. (Must be true,
// will be set to false.)
void StampGoodCrcTemplate();
// Reads a PD message out of the Rx (receive) FIFO.
//
// Returns an error if retrieving the PD message from the PHY layer encounters
// an I/O error. Otherwise, performs PD Protocol Layer processing (mostly
// MessageID validation). The unread message queue will be updated if the
// message is accepted by the protocol layer.
void ProcessReceivedMessage(const usb_pd::Message& message);
// Guaranteed to outlive this instance, because it's owned by this instance's
// owner (Fusb302).
Fusb302Fifos& fifos_;
const GoodCrcGenerationMode good_crc_generation_mode_;
// The USB PD packet header for the next software-generated GoodCRC.
//
// Not used when hardware-generated GoodCRC is used.
usb_pd::Header good_crc_template_;
// Received messages that haven't been processed yet.
//
// With hardware-generated GoodCRC replies, it's possible to have at least 4
// messages queued up: a GoodCRC for a Request, two replies (Accept, PS_RDY),
// and a follow-up query such as Get_Sink_Capabilities.
fbl::RingBuffer<usb_pd::Message, 8> received_message_queue_;
// Message that must be transmitted after a hardware-generated GoodCRC.
//
// Only used when the software USB PD protocol implementation tracks
// hardware-generated GoodCRC replies.
std::optional<usb_pd::Message> queued_transmission_;
// If `transmission_state` is `kPending`, this MessageID was used, and we're
// waiting for a GoodCRC. Otherwise, this is MessageID will be used for the
// next transmitted message.
usb_pd::MessageId next_transmitted_message_id_;
// The expected MessageID for the next packet from the other side, if known.
//
// This PD protocol implementation supports being started up in the middle of
// a PD packet stream, where the other side has already sent some packets.
// This support is absolutely necessary for hardware that automatically
// generates GoodCRC packets (such as the FUSB302B), because sending a GoodCRC
// acknowledges the original packet and commits us to replying to it.
//
// We "lock onto" the first valid PD packet we receive, which determines the
// next expected MessageID. After this first packet, we only accept packets
// with correct MessageID sequence numbers.
//
// If `good_crc_transmission_pending_` is true, we're waiting to send a
// GoodCRC for the last received message (which has the MessageID).
std::optional<usb_pd::MessageId> next_expected_message_id_;
// Only used when waiting on notifications for hardware-generated GoodCRC.
bool good_crc_transmission_pending_ = false;
TransmissionState transmission_state_ = TransmissionState::kSuccess;
};
} // namespace fusb302
#endif // SRC_DEVICES_POWER_DRIVERS_FUSB302_FUSB302_PROTOCOL_H_