blob: 1702d2c6f887287c430fba5d1e402ebd065a2f7f [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.
#include "src/devices/power/drivers/fusb302/fusb302-fifos.h"
#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/mock-i2c/mock-i2c.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/result.h>
#include <cstdint>
#include <optional>
#include <utility>
#include <zxtest/zxtest.h>
#include "src/devices/power/drivers/fusb302/usb-pd-defs.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 {
// Register addresses from Table 16 "Register Definitions" on page 18 of the
// Rev 5 datasheet.
constexpr int kFifosAddress = 0x43;
constexpr int kStatus0Address = 0x40;
constexpr int kStatus1Address = 0x41;
// Tokens from Table 41 "Tokens used in TxFIFO" on page 29 of the Rev 5
// datasheet.
const uint8_t kTxOnTxToken = 0xa1;
const uint8_t kSync1TxToken = 0x12;
const uint8_t kSync2TxToken = 0x13;
// const uint8_t kSync3TxToken = 0x1b;
// const uint8_t kReset1TxToken = 0x15;
// const uint8_t kReset2TxToken = 0x16;
const uint8_t kPackSymTxToken = 0x80;
const uint8_t kJamCrcTxToken = 0xff;
const uint8_t kEopTxToken = 0x14;
const uint8_t kTxOffTxToken = 0xfe;
// Tokens from Table 42 "Tokens used in RxFIFO" on page 29 of the Rev 5
// datasheet.
constexpr uint8_t kSopRxToken = 0b1110'0000;
class Fusb302FifosTest : public zxtest::Test {
public:
void SetUp() override {
fdf::Logger::SetGlobalInstance(&logger_);
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_i2c::Device>();
EXPECT_TRUE(endpoints.is_ok());
mock_i2c_client_ = std::move(endpoints->client);
EXPECT_OK(loop_.StartThread());
fidl::BindServer<fuchsia_hardware_i2c::Device>(loop_.dispatcher(), std::move(endpoints->server),
&mock_i2c_);
fifos_.emplace(mock_i2c_client_);
}
void TearDown() override {
fdf::Logger::SetGlobalInstance(nullptr);
mock_i2c_.VerifyAndClear();
}
protected:
void MockReceiveFifoEmpty() { mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x20}); }
void MockReceiveFifoNotEmpty() {
mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x00});
}
void MockTransmitFifoEmpty() { mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x08}); }
void MockNoBmcTransmissionDetected() {
mock_i2c_.ExpectWrite({kStatus0Address}).ExpectReadStop({0x80});
}
fdf::Logger logger_{"fusb302-fifos-test", FUCHSIA_LOG_DEBUG, zx::socket{},
fidl::WireClient<fuchsia_logger::LogSink>()};
async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread};
mock_i2c::MockI2c mock_i2c_;
fidl::ClientEnd<fuchsia_hardware_i2c::Device> mock_i2c_client_;
std::optional<Fusb302Fifos> fifos_;
};
TEST_F(Fusb302FifosTest, TransmitMessageEncodingForControlMessage) {
MockTransmitFifoEmpty();
MockNoBmcTransmissionDetected();
mock_i2c_.ExpectWriteStop({
kFifosAddress, kSync1TxToken, kSync1TxToken, kSync1TxToken, kSync2TxToken, // Preamble
kPackSymTxToken | 2, 0x61, 0x05, // Packet data
kJamCrcTxToken, kEopTxToken, kTxOffTxToken, kTxOnTxToken, // Trailer
});
usb_pd::Message good_crc(usb_pd::MessageType::kGoodCrc, usb_pd::MessageId(2),
usb_pd::PowerRole::kSource, usb_pd::SpecRevision::kRev2,
usb_pd::DataRole::kDownstreamFacingPort, {});
EXPECT_OK(fifos_->TransmitMessage(good_crc));
}
TEST_F(Fusb302FifosTest, TransmitMessageEncodingForDataMessageWithOneObject) {
MockTransmitFifoEmpty();
MockNoBmcTransmissionDetected();
mock_i2c_.ExpectWriteStop({
kFifosAddress, kSync1TxToken, kSync1TxToken, kSync1TxToken, kSync2TxToken, // Preamble
kPackSymTxToken | 6, 0x42, 0x1a, 0x2c, 0xb1, 0x04, 0x11, // Packet data
kJamCrcTxToken, kEopTxToken, kTxOffTxToken, kTxOnTxToken, // Trailer
});
static constexpr uint32_t kPowerRequestDataObjects[] = {0x1104b12c};
usb_pd::Message request(usb_pd::MessageType::kRequestPower, usb_pd::MessageId(5),
usb_pd::PowerRole::kSink, usb_pd::SpecRevision::kRev2,
usb_pd::DataRole::kUpstreamFacingPort, kPowerRequestDataObjects);
EXPECT_OK(fifos_->TransmitMessage(request));
}
TEST_F(Fusb302FifosTest, TransmitMessageEncodingForDataMessageWithMultipleObjects) {
MockTransmitFifoEmpty();
MockNoBmcTransmissionDetected();
// clang-format off
mock_i2c_.ExpectWriteStop({
// Preamble
kFifosAddress, kSync1TxToken, kSync1TxToken, kSync1TxToken, kSync2TxToken,
// Packet data
kPackSymTxToken | 18, 0xa1, 0x4b, 0x2c, 0x91, 0x01, 0x0b, 0x2c, 0xd1, 0x02, 0x00, 0x2c, 0xb1,
0x04, 0x00, 0x45, 0x41, 0x06, 0x00,
// Trailer
kJamCrcTxToken, kEopTxToken, kTxOffTxToken, kTxOnTxToken,
});
// clang-format on
static constexpr uint32_t kPowerDataObjects[] = {0x0b01912c, 0x0002d12c, 0x0004b12c, 0x00064145};
usb_pd::Message capabilities(usb_pd::MessageType::kSourceCapabilities, usb_pd::MessageId(5),
usb_pd::PowerRole::kSource, usb_pd::SpecRevision::kRev3,
usb_pd::DataRole::kDownstreamFacingPort, kPowerDataObjects);
EXPECT_OK(fifos_->TransmitMessage(capabilities));
}
TEST_F(Fusb302FifosTest, TransmitMessageWithNonEmptyFifo) {
// Transmit FIFO not empty, other status indicators set.
mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x04});
mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x20});
mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x10});
// Transmit FIFO empty, good to go.
mock_i2c_.ExpectWrite({kStatus1Address}).ExpectReadStop({0x08});
MockNoBmcTransmissionDetected();
mock_i2c_.ExpectWriteStop({
kFifosAddress, kSync1TxToken, kSync1TxToken, kSync1TxToken, kSync2TxToken, // Preamble
kPackSymTxToken | 2, 0x61, 0x05, // Packet data
kJamCrcTxToken, kEopTxToken, kTxOffTxToken, kTxOnTxToken, // Trailer
});
usb_pd::Message good_crc(usb_pd::MessageType::kGoodCrc, usb_pd::MessageId(2),
usb_pd::PowerRole::kSource, usb_pd::SpecRevision::kRev2,
usb_pd::DataRole::kDownstreamFacingPort, {});
EXPECT_OK(fifos_->TransmitMessage(good_crc));
}
TEST_F(Fusb302FifosTest, TransmitMessageWithCcActivity) {
MockTransmitFifoEmpty();
// CC activity, VBUS power good flicker (so only CC activity is on).
mock_i2c_.ExpectWrite({kStatus0Address}).ExpectReadStop({0xc0});
mock_i2c_.ExpectWrite({kStatus0Address}).ExpectReadStop({0x40});
mock_i2c_.ExpectWrite({kStatus0Address}).ExpectReadStop({0xc0});
// CC activity gone, good to go.
mock_i2c_.ExpectWrite({kStatus0Address}).ExpectReadStop({0x80});
mock_i2c_.ExpectWriteStop({
kFifosAddress, kSync1TxToken, kSync1TxToken, kSync1TxToken, kSync2TxToken, // Preamble
kPackSymTxToken | 2, 0x61, 0x05, // Packet data
kJamCrcTxToken, kEopTxToken, kTxOffTxToken, kTxOnTxToken, // Trailer
});
usb_pd::Message good_crc(usb_pd::MessageType::kGoodCrc, usb_pd::MessageId(2),
usb_pd::PowerRole::kSource, usb_pd::SpecRevision::kRev2,
usb_pd::DataRole::kDownstreamFacingPort, {});
EXPECT_OK(fifos_->TransmitMessage(good_crc));
}
TEST_F(Fusb302FifosTest, ReadReceivedMessageEmptyFifo) {
MockReceiveFifoEmpty();
zx::result<std::optional<usb_pd::Message>> result = fifos_->ReadReceivedMessage();
ASSERT_OK(result);
EXPECT_FALSE(result.value().has_value());
}
TEST_F(Fusb302FifosTest, ReadReceivedMessageDecodingForControlMessage) {
MockReceiveFifoNotEmpty();
mock_i2c_.ExpectWrite({kFifosAddress}).ExpectReadStop({kSopRxToken, 0x61, 0x05});
mock_i2c_.ExpectWrite({kFifosAddress}).ExpectReadStop({0xcd, 0xcd, 0xcd, 0xcd});
zx::result<std::optional<usb_pd::Message>> result = fifos_->ReadReceivedMessage();
ASSERT_OK(result);
ASSERT_TRUE(result.value().has_value());
EXPECT_FALSE(result->header().is_extended());
EXPECT_EQ(usb_pd::MessageId(2), result->header().message_id());
EXPECT_EQ(usb_pd::PowerRole::kSource, result->header().power_role());
EXPECT_EQ(usb_pd::SpecRevision::kRev2, result->header().spec_revision());
EXPECT_EQ(usb_pd::DataRole::kDownstreamFacingPort, result->header().data_role());
EXPECT_EQ(usb_pd::MessageType::kGoodCrc, result->header().message_type());
}
TEST_F(Fusb302FifosTest, RedReceivedMessageDecodingforDataMessageWithOneObject) {
MockReceiveFifoNotEmpty();
mock_i2c_.ExpectWrite({kFifosAddress}).ExpectReadStop({kSopRxToken, 0x42, 0x1a});
mock_i2c_.ExpectWrite({kFifosAddress})
.ExpectReadStop({
0x2c, 0xb1, 0x04, 0x11, // Packet data
0xcd, 0xcd, 0xcd, 0xcd, // Ignored CRC
});
zx::result<std::optional<usb_pd::Message>> result = fifos_->ReadReceivedMessage();
ASSERT_OK(result);
ASSERT_TRUE(result.value().has_value());
EXPECT_FALSE(result->header().is_extended());
EXPECT_EQ(usb_pd::MessageId(5), result->header().message_id());
EXPECT_EQ(usb_pd::PowerRole::kSink, result->header().power_role());
EXPECT_EQ(usb_pd::SpecRevision::kRev2, result->header().spec_revision());
EXPECT_EQ(usb_pd::DataRole::kUpstreamFacingPort, result->header().data_role());
EXPECT_EQ(usb_pd::MessageType::kRequestPower, result->header().message_type());
ASSERT_EQ(1u, result->data_objects().size());
EXPECT_EQ(0x1104b12c, result->data_objects()[0]);
}
TEST_F(Fusb302FifosTest, RedReceivedMessageDecodingforDataMessageWithMultipleObjects) {
MockReceiveFifoNotEmpty();
mock_i2c_.ExpectWrite({kFifosAddress}).ExpectReadStop({kSopRxToken, 0xa1, 0x4b});
mock_i2c_.ExpectWrite({kFifosAddress})
.ExpectReadStop({
0x2c, 0x91, 0x01, 0x0b, 0x2c, 0xd1, 0x02, 0x00,
0x2c, 0xb1, 0x04, 0x00, 0x45, 0x41, 0x06, 0x00, // Packet data
0xcd, 0xcd, 0xcd, 0xcd, // Ignored CRC
});
zx::result<std::optional<usb_pd::Message>> result = fifos_->ReadReceivedMessage();
ASSERT_OK(result);
ASSERT_TRUE(result.value().has_value());
EXPECT_FALSE(result->header().is_extended());
EXPECT_EQ(usb_pd::MessageId(5), result->header().message_id());
EXPECT_EQ(usb_pd::PowerRole::kSource, result->header().power_role());
EXPECT_EQ(usb_pd::SpecRevision::kRev3, result->header().spec_revision());
EXPECT_EQ(usb_pd::DataRole::kDownstreamFacingPort, result->header().data_role());
EXPECT_EQ(usb_pd::MessageType::kSourceCapabilities, result->header().message_type());
ASSERT_EQ(4u, result->data_objects().size());
EXPECT_EQ(0x0b01912c, result->data_objects()[0]);
EXPECT_EQ(0x0002d12c, result->data_objects()[1]);
EXPECT_EQ(0x0004b12c, result->data_objects()[2]);
EXPECT_EQ(0x00064145, result->data_objects()[3]);
}
} // namespace
} // namespace fusb302