blob: 6e9316a90b40ffdcbdaa6db06c5eb8c1cdd11473 [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 "upgrade.h"
#include <algorithm>
#include <iterator>
#include <numeric>
#include <string>
#include <tuple>
#include <vector>
#include <base/files/file_util.h>
#include <base/strings/stringprintf.h>
#include <base/sys_byteorder.h>
#include "atrus_device.h"
#include "usb_dfu_device.h"
#include "util.h"
namespace atrusctl {
namespace {
const int kSuffixSizeBytes = 16;
const char kExpectedDfuSignature[] = "DFU";
const int kReEnumerateDelayMs = 1000;
// Represents an USB binary coded decimal
struct UsbBcd {
explicit UsbBcd(uint16_t bcd) {
major = (bcd >> 8) & 0xFF;
minor = (bcd >> 4) & 0xF;
sub_minor = bcd & 0xF;
}
bool operator>=(const UsbBcd& other) const {
return (std::tie(major, minor, sub_minor) >=
std::tie(other.major, other.minor, other.sub_minor));
}
std::string ToString() const {
return base::StringPrintf("%d.%d.%d", major, minor, sub_minor);
}
uint8_t major;
uint8_t minor;
uint8_t sub_minor;
};
// Represents an USB DFU file suffix, don't reorder. According to Appendix B in
// the specification, the suffix is mirrored in relation to the structure.
struct UsbDfuFileSuffix {
uint16_t bcd_device;
uint16_t id_product;
uint16_t id_vendor;
uint16_t bcd_dfu;
uint8_t dfu_signature[3];
uint8_t length;
uint32_t crc;
};
bool DeviceVersionIsHigherOrEqualFileVersion(const UsbDfuDevice& dev,
const UsbDfuFileSuffix& suffix) {
uint16_t bcd_device;
if (!dev.GetBcdDevice(&bcd_device)) {
LOG(ERROR) << "Could not get bcdDevice field from device";
return false;
}
return (UsbBcd(bcd_device) >= UsbBcd(suffix.bcd_device));
}
bool ParseSuffixFromData(const std::vector<char>& data,
UsbDfuFileSuffix* suffix) {
static_assert(sizeof(*suffix) == kSuffixSizeBytes, "Suffix is wrong size");
if (data.capacity() <= kSuffixSizeBytes) {
return false;
}
std::copy(data.end() - kSuffixSizeBytes, data.end(),
reinterpret_cast<char*>(suffix));
// Only the DFU signature needs to be reversed
std::reverse(std::begin(suffix->dfu_signature),
std::end(suffix->dfu_signature));
// Validate the suffix, return false if it's invalid
if (std::string(reinterpret_cast<char*>(suffix->dfu_signature),
sizeof(suffix->dfu_signature)) != kExpectedDfuSignature) {
return false;
}
uint32_t crc = std::accumulate(
data.begin(), (data.end() - sizeof(suffix->crc)), 0xFFFFFFFF, Crc32);
return (crc == suffix->crc);
}
} // namespace
bool PerformUpgrade(const base::FilePath& upgrade_file,
bool* skipped,
bool force_upgrade) {
*skipped = false;
LOG(INFO) << "[Device is entering upgrade mode]";
LOG(INFO) << "Upgrade file " << upgrade_file.value();
int64_t file_size;
if (!base::GetFileSize(upgrade_file, &file_size)) {
LOG(ERROR) << "Could not get file size";
return false;
}
std::vector<char> file_contents(file_size);
int read = base::ReadFile(upgrade_file, file_contents.data(), file_size);
if (read < 0) {
LOG(ERROR) << "Error while reading file";
return false;
}
if (read != file_size) {
LOG(ERROR) << "Could not read whole file";
return false;
}
UsbDfuFileSuffix suffix;
if (!ParseSuffixFromData(file_contents, &suffix)) {
LOG(ERROR) << "Could not parse USB DFU file suffix";
return false;
}
if ((suffix.id_vendor != kAtrusUsbVid) &&
(suffix.id_product != kAtrusUsbPid)) {
LOG(ERROR) << "The upgrade file is not intended for an Atrus device";
return false;
}
std::string file_version = UsbBcd(suffix.bcd_device).ToString();
LOG(INFO) << "Upgrade file version " << file_version;
uint16_t bcd_device;
std::string dev_version = "unknown";
bool run_time_mode_failed = false;
// Set the device in DFU mode
UsbDfuDevice dev_run_time(kAtrusUsbVid, kAtrusUsbPid);
if (!dev_run_time.Open()) {
LOG(INFO) << "Could not open device in run-time mode";
run_time_mode_failed = true;
} else {
if (dev_run_time.GetBcdDevice(&bcd_device)) {
dev_version = UsbBcd(bcd_device).ToString();
}
LOG(INFO) << "Current device version " << dev_version;
if (DeviceVersionIsHigherOrEqualFileVersion(dev_run_time, suffix) &&
!force_upgrade) {
*skipped = true;
return true;
}
LOG(INFO) << "Setting device in DFU mode";
dev_run_time.SetInDfuMode();
// Wait for device to re-enumerate in DFU mode
TimeoutMs(kReEnumerateDelayMs);
}
// Device should be in DFU mode, start upgrade
UsbDfuDevice dev_dfu(kAtrusUsbDfuVid, kAtrusUsbDfuPid);
if (!dev_dfu.Open()) {
LOG(ERROR) << "Could not open device in DFU mode";
return false;
}
if (run_time_mode_failed) {
if (dev_dfu.GetBcdDevice(&bcd_device)) {
dev_version = UsbBcd(bcd_device).ToString();
}
LOG(INFO) << "Current device version " << dev_version;
}
if (DeviceVersionIsHigherOrEqualFileVersion(dev_dfu, suffix) &&
!force_upgrade) {
*skipped = true;
return true;
}
LOG(INFO) << "Upgrading from device version " << dev_version << " to "
<< file_version;
if (!dev_dfu.DownloadFile(file_contents)) {
return false;
}
LOG(INFO) << "Resetting device";
return dev_dfu.Reset();
}
} // namespace atrusctl