| // Copyright 2019 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef LIBEC_EC_COMMAND_H_ | |
| #define LIBEC_EC_COMMAND_H_ | |
| #include <sys/ioctl.h> | |
| #include <cerrno> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <limits> | |
| #include <libusb-1.0/libusb.h> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <base/logging.h> | |
| #include <chromeos/ec/cros_ec_dev.h> | |
| #include <chromeos/ec/ec_commands.h> | |
| #include "libec/ec_usb_endpoint.h" | |
| namespace ec { | |
| // Character device exposing the EC command interface. | |
| inline constexpr char kCrosEcPath[] = "/dev/cros_ec"; | |
| // Map from |enum ec_status| to the pretty string. | |
| #define EC_MAP_ITEM(k, v) \ | |
| { k, #v } | |
| inline const std::unordered_map<enum ec_status, const char*> kECStatusText = | |
| EC_STATUS_TEXT; | |
| #undef EC_MAP_ITEM | |
| enum class EcCmdVersionSupportStatus { | |
| UNKNOWN = 0, | |
| SUPPORTED = 1, | |
| UNSUPPORTED = 2, | |
| }; | |
| // Upper bound of the host command packet transfer size. Although the EC can | |
| // request a smaller transfer size, this value should never be smaller than | |
| // the largest size the EC can transfer; this value is used to create buffers | |
| // to hold the data to be transferred to and from the EC. | |
| // | |
| // The standard transfer size for v3 commands is is big enough to handle a | |
| // request/response header, flash write offset/size, and 512 bytes of flash | |
| // data: | |
| // sizeof(ec_host_request): 8 | |
| // sizeof(ec_params_flash_write): 8 | |
| // payload 512 | |
| // = 544 (0x220) | |
| // See | |
| // https://source.chromium.org/chromiumos/_/chromium/chromiumos/platform/ec/+/f3ffccd7d0fe4d0ce60434310795a7bfdaa5274c:chip/stm32/spi.c;l=82;drc=dede4e01ae4c877bb05d671087a6e85a29a0f902 | |
| // https://source.chromium.org/chromiumos/_/chromium/chromiumos/platform/ec/+/f3ffccd7d0fe4d0ce60434310795a7bfdaa5274c:chip/npcx/shi.c;l=118;drc=2a5ce905c11807a19035f7a072489df04be4db97 | |
| inline constexpr int kMaxPacketSize = 544; | |
| // 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> | |
| inline constexpr size_t realsizeof = std::is_empty_v<T> ? 0 : sizeof(T); | |
| // Variable length arrays in request or response for the EcCommand template | |
| // below. T refers to the type of array data and RemainingParams refers to the | |
| // type including remaining parameters in the struct. | |
| template <typename T, typename RemainingParams = EmptyParam> | |
| using ArrayData = | |
| std::array<T, (kMaxPacketSize - realsizeof<RemainingParams>) / sizeof(T)>; | |
| inline constexpr uint32_t kVersionZero = 0; | |
| inline constexpr uint32_t kVersionOne = 1; | |
| inline constexpr auto kEcCommandUninitializedResult = | |
| std::numeric_limits<uint32_t>::max(); | |
| class EcCommandInterface { | |
| public: | |
| virtual ~EcCommandInterface() = default; | |
| virtual bool Run(int fd) = 0; | |
| virtual bool Run(ec::EcUsbEndpointInterface& uep) = 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 Params, typename Response> | |
| class EcCommand : public EcCommandInterface { | |
| public: | |
| explicit EcCommand(uint32_t cmd, uint32_t ver = 0, const Params& req = {}) | |
| : request_(req), | |
| cmd_({.version = ver, | |
| .command = cmd, | |
| // "outsize" is the number of bytes of data going "out" | |
| // to the EC. | |
| .outsize = realsizeof<Params>, | |
| // "insize" is the number of bytes we can accept as the | |
| // "incoming" data from the EC. | |
| .insize = realsizeof<Response>, | |
| .result = kEcCommandUninitializedResult}) {} | |
| EcCommand(const EcCommand&) = delete; | |
| EcCommand& operator=(const EcCommand&) = delete; | |
| ~EcCommand() override = default; | |
| void SetRespSize(uint32_t insize) { cmd_.insize = insize; } | |
| void SetReqSize(uint32_t outsize) { cmd_.outsize = outsize; } | |
| void SetReq(const Params& req) { request_ = 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; | |
| bool Run(ec::EcUsbEndpointInterface& uep) override; | |
| bool RunWithMultipleAttempts(int fd, int num_attempts) override; | |
| virtual Response* Resp() { return &response_; } | |
| virtual const Response* Resp() const { return &response_; } | |
| virtual uint32_t RespSize() const { return cmd_.insize; } | |
| Params* Req() { return &request_; } | |
| const Params* Req() const { return &request_; } | |
| virtual uint32_t ReqSize() const { return cmd_.outsize; } | |
| virtual uint32_t Result() const { return cmd_.result; } | |
| virtual std::string ResultString() const { | |
| return ResultToString(cmd_.result); | |
| } | |
| uint32_t Version() const override { return cmd_.version; } | |
| uint32_t Command() const override { return cmd_.command; } | |
| protected: | |
| struct Data { | |
| struct cros_ec_command_v2 cmd; | |
| union { | |
| Params req; | |
| Response resp; | |
| }; | |
| }; | |
| std::string ResultToString(uint32_t ec_cmd_result) const; | |
| bool ErrorTypeCanBeRetried(uint32_t ec_cmd_result); | |
| private: | |
| virtual int ioctl(int fd, uint32_t request, Data* data) { | |
| return ::ioctl(fd, request, data); | |
| } | |
| int usb_xfer(const struct usb_endpoint& uep, | |
| void* outbuf, | |
| int outlen, | |
| void* inbuf, | |
| int inlen); | |
| unsigned int kUsbXferTimeoutMs = 1000; | |
| Params request_{}; | |
| Response response_{}; | |
| struct cros_ec_command_v2 cmd_ {}; | |
| }; | |
| /** | |
| * @tparam Params request structure | |
| * @tparam Response response structure | |
| * @param ec_fd File descriptor for opened EC device | |
| * @return true if command is successful in which case cmd.Result() is | |
| * EC_RES_SUCCESS. false if either the ioctl fails or the command fails on | |
| * the EC (returns something other than EC_RES_SUCCESS). If the ioctl fails, | |
| * cmd.Result() will be kEcCommandUninitializedResult. If the command fails | |
| * on the EC, cmd.Result() will be set to the error returned by the EC (e.g., | |
| * EC_RES_BUSY, EC_RES_UNAVAILABLE, etc.) See ec_command_test.cc for details. | |
| */ | |
| template <typename Params, typename Response> | |
| bool EcCommand<Params, Response>::Run(int ec_fd) { | |
| cmd_.result = kEcCommandUninitializedResult; | |
| Data data = {.cmd = cmd_, .req = request_}; | |
| int ret = ioctl(ec_fd, CROS_EC_DEV_IOCXCMD_V2, &data); | |
| if (ret < 0) { | |
| PLOG(ERROR) << "cros_ec ioctl command 0x" << std::hex << cmd_.command | |
| << std::dec << " failed"; | |
| return false; | |
| } | |
| cmd_.result = data.cmd.result; | |
| response_ = data.resp; | |
| // Log errors returned from the EC. INVALID_COMMAND and INVALID_VERSION are | |
| // commonly used to probe the EC thus more-or-less expected. | |
| if (cmd_.result == EC_RES_INVALID_COMMAND || | |
| cmd_.result == EC_RES_INVALID_VERSION) { | |
| LOG(INFO) << "cros_ec does not support cmd=0x" << std::hex << cmd_.command | |
| << std::dec << " ver=" << cmd_.version; | |
| } else if (cmd_.result != EC_RES_SUCCESS && | |
| cmd_.result != EC_RES_IN_PROGRESS) { | |
| LOG(WARNING) << "cros_ec returned error=" << ResultToString(cmd_.result) | |
| << " for cmd=0x" << std::hex << cmd_.command; | |
| } | |
| // Check size in addition to result code to guard against bugs in the | |
| // command implementation. See ec_command_test.cc for details and example test | |
| // cases. | |
| return (static_cast<uint32_t>(ret) == cmd_.insize) && | |
| cmd_.result == EC_RES_SUCCESS; | |
| } | |
| template <typename Params, typename Response> | |
| int EcCommand<Params, Response>::usb_xfer(const struct usb_endpoint& uep, | |
| void* outbuf, | |
| int outlen, | |
| void* inbuf, | |
| int inlen) { | |
| int r, transferred; | |
| /* Send data out */ | |
| if (outbuf && outlen) { | |
| transferred = 0; | |
| r = libusb_bulk_transfer(uep.dev_handle, uep.address, | |
| (unsigned char*)outbuf, outlen, &transferred, | |
| kUsbXferTimeoutMs); | |
| if (r < LIBUSB_SUCCESS) { | |
| LOG(ERROR) << "libusb_bulk_transfer: " << libusb_error_name(r); | |
| return -1; | |
| } | |
| if (transferred != outlen) { | |
| LOG(ERROR) << "Sent " << transferred << " of " << outlen << " bytes"; | |
| return -1; | |
| } | |
| VLOG(1) << "Sent " << outlen << " bytes"; | |
| } | |
| /* Read reply back */ | |
| if (inbuf && inlen) { | |
| transferred = 0; | |
| r = libusb_bulk_transfer(uep.dev_handle, uep.address | 0x80, | |
| (unsigned char*)inbuf, inlen, &transferred, | |
| kUsbXferTimeoutMs); | |
| if (r < LIBUSB_SUCCESS) { | |
| LOG(ERROR) << "libusb_bulk_transfer: " << libusb_error_name(r); | |
| return -1; | |
| } | |
| if (transferred != inlen) { | |
| LOG(ERROR) << "Received " << transferred << " of " << inlen << " bytes"; | |
| return -1; | |
| } | |
| VLOG(1) << "Received " << inlen << " bytes"; | |
| } | |
| return 0; | |
| } | |
| static inline int sum_bytes(const void* data, int length) { | |
| const uint8_t* bytes = (const uint8_t*)data; | |
| int sum = 0; | |
| for (int i = 0; i < length; i++) | |
| sum += bytes[i]; | |
| return sum; | |
| } | |
| template <typename Params, typename Response> | |
| bool EcCommand<Params, Response>::Run(ec::EcUsbEndpointInterface& uep) { | |
| cmd_.result = kEcCommandUninitializedResult; | |
| if (!uep.ClaimInterface()) { | |
| LOG(WARNING) << "Failed to claim USB interface"; | |
| return false; | |
| } | |
| size_t req_len = sizeof(struct ec_host_request) + cmd_.outsize; | |
| uint8_t* req_buf = reinterpret_cast<uint8_t*>(malloc(req_len)); | |
| if (req_buf == nullptr) { | |
| LOG(ERROR) << "Failed to allocate memory for request"; | |
| uep.ReleaseInterface(); | |
| return false; | |
| } | |
| struct ec_host_request* req = (struct ec_host_request*)req_buf; | |
| uint8_t* req_data = req_buf + sizeof(struct ec_host_request); | |
| req->struct_version = EC_HOST_REQUEST_VERSION; /* 3 */ | |
| req->checksum = 0; | |
| req->command = cmd_.command; | |
| req->command_version = cmd_.version; | |
| req->reserved = 0; | |
| req->data_len = cmd_.outsize; | |
| if (cmd_.outsize) | |
| memcpy(req_data, &request_, cmd_.outsize); | |
| req->checksum = (uint8_t)(-sum_bytes(req, req_len)); | |
| size_t res_len = sizeof(struct ec_host_response) + cmd_.insize; | |
| uint8_t* res_buf = reinterpret_cast<uint8_t*>(malloc(res_len)); | |
| if (res_buf == nullptr) { | |
| LOG(ERROR) << "Failed to allocate memory for response"; | |
| free(req); | |
| uep.ReleaseInterface(); | |
| return false; | |
| } | |
| struct ec_host_response* res = (struct ec_host_response*)res_buf; | |
| uint8_t* res_data = res_buf + sizeof(struct ec_host_response); | |
| memset(res_buf, 0, res_len); | |
| if (usb_xfer(uep.GetEndpointPtr(), req, req_len, res, res_len)) { | |
| LOG(ERROR) << "Command 0x" << std::hex << cmd_.command << std::dec | |
| << " over USB failed"; | |
| } else { | |
| cmd_.result = res->result; | |
| if (cmd_.insize) { | |
| memcpy(&response_, res_data, cmd_.insize); | |
| } | |
| } | |
| free(req); | |
| free(res); | |
| /* We may fail here but the command was successfully executed. */ | |
| uep.ReleaseInterface(); | |
| return true; | |
| } | |
| template <typename Params, typename Response> | |
| bool EcCommand<Params, Response>::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) | |
| << "cros_ec ioctl command 0x" << std::hex << cmd_.command << std::dec | |
| << " succeeded on attempt " << retry + 1 << "/" << num_attempts | |
| << "."; | |
| return true; | |
| } | |
| if (!ErrorTypeCanBeRetried(Result()) || (errno != ETIMEDOUT)) { | |
| LOG(ERROR) << "cros_ec ioctl command 0x" << std::hex << cmd_.command | |
| << std::dec << " failed on attempt " << retry + 1 << "/" | |
| << num_attempts << ", retry is not allowed for error"; | |
| return false; | |
| } | |
| LOG(ERROR) << "cros_ec ioctl command 0x" << std::hex << cmd_.command | |
| << std::dec << " failed on attempt " << retry + 1 << "/" | |
| << num_attempts; | |
| } | |
| return false; | |
| } | |
| template <typename Params, typename Response> | |
| std::string EcCommand<Params, Response>::ResultToString( | |
| uint32_t ec_cmd_result) const { | |
| const auto it = kECStatusText.find((enum ec_status)ec_cmd_result); | |
| if (it == kECStatusText.end()) { | |
| return std::to_string(ec_cmd_result); | |
| } | |
| return std::string(it->second); | |
| } | |
| template <typename Params, typename Response> | |
| bool EcCommand<Params, Response>::ErrorTypeCanBeRetried( | |
| uint32_t ec_cmd_result) { | |
| switch (ec_cmd_result) { | |
| case kEcCommandUninitializedResult: | |
| case EC_RES_TIMEOUT: | |
| case EC_RES_BUSY: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| } // namespace ec | |
| #endif // LIBEC_EC_COMMAND_H_ |