blob: f7c2402f8f668db52d41af5728e9ac6239e3a04e [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.
#ifndef BIOD_EC_COMMAND_H_
#define BIOD_EC_COMMAND_H_
#include <sys/ioctl.h>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <base/logging.h>
#include <base/macros.h>
#include <chromeos/ec/cros_ec_dev.h>
namespace biod {
enum class EcCmdVersionSupportStatus {
UNKNOWN = 0,
SUPPORTED = 1,
UNSUPPORTED = 2,
};
// Empty request or response for the EcCommand template below.
struct EmptyParam {};
// empty struct is one byte in C++, get the size we want instead.
template <typename T>
constexpr size_t realsizeof() {
return std::is_empty<T>::value ? 0 : sizeof(T);
}
constexpr uint32_t kVersionZero = 0;
constexpr uint32_t kVersionOne = 1;
static constexpr auto kEcCommandUninitializedResult =
std::numeric_limits<uint32_t>::max();
class EcCommandInterface {
public:
virtual ~EcCommandInterface() = default;
virtual bool Run(int fd) = 0;
virtual bool RunWithMultipleAttempts(int fd, int num_attempts) = 0;
virtual uint32_t Version() const = 0;
virtual uint32_t Command() const = 0;
};
// Helper to build and send the command structures for cros_fp.
template <typename O, typename I>
class EcCommand : public EcCommandInterface {
public:
explicit EcCommand(uint32_t cmd, uint32_t ver = 0, const O& req = {})
: data_({
.cmd = {.version = ver,
.command = cmd,
.outsize = realsizeof<O>(),
.insize = realsizeof<I>(),
.result = kEcCommandUninitializedResult},
.req = req,
}) {}
EcCommand(const EcCommand&) = delete;
EcCommand& operator=(const EcCommand&) = delete;
~EcCommand() override = default;
void SetRespSize(uint32_t insize) { data_.cmd.insize = insize; }
void SetReqSize(uint32_t outsize) { data_.cmd.outsize = outsize; }
void SetReq(const O& req) { data_.req = req; }
/**
* Run an EC command.
*
* @param ec_fd file descriptor for the EC device
* @return true if command runs successfully and response size is same as
* expected, false otherwise
*
* The caller must be careful to only retry EC state-less
* commands, that can be rerun without consequence.
*/
bool Run(int ec_fd) override {
data_.cmd.result = kEcCommandUninitializedResult;
// We rely on the ioctl preserving data_.req when the command fails.
// This is important for subsequent retries using the same data_.req.
int ret = ioctl(ec_fd, CROS_EC_DEV_IOCXCMD_V2, &data_);
if (ret < 0) {
// If the ioctl fails for some reason let's make sure that the driver
// didn't touch the result.
data_.cmd.result = kEcCommandUninitializedResult;
PLOG(ERROR) << "FPMCU ioctl command 0x" << std::hex << data_.cmd.command
<< std::dec << " failed";
return false;
}
return (static_cast<uint32_t>(ret) == data_.cmd.insize);
}
bool RunWithMultipleAttempts(int fd, int num_attempts) override {
for (int retry = 0; retry < num_attempts; retry++) {
bool ret = Run(fd);
if (ret) {
LOG_IF(INFO, retry > 0)
<< "FPMCU ioctl command 0x" << std::hex << data_.cmd.command
<< std::dec << " succeeded on attempt " << retry + 1 << "/"
<< num_attempts << ".";
return true;
}
// If we just want to check the supported version of a command, and the
// command does not exist, do not emit error in the log and do not retry.
if (Command() == EC_CMD_GET_CMD_VERSIONS &&
Result() == EC_RES_INVALID_PARAM)
return false;
if (errno != ETIMEDOUT) {
LOG(ERROR) << "FPMCU ioctl command 0x" << std::hex << data_.cmd.command
<< std::dec << " failed on attempt " << retry + 1 << "/"
<< num_attempts << ", retry is not allowed for error";
return false;
}
LOG(ERROR) << "FPMCU ioctl command 0x" << std::hex << data_.cmd.command
<< std::dec << " failed on attempt " << retry + 1 << "/"
<< num_attempts;
}
return false;
}
virtual I* Resp() { return &data_.resp; }
virtual const I* Resp() const { return &data_.resp; }
uint32_t RespSize() const { return data_.cmd.insize; }
O* Req() { return &data_.req; }
const O* Req() const { return &data_.req; }
virtual uint32_t Result() const { return data_.cmd.result; }
uint32_t Version() const override { return data_.cmd.version; }
uint32_t Command() const override { return data_.cmd.command; }
struct Data {
struct cros_ec_command_v2 cmd;
union {
O req;
I resp;
};
};
protected:
Data data_;
private:
virtual int ioctl(int fd, uint32_t request, Data* data) {
return ::ioctl(fd, request, data);
}
};
} // namespace biod
#endif // BIOD_EC_COMMAND_H_