blob: 8f25efc652a7cc7e651d3ee81443b11be584bf36 [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libec/ec_command.h"
using testing::_;
using testing::InvokeWithoutArgs;
using testing::Return;
namespace ec {
namespace {
constexpr int kDummyFd = 0;
constexpr int kIoctlFailureRetVal = -1;
template <typename O, typename I>
class MockEcCommand : public EcCommand<O, I> {
public:
using EcCommand<O, I>::EcCommand;
~MockEcCommand() override = default;
using Data = typename EcCommand<O, I>::Data;
MOCK_METHOD(int, ioctl, (int fd, uint32_t request, Data* data));
};
class MockFpModeCommand : public MockEcCommand<struct ec_params_fp_mode,
struct ec_response_fp_mode> {
public:
MockFpModeCommand() : MockEcCommand(EC_CMD_FP_MODE, 0, {.mode = 1}) {}
};
class MockEmptyResponseCommand
: public MockEcCommand<struct ec_params_fp_seed, EmptyParam> {
public:
MockEmptyResponseCommand()
: MockEcCommand(EC_CMD_FP_SEED, 0, {.seed = "foo"}) {}
};
// ioctl behavior for EC commands:
// returns sizeof(EC response) (>=0) when the command goes to the EC, -1 if
// there's a failure to communicate with the EC or other kernel failure.
//
// In the case where the command went to the EC, cmd.result is error code from
// returned from the EC (EC_RES_SUCCESS, EC_RES_BUSY, EC_RES_UNAVAILABLE,
// etc.)
//
// In the case where an error code (i.e., not EC_RES_SUCCESS) is returned
// by code in the EC, the EC logic will set the "response_size" to 0. In
// this case, the ioctl returns 0 since it is returning the size of the
// response. See
// https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/ec/common/host_command.c;l=202-205;drc=d64d5ca86d1fd6274011146e33597ef01bf551b1
TEST(EcCommand, Run_Success) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
});
EXPECT_TRUE(mock.Run(kDummyFd));
}
TEST(EcCommand, Run_IoctlFailure) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl).WillOnce(Return(kIoctlFailureRetVal));
EXPECT_FALSE(mock.Run(kDummyFd));
}
TEST(EcCommand, Run_CommandFailure) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
// Test the case where the ioctl itself succeeds, the but the EC
// command did not. In this case, "result" will be set, but the
// response size will not match the command's response size.
data->cmd.result = EC_RES_ACCESS_DENIED;
return 0;
});
EXPECT_FALSE(mock.Run(kDummyFd));
}
// It's possible for the implementation of the command to incorrectly return
// the wrong size. The kernel driver does not check for this, but Run() should
// return an error since the data returned is not what was requested.
TEST(EcCommand, Run_ResponseSizeTooSmall) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize - 1;
});
EXPECT_FALSE(mock.Run(kDummyFd));
}
// It's possible for the implementation of the command to incorrectly return
// the wrong size. In the case where the size is too large, the kernel driver
// will return an error, but we'll be defensive and check as well in case the
// implementation changes.
// See
// https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/third_party/kernel/upstream/drivers/platform/chrome/cros_ec_spi.c;l=259-261;drc=a0386bba70934d42f586eaf68b21d5eeaffa7bd0
TEST(EcCommand, Run_ResponseSizeTooLarge) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize + 1;
});
EXPECT_FALSE(mock.Run(kDummyFd));
}
TEST(EcCommand, Run_CommandWithEmptyResponse_Failure) {
MockEmptyResponseCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockEmptyResponseCommand::Data* data) {
// In the case where code in the EC sets a return value of something
// other than EC_RES_SUCCESS, the code that sends the response from the
// EC sets the size to 0, so the ioctl will always return 0 in that
// case. See
// https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/ec/common/host_command.c;l=202-205;drc=d64d5ca86d1fd6274011146e33597ef01bf551b1
data->cmd.result = EC_RES_INVALID_PARAM;
return 0;
});
EXPECT_FALSE(mock.Run(kDummyFd));
}
TEST(EcCommand, ConstReq) {
const MockFpModeCommand mock;
EXPECT_TRUE(mock.Req());
}
TEST(EcCommand, ConstResp) {
const MockFpModeCommand mock;
EXPECT_TRUE(mock.Resp());
}
TEST(EcCommand, Run_CheckResult_Success) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
});
EXPECT_TRUE(mock.Run(kDummyFd));
EXPECT_EQ(mock.Result(), EC_RES_SUCCESS);
}
TEST(EcCommand, Run_CheckResult_Failure) {
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
// Note that it's not expected that the result would be set by the
// kernel driver in this case, but we want to be defensive against
// the behavior in case there is an instance where it does.
data->cmd.result = EC_RES_ERROR;
return kIoctlFailureRetVal;
});
EXPECT_FALSE(mock.Run(kDummyFd));
EXPECT_EQ(mock.Result(), kEcCommandUninitializedResult);
}
TEST(EcCommand, Run_CheckResult_CommandWithEmptyResponse_Failure) {
MockEmptyResponseCommand mock;
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockEmptyResponseCommand::Data* data) {
// In the case where code in the EC sets a return value of something
// other than EC_RES_SUCCESS, the code that sends the response from the
// EC sets the size to 0, so the ioctl will always return 0 in that
// case. See
// https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/ec/common/host_command.c;l=202-205;drc=d64d5ca86d1fd6274011146e33597ef01bf551b1
data->cmd.result = EC_RES_ACCESS_DENIED;
return 0;
});
EXPECT_FALSE(mock.Run(kDummyFd));
EXPECT_EQ(mock.Result(), EC_RES_ACCESS_DENIED);
}
TEST(EcCommand, RunWithMultipleAttempts_Success) {
constexpr int kNumAttempts = 2;
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.Times(kNumAttempts)
// First ioctl() fails
.WillOnce(InvokeWithoutArgs([]() {
errno = ETIMEDOUT;
return kIoctlFailureRetVal;
}))
// Second ioctl() succeeds
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
});
EXPECT_TRUE(mock.RunWithMultipleAttempts(kDummyFd, kNumAttempts));
}
TEST(EcCommand, RunWithMultipleAttempts_Timeout_Failure) {
constexpr int kNumAttempts = 2;
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
.Times(kNumAttempts)
// All calls to ioctl() timeout
.WillRepeatedly(InvokeWithoutArgs([]() {
errno = ETIMEDOUT;
return kIoctlFailureRetVal;
}));
EXPECT_FALSE(mock.RunWithMultipleAttempts(kDummyFd, kNumAttempts));
}
TEST(EcCommand, RunWithMultipleAttempts_ErrorNotTimeout_Failure) {
constexpr int kNumAttempts = 2;
MockFpModeCommand mock;
EXPECT_CALL(mock, ioctl)
// Errors other than timeout should cause immediate failure even when
// attempting retries.
.Times(1)
.WillOnce(InvokeWithoutArgs([]() {
errno = EINVAL;
return kIoctlFailureRetVal;
}));
EXPECT_FALSE(mock.RunWithMultipleAttempts(kDummyFd, kNumAttempts));
}
TEST(EcCommand, RunWithMultipleAttempts_AccessDenied) {
MockFpModeCommand mock;
// ioctl should only be called once because we won't retry access denied
// failures.
EXPECT_CALL(mock, ioctl)
.WillOnce([](int, uint32_t, MockFpModeCommand::Data* data) {
// ioctl succeeds, but the EC command did not. In this case, "result"
// will be set, but the response size will not match the command's
// response size.
data->cmd.result = EC_RES_ACCESS_DENIED;
return 0;
});
constexpr int kNumAttempts = 2;
EXPECT_FALSE(mock.RunWithMultipleAttempts(kDummyFd, kNumAttempts));
}
} // namespace
} // namespace ec