| // 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 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() 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) { |
| 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 (data_.cmd.command == EC_CMD_GET_CMD_VERSIONS && |
| data_.cmd.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; |
| } |
| |
| I* Resp() { return &data_.resp; } |
| uint32_t RespSize() const { return data_.cmd.insize; } |
| O* Req() { return &data_.req; } |
| 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); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(EcCommand); |
| }; |
| |
| } // namespace biod |
| |
| #endif // BIOD_EC_COMMAND_H_ |