| // Copyright 2017 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. |
| // |
| // This file contains structures used to facilitate EC firmware updates |
| // over USB. Note that many contents in this file are copied from EC overlay, |
| // and it might be trickier to include them directly. |
| // |
| // The firmware update protocol consists of two phases: connection |
| // establishment and actual image transfer. |
| // |
| // Image transfer is done in 1K blocks. The host supplying the image |
| // encapsulates blocks in PDUs by prepending a header including the flash |
| // offset where the block is destined and its digest. |
| // |
| // The EC device responds to each PDU with a confirmation which is 1 byte |
| // response. Zero value means success, non zero value is the error code |
| // reported by EC. |
| // |
| // To establish the connection, the host sends a different PDU, which |
| // contains no data and is destined to offset 0. Receiving such a PDU |
| // signals the EC that the host intends to transfer a new image. |
| // |
| // The connection establishment response is described by the |
| // FirstResponsePdu structure below. |
| |
| #ifndef HAMMERD_UPDATE_FW_H_ |
| #define HAMMERD_UPDATE_FW_H_ |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <base/macros.h> |
| #include <gtest/gtest_prod.h> |
| #include <openssl/sha.h> |
| |
| #include "hammerd/fmap_utils.h" |
| #include "hammerd/usb_utils.h" |
| |
| namespace hammerd { |
| |
| constexpr int kUpdateProtocolVersion = 6; |
| constexpr uint32_t kUpdateDoneCmd = 0xB007AB1E; |
| constexpr uint32_t kUpdateExtraCmd = 0xB007AB1F; |
| constexpr int kEntropySize = 32; |
| |
| enum class FirstResponsePduHeaderType { |
| kCR50 = 0, |
| kCommon = 1, |
| }; |
| |
| enum class UpdateCommandResponseStatus : uint32_t { |
| kSuccess = 0, |
| kBadAddr = 1, |
| kEraseFailure = 2, |
| kDataError = 3, |
| kWriteFailure = 4, |
| kVerifyError = 5, |
| kGenError = 6, |
| kMallocError = 7, |
| kRollbackError = 8, |
| kRateLimitError = 9, |
| kRwsigBusy = 10, |
| }; |
| |
| // The extra vendor subcommand. |
| enum class UpdateExtraCommand : uint16_t { |
| kImmediateReset = 0, |
| kJumpToRW = 1, |
| kStayInRO = 2, |
| kUnlockRW = 3, |
| kUnlockRollback = 4, |
| kInjectEntropy = 5, |
| kPairChallenge = 6, |
| kTouchpadInfo = 7, |
| kTouchpadDebug = 8, |
| kConsoleReadInit = 9, |
| kConsoleReadNext = 10, |
| kMaxValue = kTouchpadInfo |
| }; |
| const char* ToString(UpdateExtraCommand subcommand); |
| |
| enum class EcResponseStatus : uint8_t { |
| kSuccess = 0, |
| kInvalidCommand = 1, |
| kError = 2, |
| kInvalidParam = 3, |
| kAccessDenied = 4, |
| kInvalidResponse = 5, |
| kInvalidVersion = 6, |
| kInvalidChecksum = 7, |
| kInProgress = 8, // Accepted, command in progress |
| kUnavailable = 9, // No response available |
| kTimeout = 10, // We got a timeout |
| kOverflow = 11, // Table / data overflow |
| kInvalidHeader = 12, // Header contains invalid data |
| kRequestTruncated = 13, // Didn't get the entire request |
| kResponseTooBig = 14, // Response was too big to handle |
| kBusError = 15, // Communications bus error |
| kBusy = 16, // Up but too busy. Should retry |
| }; |
| |
| // Flash protection masks. |
| // Defined at EC repository: include/ec_commands.h |
| enum class EcFlashProtect : uint32_t { |
| kROAtBoot = 1 << 0, |
| kRONow = 1 << 1, |
| kAllNow = 1 << 2, |
| kGpioAsserted = 1 << 3, |
| kErrorStuck = 1 << 4, |
| kErrorInconsistent = 1 << 5, |
| kAllAtBoot = 1 << 6, |
| kRWAtBoot = 1 << 7, |
| kRWNow = 1 << 8, |
| kRollbackAtBoot = 1 << 9, |
| kRollbackNow = 1 << 10, |
| }; |
| |
| // This is the frame format the host uses when sending update PDUs over USB. |
| // |
| // The PDUs are up to 1K bytes in size, they are fragmented into USB chunks of |
| // 64 bytes each and reassembled on the receive side before being passed to |
| // the flash update function. |
| // |
| // The flash update function receives the unframed PDU body, and puts its reply |
| // into the same buffer the PDU was in. |
| struct UpdateFrameHeader { |
| uint32_t block_size; // Total frame size, including this field. |
| uint32_t block_digest; |
| uint32_t block_base; |
| UpdateFrameHeader() : UpdateFrameHeader(0, 0, 0) {} |
| UpdateFrameHeader(uint32_t size, uint32_t digest, uint32_t base) |
| : block_size(htobe32(size)), |
| block_digest(htobe32(digest)), |
| block_base(htobe32(base)) {} |
| }; |
| |
| // Response to the connection establishment request. |
| // |
| // When responding to the very first packet of the update sequence, the |
| // original USB update implementation was responding with a four byte value, |
| // just as to any other block of the transfer sequence. |
| // |
| // It became clear that there is a need to be able to enhance the update |
| // protocol, while staying backwards compatible. |
| // |
| // All newer protocol versions (starting with version 2) respond to the very |
| // first packet with an 8 byte or larger response, where the first 4 bytes are |
| // a version specific data, and the second 4 bytes - the protocol version |
| // number. |
| // |
| // This way the host receiving of a four byte value in response to the first |
| // packet is considered an indication of the target running the 'legacy' |
| // protocol, version 1. Receiving of an 8 byte or longer response would |
| // communicates the protocol version in the second 4 bytes. |
| struct FirstResponsePdu { |
| uint32_t return_value; |
| |
| // The below fields are present in versions 2 and up. |
| // Type of header following (one of first_response_pdu_header_type) |
| uint16_t header_type; |
| uint16_t protocol_version; // Must be kUpdateProtocolVersion |
| uint32_t maximum_pdu_size; // Maximum PDU size |
| uint32_t flash_protection; // Flash protection status |
| uint32_t offset; // Offset of the other region |
| char version[32]; // Version string of the other region |
| int32_t min_rollback; // Minimum rollback version that RO will accept |
| uint32_t key_version; // RO public key version |
| }; |
| |
| enum class SectionName { |
| RO, |
| RW, |
| Invalid, |
| }; |
| const char* ToString(SectionName name); |
| SectionName OtherSection(SectionName name); |
| |
| // product_id format from elan_i2c.h. definition of ETP_PRODUCT_ID_FORMAT_STRING |
| constexpr char kElanFormatString[] = "%d.0"; |
| constexpr uint8_t kElanBrokenFwVersion = 0xff; |
| |
| constexpr char kStFormatString[] = "%d.%d"; |
| |
| const uint16_t ST_VENDOR_ID = 0x0483; |
| const uint16_t ELAN_VENDOR_ID = 0x04f3; |
| |
| // Below is the touchpad_info struct from src/platform/ec/include/update_fw.h. |
| struct __attribute__((packed)) TouchpadInfo { |
| uint8_t status; // Indicate if we get info from touchpad |
| uint8_t reserved; // Reserved for padding |
| uint16_t vendor; // Vendor USB id |
| uint32_t fw_address; // Virtual address to touchpad firmware |
| uint32_t fw_size; // Size of the touchpad firmware |
| // Checksum of the entire touchpad firmware accepted by the EC image |
| uint8_t allowed_fw_hash[SHA256_DIGEST_LENGTH]; |
| union { |
| struct { |
| uint16_t id; |
| uint16_t fw_version; |
| uint16_t fw_checksum; |
| } elan; |
| struct { |
| uint16_t id; |
| uint16_t fw_version; |
| uint16_t fw_checksum; |
| } st; |
| }; |
| }; |
| |
| // The section information of the new EC image. |
| struct SectionInfo { |
| SectionName name; |
| uint32_t offset; |
| uint32_t size; |
| char version[32]; |
| int32_t rollback; |
| int32_t key_version; |
| explicit SectionInfo(SectionName name); |
| SectionInfo(SectionName name, |
| uint32_t offset, |
| uint32_t size, |
| const char* version, |
| int32_t rollback, |
| int32_t key_version); |
| friend bool operator==(const SectionInfo& lhs, const SectionInfo& rhs); |
| friend bool operator!=(const SectionInfo& lhs, const SectionInfo& rhs); |
| }; |
| |
| class FirmwareUpdaterInterface { |
| public: |
| virtual ~FirmwareUpdaterInterface() = default; |
| |
| // Scans the new EC image and retrieve versions of RO and RW sections. |
| virtual bool LoadEcImage(const std::string& ec_image) = 0; |
| |
| // Retrieves local touchpad firmware. |
| virtual bool LoadTouchpadImage(const std::string& touchpad_image) = 0; |
| |
| virtual bool UsbSysfsExists() = 0; |
| virtual UsbConnectStatus ConnectUsb() = 0; |
| // Tries to connect to the USB endpoint during a period of time. |
| virtual UsbConnectStatus TryConnectUsb() = 0; |
| |
| // Closes the connection to the USB endpoint. |
| virtual void CloseUsb() = 0; |
| |
| // Setups the connection with the EC firmware by sending the first PDU. |
| // Returns true if successfully setup the connection. |
| virtual bool SendFirstPdu() = 0; |
| |
| // Indicates to hammer that transferal of EC/touchpad firmware has completed. |
| // Upon receipt of this message the target state machine transitions into |
| // the 'rx_idle' state. The host may send an extension command to reset the |
| // target after this. |
| virtual void SendDone() = 0; |
| |
| // Injects entropy into the hammer device. |
| virtual bool InjectEntropy() = 0; |
| |
| // Sends the external command through USB. The format of the payload is: |
| // 4 bytes 4 bytes 4 bytes 2 bytes variable size |
| // +-----------+--------------+---------------+-----------+------~~~-------+ |
| // + total size| block digest | EXT_CMD | Vend. sub.| data | |
| // +-----------+--------------+---------------+-----------+------~~~-------+ |
| // |
| // Where 'Vend. sub' is the vendor subcommand, and data field is subcommand |
| // dependent. The target tells between update PDUs and encapsulated vendor |
| // subcommands by looking at the EXT_CMD value - it is kUpdateExtraCmd and |
| // as such is guaranteed not to be a valid update PDU destination address. |
| virtual bool SendSubcommand(UpdateExtraCommand subcommand) = 0; |
| virtual bool SendSubcommandWithPayload(UpdateExtraCommand subcommand, |
| const std::string& cmd_body) = 0; |
| virtual bool SendSubcommandReceiveResponse(UpdateExtraCommand subcommand, |
| const std::string& cmd_body, |
| void* resp, |
| size_t resp_size, |
| bool allow_less = false) = 0; |
| |
| // Transfers the image to the target section. |
| virtual bool TransferImage(SectionName section_name) = 0; |
| |
| // Transfers the touchpad firmware to a virtual address. |
| virtual bool TransferTouchpadFirmware(uint32_t section_addr, |
| size_t data_len) = 0; |
| |
| // Returns the current section that EC is running. One of "RO" or "RW". |
| virtual SectionName CurrentSection() const = 0; |
| |
| // Returns whether the key version of the local image is valid |
| // (i.e. matches key version in RW section of EC). |
| virtual bool ValidKey() const = 0; |
| |
| // Compare the rollback version of the local image with current EC. Following |
| // comparator function convention, it returns: |
| // 0 if the rollback is equal to current EC |
| // 1 if the rollback is higher than current EC (i.e. security update) |
| // -1 if the rollback is lower than current EC (i.e. disallow update) |
| virtual int CompareRollback() const = 0; |
| |
| // Determines whether the given section has a different firmware version |
| // from that of the local file. |
| virtual bool VersionMismatch(SectionName section_name) const = 0; |
| |
| // Determines the section is locked or not. |
| virtual bool IsSectionLocked(SectionName section_name) const = 0; |
| |
| // Determines whether the local image is considered a "critical" update. |
| // IsCritical() == true must imply VersionMismatch(RW) == true. |
| virtual bool IsCritical() const = 0; |
| |
| // Unlocks the section. Need to send "Reset" command afterward. |
| virtual bool UnlockRW() = 0; |
| |
| // Determines the rollback is locked or not. |
| virtual bool IsRollbackLocked() const = 0; |
| |
| // Unlocks the rollback. Need to send "Reset" command afterward. |
| virtual bool UnlockRollback() = 0; |
| |
| virtual std::string GetEcImageVersion() const = 0; |
| |
| virtual std::string ReadConsole() = 0; |
| }; |
| |
| // Implement the core logic of updating firmware. |
| // It contains the data of the original transfer_descriptor. |
| class FirmwareUpdater : public FirmwareUpdaterInterface { |
| public: |
| explicit FirmwareUpdater(std::unique_ptr<UsbEndpointInterface> endpoint); |
| |
| // FirmwareUpdaterInterface implementation: |
| bool LoadEcImage(const std::string& ec_image) override; |
| bool LoadTouchpadImage(const std::string& touchpad_image) override; |
| bool UsbSysfsExists() override; |
| UsbConnectStatus ConnectUsb() override; |
| UsbConnectStatus TryConnectUsb() override; |
| void CloseUsb() override; |
| bool SendFirstPdu() override; |
| void SendDone() override; |
| bool InjectEntropy() override; |
| // InjectEntropyWithPayload is exposed at hammerd_api for FAFT use. |
| // It should not be called directly in hammer_updater. |
| bool InjectEntropyWithPayload(const std::string& payload); |
| bool SendSubcommand(UpdateExtraCommand subcommand) override; |
| bool SendSubcommandWithPayload(UpdateExtraCommand subcommand, |
| const std::string& cmd_body) override; |
| bool SendSubcommandReceiveResponse(UpdateExtraCommand subcommand, |
| const std::string& cmd_body, |
| void* resp, |
| size_t resp_size, |
| bool allow_less = false) override; |
| bool TransferImage(SectionName section_name) override; |
| bool TransferTouchpadFirmware(uint32_t section_addr, |
| size_t data_len) override; |
| SectionName CurrentSection() const override; |
| bool ValidKey() const override; |
| int CompareRollback() const override; |
| bool VersionMismatch(SectionName section_name) const override; |
| bool IsSectionLocked(SectionName section_name) const override; |
| bool IsCritical() const override; |
| bool UnlockRW() override; |
| |
| bool IsRollbackLocked() const override; |
| bool UnlockRollback() override; |
| |
| std::string GetEcImageVersion() const override; |
| std::string ReadConsole() override; |
| const FirstResponsePdu* GetFirstResponsePdu() const; |
| std::string GetSectionVersion(SectionName section_name) const; |
| |
| protected: |
| // Used in unit tests to inject mocks. |
| FirmwareUpdater(std::unique_ptr<UsbEndpointInterface> endpoint, |
| std::unique_ptr<FmapInterface> fmap); |
| FirmwareUpdater(const FirmwareUpdater&) = delete; |
| FirmwareUpdater& operator=(const FirmwareUpdater&) = delete; |
| |
| // Fetches the version of the currently-running section. |
| bool FetchVersion(); |
| |
| // Transfers the data of the target section. |
| bool TransferSection(const uint8_t* data_ptr, |
| uint32_t section_addr, |
| size_t data_len, |
| bool use_block_skip); |
| |
| // Transfers a block of data. |
| bool TransferBlock(UpdateFrameHeader* ufh, |
| const uint8_t* transfer_data_ptr, |
| size_t payload_size, |
| bool use_block_skip); |
| |
| // The USB endpoint interface to the hammer EC. |
| std::unique_ptr<UsbEndpointInterface> endpoint_; |
| // The fmap function interface. |
| std::unique_ptr<FmapInterface> fmap_; |
| // The information of the first response PDU. |
| FirstResponsePdu targ_; |
| // The version of the currently-running section. Retrieved through USB |
| // endpoint's configuration string descriptor as part of TryConnectUsb. |
| std::string version_; |
| // The EC image data to be updated. |
| std::string ec_image_; |
| // The touchpad image data to be updated. |
| std::string touchpad_image_; |
| // The information of the RO and RW sections in the EC image data. |
| std::vector<SectionInfo> sections_; |
| |
| friend class FirmwareUpdaterTest; |
| FRIEND_TEST(FirmwareUpdaterTest, LoadEcImage); |
| FRIEND_TEST(FirmwareUpdaterTest, TryConnectUsb_OK); |
| FRIEND_TEST(FirmwareUpdaterTest, TryConnectUsb_FAIL); |
| FRIEND_TEST(FirmwareUpdaterTest, TryConnectUsb_FetchVersion_Legacy); |
| FRIEND_TEST(FirmwareUpdaterTest, TryConnectUsb_FetchVersion_FAIL); |
| FRIEND_TEST(FirmwareUpdaterTest, SendDone); |
| FRIEND_TEST(FirmwareUpdaterTest, SendFirstPdu); |
| FRIEND_TEST(FirmwareUpdaterTest, SendFirstPdu_RwsigBusy); |
| FRIEND_TEST(FirmwareUpdaterTest, SendSubcommand_InjectEntropy); |
| FRIEND_TEST(FirmwareUpdaterTest, SendSubcommand_Reset); |
| FRIEND_TEST(FirmwareUpdaterTest, TransferImage); |
| FRIEND_TEST(FirmwareUpdaterTest, CurrentSection); |
| FRIEND_TEST(FirmwareUpdaterTest, CheckKeyRollback); |
| FRIEND_TEST(FirmwareUpdaterTest, VersionMismatch); |
| FRIEND_TEST(FirmwareUpdaterTest, IsCritical); |
| |
| private: |
| bool CheckEmptyBlock(const uint8_t* transfer_data_ptr, size_t payload_size); |
| }; |
| |
| } // namespace hammerd |
| #endif // HAMMERD_UPDATE_FW_H_ |