blob: c8342c1a7fcca5b9313086ffe6e357d3f2a081a1 [file] [log] [blame] [edit]
/* Copyright 2024 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <gtest/gtest.h>
#include <stdint.h>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <utility>
#include <vector>
extern "C" {
#include "include/pd_driver.h"
#include "include/platform.h"
#include "include/ppm.h"
#include "ppm_common.h"
}
// Forward declarations.
class PpmTest;
namespace {
struct ucsi_pd_driver* open_test_pd(PpmTest* ppm_test);
void ucsi_opm_notify(void* context);
// Allocations made using platform_{malloc|calloc} need to use platform_free to
// delete. Use this as a custom deleter for std::unique_ptr for those structs.
template <typename T>
struct FreeDeleter {
void operator()(T* obj) { platform_free(static_cast<void*>(obj)); }
};
using PpmDriverUniquePtr = std::unique_ptr<struct ucsi_ppm_driver,
FreeDeleter<struct ucsi_ppm_driver>>;
using PdDriverUniquePtr =
std::unique_ptr<struct ucsi_pd_driver, FreeDeleter<struct ucsi_pd_driver>>;
const struct ucsi_cci kCciCmdComplete = {.cmd_complete = 1};
const struct ucsi_cci kCciBusy = {.busy = 1};
const struct ucsi_cci kCciError = {.error = 1, .cmd_complete = 1};
const struct ucsi_cci kCciAckCommand = {.ack_command = 1};
const uint8_t kDefaultAlertPort = 1;
} // namespace
// Test fixture class for the PPM state machine.
// This attempts to validate the overall PPM state machine described in the UCSI
// spec and implemented in |ppm_common|.
class PpmTest : public testing::Test {
public:
PpmTest() {
pd_ = PdDriverUniquePtr(open_test_pd(this));
ppm_ = PpmDriverUniquePtr(ppm_open(pd_.get()));
platform_set_debug(false);
}
// --- START (struct ucsi_pd_driver) ---
int init_ppm() {
if (ppm_->register_notify(ppm_->dev, ucsi_opm_notify, this) == -1) {
return -1;
}
return ppm_->init_and_wait(ppm_->dev, num_ports_);
}
struct ucsi_ppm_driver* get_ppm() { return ppm_.get(); }
int execute_cmd(struct ucsi_control* control, uint8_t* lpm_data_out) {
uint8_t ucsi_command = control->command;
// Either immediately return with the queued expected commands OR
// fall back to blocking using the |cmd_notifier_| condition variable. The
// latter requires notification via the test.
if (!expected_commands_queue_.empty()) {
// Make sure this is the expected command or return an error.
auto [expected_cmd, ret, lpm_data] = expected_commands_queue_.front();
EXPECT_EQ(ucsi_command, expected_cmd);
if (ucsi_command != expected_cmd) {
return -1;
}
if (lpm_data.has_value()) {
memcpy(lpm_data_out, lpm_data.value().data(), lpm_data.value().size());
}
expected_commands_queue_.pop_front();
return ret;
}
std::unique_lock<std::mutex> lock(cmd_lock_);
auto status = cmd_notifier_.wait_for(
lock, std::chrono::milliseconds(cmd_wait_timeout_ms_));
EXPECT_NE(status, std::cv_status::timeout);
auto [expected_cmd, ret, lpm_data] = cmd_notifier_expected_cmd_result_;
EXPECT_EQ(expected_cmd, ucsi_command);
if (status == std::cv_status::timeout || expected_cmd != ucsi_command) {
ret = -1;
return ret;
}
if (lpm_data.has_value()) {
memcpy(lpm_data_out, lpm_data.value().data(), lpm_data.value().size());
}
return ret;
}
void cleanup() { ppm_->cleanup(ppm_.get()); }
// ---- FINISH (struct ucsi_pd_driver)
void opm_notify() {
notified_count_++;
opm_notifier_.notify_one();
}
protected:
int Initialize() {
// Init does a reset and that's it.
QueueExpectedCommandWithResult({UCSI_CMD_PPM_RESET, 0});
return init_ppm();
}
void InitializeToIdleNotify() {
ASSERT_EQ(Initialize(), 0);
QueueExpectedCommandWithResult({UCSI_CMD_SET_NOTIFICATION_ENABLE, 0});
struct ucsi_control control = {.command = UCSI_CMD_SET_NOTIFICATION_ENABLE,
.data_length = 0};
EXPECT_NE(-1, WriteCommand(control));
EXPECT_TRUE(WaitForCommandPendingState(false));
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
QueueExpectedCommandWithResult({UCSI_CMD_ACK_CC_CI, 0});
WriteAckCommand(/*connector_change_ack*/ false,
/*command_complete_ack*/ true);
EXPECT_TRUE(WaitForCommandPendingState(false));
ASSERT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE_NOTIFY);
}
struct ppm_common_device* GetPpmData() {
return reinterpret_cast<struct ppm_common_device*>(ppm_->dev);
}
struct ExpectedCommand {
// expected PPM command to LPM
uint8_t ucsi_command;
int result;
// Any data that the PDC returns.
std::optional<std::vector<uint8_t>> lpm_data;
};
// Queue an expected PPM command to the LPM.
// Call this function before performing an OPM write.
void QueueExpectedCommandWithResult(ExpectedCommand cmd) {
expected_commands_queue_.push_back(std::move(cmd));
}
// Provide a response from the LPM for a command that has already been issued
// by the PPM.
void CompleteSpecificCommand(ExpectedCommand expected_command) {
// Update expected result with lock before notifying.
{
std::lock_guard<std::mutex> lock(cmd_lock_);
cmd_notifier_expected_cmd_result_ = std::move(expected_command);
}
cmd_notifier_.notify_one();
}
int GetNotifiedCount() const { return notified_count_; }
void ClearNotifiedCount() { notified_count_ = 0; }
int ReadCCI(struct ucsi_cci& cci) {
return ppm_->read(ppm_->dev, UCSI_CCI_OFFSET, static_cast<void*>(&cci),
sizeof(struct ucsi_cci));
}
bool IsEqualCCI(const struct ucsi_cci& rhs) {
struct ucsi_cci lhs;
ReadCCI(lhs);
return *reinterpret_cast<const uint32_t*>(&lhs) ==
*reinterpret_cast<const uint32_t*>(&rhs);
}
void EXPECT_CCI(const struct ucsi_cci& expected_cci, int line) {
struct ucsi_cci cci;
ReadCCI(cci);
EXPECT_TRUE(IsEqualCCI(expected_cci))
<< "CCI comparison failed on line " << line;
EXPECT_EQ(expected_cci.end_of_message, cci.end_of_message);
EXPECT_EQ(expected_cci.connector_changed, cci.connector_changed);
EXPECT_EQ(expected_cci.data_length, cci.data_length);
EXPECT_EQ(expected_cci.vendor_defined_message, cci.vendor_defined_message);
EXPECT_EQ(expected_cci.reserved_0, cci.reserved_0);
EXPECT_EQ(expected_cci.security_request, cci.security_request);
EXPECT_EQ(expected_cci.fw_update_request, cci.fw_update_request);
EXPECT_EQ(expected_cci.not_supported, cci.not_supported);
EXPECT_EQ(expected_cci.cancel_completed, cci.cancel_completed);
EXPECT_EQ(expected_cci.reset_completed, cci.reset_completed);
EXPECT_EQ(expected_cci.busy, cci.busy);
EXPECT_EQ(expected_cci.ack_command, cci.ack_command);
EXPECT_EQ(expected_cci.error, cci.error);
EXPECT_EQ(expected_cci.cmd_complete, cci.cmd_complete);
}
bool WaitForNotification(int exp_notified_count) {
if (exp_notified_count <= GetNotifiedCount()) return true;
while (GetNotifiedCount() < exp_notified_count) {
std::unique_lock<std::mutex> lk(opm_notify_lock_);
auto status = opm_notifier_.wait_for(
lk, std::chrono::milliseconds(cmd_wait_timeout_ms_));
if (status == std::cv_status::timeout) {
return false;
}
}
return true;
}
void SendLpmAlert(uint8_t lpm_id) { ppm_->lpm_alert(ppm_->dev, lpm_id); }
void TearDown() override { EXPECT_TRUE(expected_commands_queue_.empty()); }
// Set up a PPM alert on port 1. This results in a GET_CONNECTOR_STATUS read,
// and a subsequent notification to the OPM.
void TriggerConnectorChangedNotification(uint8_t lpm_id) {
ucsiv3_get_connector_status_data data = {.connector_status_change = 1};
auto lpm_data = reinterpret_cast<uint8_t*>(&data);
size_t lpm_data_size = sizeof(data) / sizeof(uint8_t);
QueueExpectedCommandWithResult(
{UCSI_CMD_GET_CONNECTOR_STATUS, 0,
std::vector<uint8_t>(lpm_data, lpm_data + lpm_data_size)});
SendLpmAlert(lpm_id);
EXPECT_TRUE(WaitForAsyncEventPendingState(false));
}
bool WaitForAsyncEventPendingState(bool target_pending_state) {
return wait_for_pending_async_event(target_pending_state, 3);
}
bool WaitForCommandPendingState(bool target_pending_state) {
return wait_for_pending_command(target_pending_state, 3);
}
int WriteCommand(struct ucsi_control& control) {
return ppm_->write(ppm_->dev, UCSI_CONTROL_OFFSET,
static_cast<void*>(&control),
sizeof(struct ucsi_control));
}
void WriteAckCommand(bool connector_change_ack, bool command_complete_ack) {
struct ucsi_control control = {.command = UCSI_CMD_ACK_CC_CI,
.data_length = 0};
struct ucsiv3_ack_cc_ci_cmd ack_data = {
.connector_change_ack = connector_change_ack,
.command_complete_ack = command_complete_ack};
memcpy(control.command_specific, &ack_data, sizeof(ack_data));
ASSERT_NE(-1, WriteCommand(control));
}
private:
bool wait_for_pending_command(bool target_pending_state, int num_iterations) {
bool current_cmd_pending = false;
for (int i = 0; i < num_iterations; ++i) {
platform_mutex_lock(GetPpmData()->ppm_lock);
current_cmd_pending = is_pending_command();
platform_mutex_unlock(GetPpmData()->ppm_lock);
if (current_cmd_pending == target_pending_state) {
break;
} else {
platform_condvar_signal(GetPpmData()->ppm_condvar);
// No better option than sleep here unfortunately. Keep this value low.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
return current_cmd_pending == target_pending_state;
}
bool wait_for_pending_async_event(bool target_pending_state,
int num_iterations) {
bool currently_pending = false;
for (int i = 0; i < num_iterations; ++i) {
platform_mutex_lock(GetPpmData()->ppm_lock);
currently_pending = is_pending_async_event();
platform_mutex_unlock(GetPpmData()->ppm_lock);
if (currently_pending == target_pending_state) {
break;
} else {
platform_condvar_signal(GetPpmData()->ppm_condvar);
// No better option than sleep here unfortunately. Keep this value low.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
return currently_pending == target_pending_state;
}
bool is_pending_command() { return GetPpmData()->pending.command; }
bool is_pending_async_event() { return GetPpmData()->pending.async_event; }
PpmDriverUniquePtr ppm_;
PdDriverUniquePtr pd_;
std::mutex opm_notify_lock_;
std::condition_variable opm_notifier_;
int notified_count_ = 0;
// If we are blocking execute_cmd to return a specific value, use these
// variables.
std::mutex cmd_lock_;
std::condition_variable cmd_notifier_;
ExpectedCommand cmd_notifier_expected_cmd_result_;
// If we are expecting a list of commands, pop the command and return the
// value listed.
std::deque<ExpectedCommand> expected_commands_queue_;
// Number of ports for fake pd driver.
static constexpr int num_ports_ = 2;
// Timeout for pending command. This is arbitrarily set (high enough in case
// unit tests are run in high cpu environment but low enough that tests
// complete quickly).
static constexpr int cmd_wait_timeout_ms_ = 250;
};
namespace {
inline PpmTest* ppm_cast(struct ucsi_pd_device* dev) {
return reinterpret_cast<PpmTest*>(dev);
}
int init_ppm(struct ucsi_pd_device* dev) { return ppm_cast(dev)->init_ppm(); }
struct ucsi_ppm_driver* get_ppm(struct ucsi_pd_device* dev) {
return ppm_cast(dev)->get_ppm();
}
int execute_cmd(struct ucsi_pd_device* dev, struct ucsi_control* control,
uint8_t* lpm_data_out) {
return ppm_cast(dev)->execute_cmd(control, lpm_data_out);
}
void cleanup(struct ucsi_pd_driver* driver) {
ppm_cast(driver->dev)->cleanup();
}
void ucsi_opm_notify(void* context) {
ppm_cast(static_cast<ucsi_pd_device*>(context))->opm_notify();
}
struct ucsi_pd_driver* open_test_pd(PpmTest* ppm_test) {
struct ucsi_pd_driver* drv = static_cast<struct ucsi_pd_driver*>(
platform_calloc(1, sizeof(struct ucsi_pd_driver)));
drv->dev = reinterpret_cast<struct ucsi_pd_device*>(ppm_test);
drv->init_ppm = init_ppm;
drv->get_ppm = get_ppm;
drv->execute_cmd = execute_cmd;
drv->cleanup = cleanup;
return drv;
}
} // namespace
// On init, we should go to the Idle state.
TEST_F(PpmTest, InitializesToIdle) {
// Make sure we initialize correctly.
ASSERT_EQ(Initialize(), 0);
// System should be in the idle state at init.
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE);
}
// From the Idle state, only PPM_RESET and SET_NOTIFICATION_ENABLE is allowed.
TEST_F(PpmTest, Idle_DropsUnexpectedCommands) {
ASSERT_EQ(Initialize(), 0);
// Try all commands except PPM_RESET and SET_NOTIFICATION_ENABLE
for (uint8_t cmd = UCSI_CMD_PPM_RESET; cmd <= UCSI_CMD_VENDOR_CMD; cmd++) {
if (cmd == UCSI_CMD_PPM_RESET || cmd == UCSI_CMD_SET_NOTIFICATION_ENABLE) {
continue;
}
struct ucsi_control control = {.command = cmd, .data_length = 0};
// Make sure Write completed and then wait for pending command to be
// cleared. Only the .command part will really matter as that's how we
// determine whether the next command should be executed.
ASSERT_NE(-1, WriteCommand(control));
EXPECT_TRUE(WaitForCommandPendingState(false));
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE);
}
// Write SET_NOTIFICATION_ENABLE and wait for state transition.
QueueExpectedCommandWithResult({UCSI_CMD_SET_NOTIFICATION_ENABLE, 0});
struct ucsi_control control = {.command = UCSI_CMD_SET_NOTIFICATION_ENABLE,
.data_length = 0};
ASSERT_NE(-1, WriteCommand(control));
EXPECT_TRUE(WaitForCommandPendingState(false));
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
}
// From the Idle state, we process async events but we do not notify the OPM or
// change the PPM state (i.e. silently drop).
TEST_F(PpmTest, Idle_SilentlyProcessesAsyncEvent) {
ASSERT_EQ(Initialize(), 0);
ClearNotifiedCount();
// Set up a PPM alert with lpm_id=1
SendLpmAlert(kDefaultAlertPort);
EXPECT_TRUE(WaitForAsyncEventPendingState(false));
EXPECT_EQ(0, GetNotifiedCount());
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE);
}
// From the Idle Notify, complete a full command loop:
// - Send command, CCI notifies busy
// - Command complete, CCI notifies command complete.
// - Send ACK_CC_CI, CCI notifies busy
// - Command complete, CCI notifies ack command complete.
TEST_F(PpmTest, IdleNotify_FullCommandLoop) {
InitializeToIdleNotify();
int notified_count = GetNotifiedCount();
// Emulate a UCSI write from the OPM, and wait for a notification with
// CCI.busy=1
struct ucsi_control control = {.command = UCSI_CMD_GET_ALTERNATE_MODES,
.data_length = 0};
ASSERT_NE(-1, WriteCommand(control));
ASSERT_TRUE(WaitForNotification(++notified_count));
EXPECT_CCI(kCciBusy, __LINE__);
// Send a fake response from the PD driver, and expect a notification to the
// OPM with CCI.cmd_complete=1.
CompleteSpecificCommand({UCSI_CMD_GET_ALTERNATE_MODES, 0});
EXPECT_TRUE(WaitForCommandPendingState(false));
ASSERT_TRUE(WaitForNotification(++notified_count));
EXPECT_CCI(kCciCmdComplete, __LINE__);
// OPM acknowledges the PPM's cmd_complete.
QueueExpectedCommandWithResult({UCSI_CMD_ACK_CC_CI, 0});
WriteAckCommand(/*connector_change_ack*/ false,
/*command_complete_ack*/ true);
ASSERT_TRUE(WaitForNotification(++notified_count));
EXPECT_CCI(kCciAckCommand, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE_NOTIFY);
}
// When processing an async event, PPM will figure out which port changed and
// then send the connector change event for that port.
TEST_F(PpmTest, IdleNotify_ProcessAsyncEventAndSendConnectorChange) {
InitializeToIdleNotify();
int notified_count = GetNotifiedCount();
TriggerConnectorChangedNotification(kDefaultAlertPort);
EXPECT_EQ(++notified_count, GetNotifiedCount());
struct ucsi_cci cci = {.connector_changed = kDefaultAlertPort};
EXPECT_CCI(cci, __LINE__);
}
// While in the processing command state, the PPM is busy and should reject any
// new commands that are sent.
TEST_F(PpmTest, ProcessingCommand_BusyRejectsCommands) {
GTEST_SKIP() << "Not implemented";
}
// While in the processing command state, we still allow the cancel command to
// be sent WHILE a command is in progress. If a command is cancellable, it will
// replace the current command
TEST_F(PpmTest, ProcessingCommand_BusyAllowsCancelCommand) {
GTEST_SKIP() << "Not implemented";
}
// When waiting for command complete, any command that's not ACK_CC_CI should
// get rejected.
TEST_F(PpmTest, WaitForCmdAck_ErrorIfNotCommandComplete) {
ASSERT_EQ(Initialize(), 0);
ClearNotifiedCount();
QueueExpectedCommandWithResult({UCSI_CMD_SET_NOTIFICATION_ENABLE, 0});
struct ucsi_control control = {.command = UCSI_CMD_SET_NOTIFICATION_ENABLE,
.data_length = 0};
ASSERT_NE(-1, WriteCommand(control));
EXPECT_TRUE(WaitForCommandPendingState(false));
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
EXPECT_EQ(2, GetNotifiedCount()); // one notification each for busy and
// command complete
// Resend the previous command instead of a CC Ack.
ASSERT_NE(-1, WriteCommand(control));
ASSERT_TRUE(WaitForNotification(3));
EXPECT_CCI(kCciError, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
}
// The PPM state machine allows you to both ACK Command Complete AND
// ACK Connector Indication. Make sure this is supported in the command loop
// path.
TEST_F(PpmTest, WaitForCmdAck_SupportSimultaneousAckCCAndCI) {
InitializeToIdleNotify();
TriggerConnectorChangedNotification(kDefaultAlertPort);
int notified_count = GetNotifiedCount();
// PPM is waiting for a connector_change_ack from the OPM now. Don't send it,
// instead send a new command.
struct ucsi_control control = {.command = UCSI_CMD_GET_CONNECTOR_CAPABILITY,
.data_length = 0};
QueueExpectedCommandWithResult({UCSI_CMD_GET_CONNECTOR_CAPABILITY, 0});
ASSERT_NE(-1, WriteCommand(control)); // TODO: can connector changed
// indicator in CCI be cleared here?
ASSERT_TRUE(WaitForNotification(++notified_count));
EXPECT_CCI(kCciCmdComplete, __LINE__);
// PPM is waiting for connector_change_ack and command_complete_ack. Send them
// together.
QueueExpectedCommandWithResult({UCSI_CMD_ACK_CC_CI, 0});
WriteAckCommand(/*connector_change_ack*/ true, /*command_complete_ack*/ true);
// one busy notification for ACK_CC_CI and one for ack_command
ASSERT_TRUE(WaitForNotification(notified_count + 2));
EXPECT_CCI(kCciAckCommand, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE_NOTIFY);
EXPECT_EQ(GetPpmData()->per_port_status[0].connector_status_change, 0);
EXPECT_EQ(GetPpmData()->last_connector_changed, -1);
}
// When waiting for a Connection Indicator Ack, we accept an immediate ACK_CC_CI
// to switch the state back to Idle with Notifications.
TEST_F(PpmTest, WaitForCIAck_AckImmediatelyOrLater) {
InitializeToIdleNotify();
TriggerConnectorChangedNotification(kDefaultAlertPort);
ClearNotifiedCount();
QueueExpectedCommandWithResult({UCSI_CMD_ACK_CC_CI, 0});
WriteAckCommand(/*connector_change_ack*/ true,
/*command_complete_ack*/ false);
ASSERT_TRUE(WaitForNotification(1));
EXPECT_CCI(kCciAckCommand, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE_NOTIFY);
}
// If we get an ACK_CC_CI when there is no active connector indication, we
// should fail. In this scenario, the starting state needs to be IdleNotify but
// occurs when the OPM sends other commands after receiving Connector Change
// Indication.
TEST_F(PpmTest, WaitForCIAck_FailIfNoActiveConnectorIndication) {
InitializeToIdleNotify();
int notified_count = GetNotifiedCount();
WriteAckCommand(/*connector_change_ack*/ true,
/*command_complete_ack*/ false);
ASSERT_TRUE(WaitForNotification(notified_count + 1));
EXPECT_CCI(kCciError, __LINE__);
EXPECT_TRUE(WaitForCommandPendingState(false));
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE_NOTIFY);
}
// TODO(UCSI WG): Clarify PPM behavior when incorrect ACK is received. Current
// implementation returns a PPM error, but does not change PPM state. The
// FailIfSendCIAck and FailIfNoAck tests validate the implementation.
// When waiting for a Command Complete Ack, send a Connector Change Ack instead
TEST_F(PpmTest, WaitForCCAck_FailIfSendCIAck) {
InitializeToIdleNotify();
ClearNotifiedCount();
// Send a command and reach PPM_STATE_WAITING_CC_ACK
struct ucsi_control control = {.command = UCSI_CMD_GET_CONNECTOR_CAPABILITY,
.data_length = 0};
QueueExpectedCommandWithResult({UCSI_CMD_GET_CONNECTOR_CAPABILITY, 0});
ASSERT_NE(-1, WriteCommand(control));
ASSERT_TRUE(WaitForNotification(2));
EXPECT_CCI(kCciCmdComplete, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
// Send a bad ack and expect an error and no state change
WriteAckCommand(true /*connector_change_ack*/,
false /*command_complete_ack*/);
ASSERT_TRUE(WaitForNotification(3));
EXPECT_CCI(kCciError, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
}
// When waiting for a Command Complete Ack, send an Ack without setting either
// Command Complete Ack or Connector Change Ack
TEST_F(PpmTest, WaitForCCAck_FailIfNoAck) {
InitializeToIdleNotify();
ClearNotifiedCount();
// Send a command and reach PPM_STATE_WAITING_CC_ACK
struct ucsi_control control = {.command = UCSI_CMD_GET_CONNECTOR_CAPABILITY,
.data_length = 0};
QueueExpectedCommandWithResult({UCSI_CMD_GET_CONNECTOR_CAPABILITY, 0});
ASSERT_NE(-1, WriteCommand(control));
ASSERT_TRUE(WaitForNotification(2));
EXPECT_CCI(kCciCmdComplete, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
// Send a bad ack and expect an error and no state change
WriteAckCommand(false /*connector_change_ack*/,
false /*command_complete_ack*/);
ASSERT_TRUE(WaitForNotification(3));
EXPECT_CCI(kCciError, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
}
// When an LPM command fails, check that the appropriate CCI bits are set, and
// that the next command succeeds.
TEST_F(PpmTest, LpmError_AcceptsNewCommand) {
InitializeToIdleNotify();
ClearNotifiedCount();
struct ucsi_control control = {.command = UCSI_CMD_GET_CONNECTOR_CAPABILITY,
.data_length = 0};
// Return an error from the LPM and expect a CCI error.
QueueExpectedCommandWithResult({UCSI_CMD_GET_CONNECTOR_CAPABILITY, -1});
ASSERT_NE(-1, WriteCommand(control));
ASSERT_TRUE(WaitForNotification(2));
EXPECT_CCI(kCciError, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_IDLE_NOTIFY);
// Test acceptance of new message
ClearNotifiedCount();
QueueExpectedCommandWithResult({UCSI_CMD_GET_CONNECTOR_CAPABILITY, 0});
ASSERT_NE(-1, WriteCommand(control));
ASSERT_TRUE(WaitForNotification(2));
EXPECT_CCI(kCciCmdComplete, __LINE__);
EXPECT_EQ(GetPpmData()->ppm_state, PPM_STATE_WAITING_CC_ACK);
}