blob: f67db92932124e07fd893d0754780ebf34950122 [file] [log] [blame]
// 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.
#include "hammerd/update_fw.h"
#include <stdint.h>
#include <unistd.h>
#include <algorithm>
#include <iomanip>
#include <memory>
#include <string>
#include <base/logging.h>
#include <base/memory/free_deleter.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <fmap.h>
#include <vboot/vb21_struct.h>
#include "hammerd/usb_utils.h"
namespace hammerd {
SectionInfo::SectionInfo(std::string name)
: name(name),
offset(0),
size(0),
version(""),
rollback(0),
key_version(0) {}
FirmwareUpdater::FirmwareUpdater() : uep_(), targ_(), image_(""), sections_() {}
FirmwareUpdater::~FirmwareUpdater() {}
bool FirmwareUpdater::LoadImage(const std::string& image) {
image_.clear();
sections_ = {SectionInfo("RO"), SectionInfo("RW")};
uint8_t* image_ptr =
const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(image.data()));
size_t len = image.size();
int64_t offset = fmap_find(image_ptr, len);
if (offset < 0) {
LOG(ERROR) << "Cannot find FMAP in image";
return false;
}
// TODO: validate fmap struct more than this?
fmap* fmap = reinterpret_cast<struct fmap*>(image_ptr + offset);
if (fmap->size != len) {
LOG(ERROR) << "Mismatch between FMAP size and image size";
return false;
}
for (auto& section : sections_) {
const char* fmap_name;
const char* fmap_fwid_name;
const char* fmap_rollback_name = nullptr;
const char* fmap_key_name = nullptr;
if (section.name == "RO") {
fmap_name = "EC_RO";
fmap_fwid_name = "RO_FRID";
} else if (section.name == "RW") {
fmap_name = "EC_RW";
fmap_fwid_name = "RW_FWID";
fmap_rollback_name = "RW_RBVER";
// Key version comes from key RO (RW signature does not
// contain the key version.
fmap_key_name = "KEY_RO";
} else {
LOG(ERROR) << "Invalid section name";
return false;
}
const fmap_area* fmaparea = fmap_find_area(fmap, fmap_name);
if (!fmaparea) {
LOG(ERROR) << "Cannot find FMAP area: " << fmap_name;
return false;
}
// TODO: endianness?
section.offset = fmaparea->offset;
section.size = fmaparea->size;
fmaparea = fmap_find_area(fmap, fmap_fwid_name);
if (!fmaparea) {
LOG(ERROR) << "Cannot find FMAP area: " << fmap_fwid_name;
return false;
}
if (fmaparea->size != sizeof(section.version)) {
LOG(ERROR) << "Invalid fwid size\n";
return false;
}
memcpy(section.version, image_ptr + fmaparea->offset, fmaparea->size);
if (fmap_rollback_name &&
(fmaparea = fmap_find_area(fmap, fmap_rollback_name))) {
section.rollback =
*(reinterpret_cast<const int32_t*>(image_ptr + fmaparea->offset));
} else {
section.rollback = -1;
}
if (fmap_key_name && (fmaparea = fmap_find_area(fmap, fmap_key_name))) {
auto key = reinterpret_cast<const vb21_packed_key*>(image_ptr +
fmaparea->offset);
section.key_version = key->key_version;
} else {
section.key_version = -1;
}
}
image_ = image;
ShowHeadersVersions();
return true;
}
void FirmwareUpdater::ShowHeadersVersions() {
LOG(INFO) << "Header versions:";
for (const auto& section : sections_) {
LOG(INFO) << section.name << std::hex << std::setfill('0')
<< " offset=" << std::setw(8) << section.offset << "/"
<< std::setw(8) << section.size << " version=" << section.version
<< std::dec << " rollback=" << section.rollback
<< " key_version=" << section.key_version;
}
}
bool FirmwareUpdater::TransferImage(const std::string& section_name) {
if (!SendFirstPDU()) {
return false;
}
// Determine if the section need to update.
bool ret = false;
const uint8_t* image_ptr = reinterpret_cast<const uint8_t*>(image_.data());
for (const auto& section : sections_) {
if (section.name == section_name) {
LOG(INFO) << "Section to be updated: " << section.name;
if (section.offset != targ_.offset) {
LOG(ERROR) << "The section offset (" << section.offset
<< ") is different from the target offset (" << targ_.offset
<< ")";
return false;
}
// TODO(akahuang): We might still want to update even the version is
// identical. Add a flag if we have the request in the future.
if (strncmp(targ_.version, section.version, 32) == 0 ||
targ_.min_rollback > section.rollback ||
targ_.key_version != section.key_version) {
LOG(INFO) << "No need to update.";
return true;
}
if (section.offset + section.size > image_.size()) {
LOG(ERROR) << "image length (" << image_.size()
<< ") is smaller than transfer requirements: "
<< section.offset << " + " << section.size;
return false;
}
ret = TransferSection(
image_ptr + section.offset, section.offset, section.size);
}
}
// Move USB receiver state machine to idle state so that vendor commands can
// be processed later, if any.
SendDone();
if (ret) {
LOG(INFO) << "Update complete. Rebooting the EC.";
SendSubcommand(UpdateExtraCommand::kImmediateReset);
}
return ret;
}
bool FirmwareUpdater::SendSubcommand(enum UpdateExtraCommand subcommand) {
if (!SendFirstPDU()) {
return false;
}
LOG(INFO) << "Send Sub-command: " << subcommand;
SendDone();
uint8_t response = -1;
uint16_t subcommand_value = static_cast<uint16_t>(subcommand);
size_t usb_msg_size = (sizeof(UpdateFrameHeader) + sizeof(subcommand_value));
std::unique_ptr<UpdateFrameHeader, base::FreeDeleter> ufh(
static_cast<UpdateFrameHeader*>(malloc(usb_msg_size)));
if (ufh == nullptr) {
LOG(ERROR) << "Failed to allocate " << usb_msg_size << " bytes";
return false;
}
ufh->block_size = htobe32(usb_msg_size);
ufh->block_base = htobe32(kUpdateExtraCmd);
ufh->block_digest = 0;
uint16_t* frame_ptr = reinterpret_cast<uint16_t*>(ufh.get() + 1);
*frame_ptr = htobe16(subcommand_value);
int received = uep_.Transfer(
ufh.get(), usb_msg_size, &response, sizeof(response), false);
bool ret = (received == sizeof(response));
if (ret) {
LOG(INFO) << "Sent sub-command: " << std::hex << subcommand << std::dec
<< ", response: " << base::HexEncode(&response, sizeof(response));
}
return ret;
}
bool FirmwareUpdater::SendFirstPDU() {
if (!uep_.Connect()) {
return false;
}
LOG(INFO) << "Send the first PDU: zero data header.";
UpdateFrameHeader ufh;
memset(&ufh, 0, sizeof(ufh));
ufh.block_size = htobe32(sizeof(ufh));
if (uep_.Send(&ufh, sizeof(ufh)) != sizeof(ufh)) {
LOG(ERROR) << "Send first update frame header failed.";
return false;
}
// We got something. Check for errors in response.
FirstResponsePDU rpdu;
size_t rxed_size = uep_.Receive(&rpdu, sizeof(rpdu), true);
const size_t kMinimumResponseSize = 8;
if (rxed_size < kMinimumResponseSize) {
LOG(ERROR) << "Unexpected response size: " << rxed_size
<< ". Response content: "
<< base::HexEncode(reinterpret_cast<uint8_t*>(&rpdu), rxed_size);
return false;
}
// Convert endian of the response.
uint32_t return_value = be32toh(rpdu.return_value);
targ_.header_type = be16toh(rpdu.header_type);
targ_.protocol_version = be16toh(rpdu.protocol_version);
targ_.maximum_pdu_size = be32toh(rpdu.maximum_pdu_size);
targ_.flash_protection = be32toh(rpdu.flash_protection);
targ_.offset = be32toh(rpdu.offset);
memcpy(targ_.version, rpdu.version, sizeof(rpdu.version));
targ_.min_rollback = be32toh(rpdu.min_rollback);
targ_.key_version = be32toh(rpdu.key_version);
LOG(INFO) << "target running protocol version " << targ_.protocol_version
<< " (type " << targ_.header_type << ")";
if (targ_.protocol_version != 6) {
LOG(ERROR) << "Unsupported protocol version " << targ_.protocol_version;
return false;
}
if (targ_.header_type !=
static_cast<int>(FirstResponsePDUHeaderType::kCommon)) {
LOG(ERROR) << "Unsupported header type " << targ_.header_type;
return false;
}
if (return_value) {
LOG(ERROR) << "Target reporting error " << return_value;
return false;
}
LOG(INFO) << "maximum PDU size: " << targ_.maximum_pdu_size;
LOG(INFO) << base::StringPrintf("Flash protection status: %04x",
targ_.flash_protection);
LOG(INFO) << "version: " << targ_.version;
LOG(INFO) << "key_version: " << targ_.key_version;
LOG(INFO) << "min_rollback: " << targ_.min_rollback;
LOG(INFO) << base::StringPrintf("Writeable at offset: 0x%x", targ_.offset);
LOG(INFO) << "SendFirstPDU finished successfully.";
return true;
}
void FirmwareUpdater::SendDone() {
// Send stop request, ignoring reply.
uint32_t out = htobe32(kUpdateDoneCmd);
uint8_t unused_received;
uep_.Transfer(&out, sizeof(out), &unused_received, 1, false);
}
bool FirmwareUpdater::TransferSection(const uint8_t* data_ptr,
uint32_t section_addr,
size_t data_len) {
// Actually, we can skip trailing chunks of 0xff, as the entire
// section space must be erased before the update is attempted.
// TODO: We can be smarter than this and skip blocks within the image.
while (data_len && (data_ptr[data_len - 1] == 0xff))
data_len--;
LOG(INFO) << "Sending 0x" << std::hex << data_len << " bytes to 0x"
<< section_addr << std::dec;
while (data_len > 0) {
// prepare the header to prepend to the block.
size_t payload_size = std::min<size_t>(data_len, targ_.maximum_pdu_size);
UpdateFrameHeader ufh;
ufh.block_size = htobe32(payload_size + sizeof(UpdateFrameHeader));
ufh.block_base = htobe32(section_addr);
ufh.block_digest = 0;
LOG(INFO) << "Update frame header: " << std::hex << "0x" << ufh.block_size
<< " "
<< "0x" << ufh.block_base << " "
<< "0x" << ufh.block_digest << std::dec;
if (!TransferBlock(&ufh, data_ptr, payload_size)) {
LOG(ERROR) << "Failed to transfer block, " << data_len << " to go";
return false;
}
data_len -= payload_size;
data_ptr += payload_size;
section_addr += payload_size;
}
return true;
}
bool FirmwareUpdater::TransferBlock(UpdateFrameHeader* ufh,
const uint8_t* transfer_data_ptr,
size_t payload_size) {
// First send the header.
LOG(INFO) << "Send the block header."
<< base::HexEncode(reinterpret_cast<uint8_t*>(ufh), sizeof(*ufh));
uep_.Send(ufh, sizeof(*ufh));
// Now send the block, chunk by chunk.
size_t transfer_size = 0;
while (transfer_size < payload_size) {
int chunk_size =
std::min<size_t>(uep_.GetChunkLength(), payload_size - transfer_size);
uep_.Send(transfer_data_ptr, chunk_size);
transfer_data_ptr += chunk_size;
transfer_size += chunk_size;
DLOG(INFO) << "Send block data " << transfer_size << "/" << payload_size;
}
// Now get the reply.
uint32_t reply;
if (uep_.Receive(&reply, sizeof(reply), true) == -1) {
return false;
}
reply = *(reinterpret_cast<uint8_t*>(&reply));
if (reply) {
LOG(ERROR) << "Error: status " << reply;
return false;
}
return true;
}
} // namespace hammerd