blob: 38e51effc51e7b3d9510e898270724e31ef3022a [file] [log] [blame]
// 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.
#include "hpk_updater.h"
#include <unistd.h>
#include <algorithm>
#include <iterator>
#include <limits>
#include <memory>
#include <sstream>
#include <utility>
#include <base/time/time.h>
#include <cfm-dfu-notification/idfu_notification.h>
#include "../common/messagepack/messagepack.h"
#include "hcp.h"
#include "message_bus.h"
#include "usb.h"
#include "utils.h"
namespace huddly {
constexpr auto kDeviceWaitTimeoutMillisec = 30000;
const uint32_t kUpdateTimeoutSeconds = 480;
constexpr uint8_t kVideoInterfaceClass = 0x0e;
constexpr uint8_t kAudioInterfaceClass = 0x01;
constexpr uint8_t kControlInterfaceSubClass = 0x01;
constexpr char kCameraName[] = "Huddly IQ";
std::unique_ptr<HpkUpdater> HpkUpdater::Create(uint16_t usb_vendor_id,
uint16_t usb_product_id,
const std::string& usb_path) {
auto instance = std::unique_ptr<HpkUpdater>(
new HpkUpdater(usb_vendor_id, usb_product_id, usb_path));
instance->usb_ = huddly::Usb::Create();
if (!instance->usb_) {
LOG(ERROR) << "Usb init failed";
return nullptr;
}
if (!instance->Connect()) {
LOG(ERROR) << "Connect failed";
return nullptr;
}
return instance;
}
HpkUpdater::~HpkUpdater() {
if (!hlink_) {
return;
}
auto usb_device = hlink_->GetUsbDevice();
if (!usb_device) {
return;
}
ReattachMediaKernelDrivers(usb_device);
}
enum class UpdateAction {
kNoUpdateNeeded,
kRunUpdate,
kRunVerification,
kUnknown
};
static UpdateAction DetermineUpdateAction(const CameraInformation& camera_info,
const HpkFile& hpk_file,
bool force_update) {
std::string error_str;
std::string hpk_firmware_version_string;
if (!hpk_file.GetFirmwareVersionString(&hpk_firmware_version_string,
&error_str)) {
LOG(WARNING) << "Failed to read firmware version from hpk file. "
<< error_str;
return UpdateAction::kUnknown;
}
LOG(INFO) << "Camera firmware version: " << camera_info.firmware_version;
LOG(INFO) << "Hpk firmware version: " << hpk_firmware_version_string;
if (camera_info.ram_boot_selector != camera_info.boot_decision) {
LOG(INFO) << "Verification needed. Verifying...";
return UpdateAction::kRunVerification;
}
if (force_update) {
LOG(INFO) << "Force update requested. Updating...";
return UpdateAction::kRunUpdate;
}
if (hpk_firmware_version_string != camera_info.firmware_version) {
LOG(INFO) << "Camera versions differ. Updating...";
return UpdateAction::kRunUpdate;
}
LOG(INFO) << "No upgrade or verification needed.";
return UpdateAction::kNoUpdateNeeded;
}
bool HpkUpdater::DoUpdate(base::FilePath hpk_file_path,
bool force,
bool udev_mode,
const std::string& configuration) {
std::string error_str;
const auto hpk_file = huddly::HpkFile::Create(hpk_file_path, &error_str);
if (!hpk_file) {
LOG(ERROR) << "Failed to read hpk file '" << hpk_file_path.MaybeAsASCII()
<< "': " << error_str;
return false;
}
if (!HpkUpdateSupported()) {
LOG(ERROR) << "The current camera firmware version, '"
<< camera_info_.firmware_version
<< "' does not support hpk update.";
return false;
}
UpdateAction action = UpdateAction::kUnknown;
const auto kNumUpdatePasses = udev_mode ? 1 : 2;
for (int update_pass = 1; update_pass <= kNumUpdatePasses; update_pass++) {
action = DetermineUpdateAction(camera_info_, *hpk_file, force);
if (action == UpdateAction::kUnknown) {
LOG(ERROR) << "Unable to determine update state";
return false;
}
if (action == UpdateAction::kNoUpdateNeeded) {
if (!ConfigureCamera(configuration)) {
LOG(ERROR) << "Failed to configure camera";
return false;
}
return true;
}
// This means that either an update or verification is needed.
// The update notification will restart after each pass.
std::unique_ptr<IDfuNotification> dfu_notification =
IDfuNotification::For(kCameraName);
dfu_notification->NotifyStartUpdate(kUpdateTimeoutSeconds);
bool reboot_requested;
if (!DoSingleUpdatePass(*hpk_file, &reboot_requested)) {
dfu_notification->NotifyEndUpdate(false);
return false;
}
if (action == UpdateAction::kRunUpdate && !reboot_requested) {
dfu_notification->NotifyEndUpdate(false);
LOG(ERROR)
<< "Expected reboot request after update pass, but none received";
return false;
}
if (action == UpdateAction::kRunVerification && reboot_requested) {
LOG(ERROR) << "Unexpected reboot requested after verification pass";
dfu_notification->NotifyEndUpdate(false);
return false;
}
if (reboot_requested) {
LOG(INFO) << "Reboot requested. Rebooting...";
if (udev_mode) {
// Do not wait for detach in udev mode, since this is konwn to fail on
// older kernel versions.
if (!Reboot(false)) {
LOG(ERROR) << "Failed to reboot";
dfu_notification->NotifyEndUpdate(false);
return false;
}
} else {
if (!RebootAndReattach()) {
LOG(ERROR) << "Failed to reboot";
dfu_notification->NotifyEndUpdate(false);
return false;
}
}
}
dfu_notification->NotifyEndUpdate(true);
}
if (action == UpdateAction::kRunVerification) {
LOG(INFO) << "Update finished successfully";
if (!ConfigureCamera(configuration)) {
LOG(ERROR) << "Failed to configure camera";
return false;
}
}
return true;
}
bool HpkUpdater::Reboot(bool wait_for_detach) {
if (!hlink_->SendReboot(wait_for_detach)) {
LOG(ERROR) << "Failed to reboot camera";
return false;
}
kernel_drivers_detached_ = false;
return true;
}
bool HpkUpdater::RebootAndReattach() {
if (!Reboot(true)) {
return false;
}
if (!Connect()) {
LOG(ERROR) << "Connect failed";
return false;
}
return true;
}
static bool FirmwareIsHuddlyIQ(const std::string& version_str) {
const std::string kProductIdentifier("HuddlyIQ");
return (version_str.find(kProductIdentifier) != std::string::npos);
}
bool HpkUpdater::HpkUpdateSupported() {
// Only Huddly IQ, firmware versions 1.2.0 and later is supported.
return (FirmwareIsHuddlyIQ(camera_info_.firmware_version) &&
camera_info_.firmware_version_numeric >= NumericVersion({1, 2, 0}));
}
bool HpkUpdater::ErrorLogReadSupported() {
// Only Huddly IQ, firmware versions 1.3.14 and later is supported.
return (FirmwareIsHuddlyIQ(camera_info_.firmware_version) &&
camera_info_.firmware_version_numeric >= NumericVersion({1, 3, 14}));
}
bool HpkUpdater::ConfigureCamera(const std::string& configuration) {
const std::string kHangoutsMeetConfiguration("hmh");
const std::string kJamboardConfiguration("jamboard");
if (configuration == kHangoutsMeetConfiguration) {
return ConfigureCameraHmh();
} else if (configuration == kJamboardConfiguration) {
return ConfigureCameraJamboard();
}
LOG(ERROR) << "Unknown configuration: " << configuration;
return false;
}
bool HpkUpdater::ConfigureCameraHmh() {
const uint16_t kClownfishProductId = 0x0031;
if (usb_product_id_ != kClownfishProductId) {
return true;
}
const std::string kCameraModeDualStream("dual-stream");
if (camera_info_.camera_mode == kCameraModeDualStream) {
LOG(INFO) << "Camera already configured";
return true;
}
if (!SetCameraMode(kCameraModeDualStream)) {
return false;
}
LOG(INFO) << "Camera configuration changed. Rebooting";
Reboot(false);
return true;
}
bool HpkUpdater::ConfigureCameraJamboard() {
const uint16_t kBoxfishProductId = 0x0021;
if (usb_product_id_ != kBoxfishProductId) {
return true;
}
const std::string kCameraModeMjpeg("mjpeg");
if ((camera_info_.camera_mode == kCameraModeMjpeg) &&
(camera_info_.autozoom_enabled == false)) {
LOG(INFO) << "Camera already configured";
return true;
}
if (!SetCameraMode(kCameraModeMjpeg)) {
return false;
}
if (!DisableAutozoom()) {
return false;
}
LOG(INFO) << "Camera configuration changed. Rebooting";
Reboot(false);
return true;
}
HpkUpdater::HpkUpdater(uint16_t usb_vendor_id,
uint16_t usb_product_id,
const std::string& usb_path)
: usb_vendor_id_(usb_vendor_id),
usb_product_id_(usb_product_id),
usb_path_(usb_path) {}
bool HpkUpdater::Connect() {
auto usb_device = usb_->WaitForDevice(usb_vendor_id_, usb_product_id_,
usb_path_, kDeviceWaitTimeoutMillisec);
if (!usb_device) {
LOG(ERROR) << "Could not find USB device with vid:pid="
<< huddly::UsbVidPidString(usb_vendor_id_, usb_product_id_);
return false;
}
LOG(INFO) << "Found USB device with vid:pid="
<< huddly::UsbVidPidString(usb_vendor_id_, usb_product_id_);
if (!usb_device->Open()) {
LOG(ERROR) << "Failed to open device";
return false;
}
if (!DetachMediaKernelDrivers(usb_device.get())) {
LOG(ERROR) << "Unable to detach kernel driver";
return false;
}
hlink_ = HLinkVsc::Create(std::move(usb_device));
if (!hlink_) {
LOG(ERROR) << "Failed to create HLinkVsc instance";
return false;
}
if (!hlink_->Connect()) {
LOG(ERROR) << "Failed to connect to HLinkVsc device";
return false;
}
if (!GetCameraInformation(&camera_info_)) {
LOG(WARNING) << "Failed to obtain camera information";
}
return true;
}
bool HpkUpdater::GetCameraInformation(CameraInformation* info) {
if (!GetFirmwareVersion(&info->firmware_version,
&info->firmware_version_numeric)) {
LOG(ERROR) << "Failed to get camera version info";
return false;
}
// Figure out if we are going to verify.
std::vector<uint8_t> product_info_msgpack;
if (!GetProductInfo(&product_info_msgpack)) {
return false;
}
auto unpacker = huddly::messagepack::Unpacker::Create(product_info_msgpack);
if (!unpacker) {
LOG(ERROR) << "Unable to decode product info reply";
return false;
}
huddly::messagepack::Map product_info;
if (!unpacker->GetRoot<huddly::messagepack::Map>(&product_info)) {
LOG(ERROR) << "Failed to get product info as map";
return false;
}
huddly::messagepack::Map bac_fsbl;
if (!product_info.GetValueAs<huddly::messagepack::Map>("bac_fsbl",
&bac_fsbl)) {
LOG(ERROR) << "Could not find bac_fsbl in product info";
return false;
}
if (!bac_fsbl.GetValueAs<std::string>("ram_boot_selector",
&info->ram_boot_selector)) {
LOG(ERROR) << "Could not find ram_boot_selector";
return false;
}
if (!bac_fsbl.GetValueAs<std::string>("boot_decision",
&info->boot_decision)) {
LOG(ERROR) << "Could not find boot_decision";
return false;
}
product_info.GetValueAs<std::string>(kCameraModeKey, &info->camera_mode);
product_info.GetValueAs<bool>(kAutozoomEnableKey, &info->autozoom_enabled);
if (!product_info.GetValueAs<std::string>(kResetCauseKey,
&info->reset_cause)) {
LOG(WARNING) << "Failed to get reset cause";
info->reset_cause = "Unknown";
}
return true;
}
bool HpkUpdater::GetProductInfo(std::vector<uint8_t>* product_info_msgpack) {
const std::string kMessageName("prodinfo/get_msgpack");
const std::string kSubscription(kMessageName + "_reply");
const auto scoped_subscription =
message_bus::ScopedSubscribe::Create(hlink_.get(), kSubscription);
if (!scoped_subscription) {
LOG(ERROR) << "Failed to subscribe to '" << kSubscription << "'";
return false;
}
if (!hlink_->Send(kMessageName, nullptr, 0)) {
LOG(ERROR) << "Failed to send '" << kMessageName << "' message";
return false;
}
HLinkBuffer buffer;
if (!hlink_->Receive(&buffer)) {
return false;
}
if (buffer.GetMessageName() != kSubscription) {
LOG(ERROR) << "Received unexpected message: '" << buffer.GetMessageName()
<< "'";
return false;
}
*product_info_msgpack = buffer.GetPayload();
return true;
}
bool HpkUpdater::SetProductInfo(
const std::vector<uint8_t>& product_info_msgpack) {
const std::string kMessageName("prodinfo/set_msgpack");
const std::string kSubscription(kMessageName + "_reply");
const auto scoped_subscription =
message_bus::ScopedSubscribe::Create(hlink_.get(), kSubscription);
if (!scoped_subscription) {
LOG(ERROR) << "Failed to subscribe to '" << kSubscription << "'";
return false;
}
if (!hlink_->Send(kMessageName, product_info_msgpack.data(),
product_info_msgpack.size())) {
LOG(ERROR) << "Failed to send '" << kMessageName << "' message";
return false;
}
HLinkBuffer buffer;
if (!hlink_->Receive(&buffer)) {
return false;
}
if (buffer.GetMessageName() != kSubscription) {
LOG(ERROR) << "Received unexpected message: '" << buffer.GetMessageName()
<< "'";
return false;
}
const auto payload = buffer.GetPayload();
if (payload.size() != 1) {
LOG(ERROR) << "Unexpected reply length";
return false;
}
if (payload[0] != 0) {
LOG(ERROR) << "Set product info failed";
return false;
}
return true;
}
bool HpkUpdater::SetProductInfoString(const std::string& key,
const std::string& value) {
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
msgpack_packer pk;
msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
msgpack_pack_map(&pk, 1);
msgpack_pack_str(&pk, key.size());
msgpack_pack_str_body(&pk, key.c_str(), key.size());
msgpack_pack_str(&pk, value.size());
msgpack_pack_str_body(&pk, value.c_str(), value.size());
std::vector<uint8_t> product_info(sbuf.data, sbuf.data + sbuf.size);
if (!SetProductInfo(product_info)) {
return false;
}
return true;
}
bool HpkUpdater::SetProductInfoBool(const std::string& key, bool value) {
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
msgpack_packer pk;
msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
msgpack_pack_map(&pk, 1);
msgpack_pack_str(&pk, key.size());
msgpack_pack_str_body(&pk, key.c_str(), key.size());
if (value) {
msgpack_pack_true(&pk);
} else {
msgpack_pack_false(&pk);
}
std::vector<uint8_t> product_info(sbuf.data, sbuf.data + sbuf.size);
if (!SetProductInfo(product_info)) {
return false;
}
return true;
}
bool HpkUpdater::SetCameraMode(const std::string& mode) {
VLOG(1) << "Setting camera mode to '" << mode << "'";
if (!SetProductInfoString(kCameraModeKey, mode)) {
LOG(ERROR) << "Failed to set camera mode '" << mode << "'";
return false;
}
return true;
}
bool HpkUpdater::DisableAutozoom() {
VLOG(1) << "Disabling auto-zoom";
if (!SetProductInfoBool(kAutozoomEnableKey, false)) {
LOG(ERROR) << "Failed to disable auto-zoom";
return false;
}
return true;
}
bool HpkUpdater::GetFirmwareVersion(std::string* version,
NumericVersion* version_numeric) {
*version_numeric = NumericVersion{};
std::vector<uint8_t> product_info_msgpack;
if (!GetProductInfo(&product_info_msgpack)) {
return false;
}
auto unpacker = messagepack::Unpacker::Create(product_info_msgpack);
if (!unpacker) {
LOG(ERROR) << "Unable to decode product info reply";
return false;
}
messagepack::Map product_info;
if (!unpacker->GetRoot<messagepack::Map>(&product_info)) {
LOG(ERROR) << "Failed to get product info as map";
return false;
}
if (!product_info.GetValueAs<std::string>("app_version", version)) {
LOG(ERROR) << "Could not find firmwave version";
return false;
}
messagepack::Map build_data;
if (!product_info.GetValue<messagepack::Map>("build_data", &build_data)) {
LOG(ERROR) << "Failed to read build data";
return false;
}
messagepack::Array version_array;
if (!build_data.GetValue<messagepack::Array>("version", &version_array)) {
LOG(ERROR) << "Failed to get firmware version as array";
return false;
}
const auto kVersionLength = 3;
if (version_array.Size() < kVersionLength) {
LOG(ERROR) << "Version has wrong format";
return false;
}
std::vector<int64_t> values;
if (!version_array.GetValuesAs<int64_t>(&values)) {
LOG(ERROR) << "Could not get version as array of signed integers";
return false;
}
std::vector<uint32_t> temp_version_numeric;
for (const auto v : values) {
if (v < 0 || v > std::numeric_limits<uint32_t>::max()) {
LOG(ERROR) << "Numeric version element with value" << v
<< " out of range";
return false;
}
temp_version_numeric.push_back(v);
}
*version_numeric = NumericVersion(temp_version_numeric);
return true;
}
std::string HpkUpdater::GetResetCause() {
return camera_info_.reset_cause;
}
bool HpkUpdater::ReadErrorLog(std::string* log) {
log->clear();
if (!ErrorLogReadSupported()) {
LOG(ERROR) << "Error log operations not supported on this version";
return false;
}
const std::string kCommand("error_logger/read_simple");
const std::string kSubscription(kCommand + "_reply");
const auto scoped_subscription =
message_bus::ScopedSubscribe::Create(hlink_.get(), kSubscription);
if (!scoped_subscription) {
LOG(ERROR) << "Failed to subscribe to " << kSubscription;
return false;
}
if (!hlink_->Send(kCommand, nullptr, 0)) {
LOG(ERROR) << "Failed to send '" << kCommand << "' command";
return false;
}
huddly::HLinkBuffer hl_buffer;
if (!hlink_->Receive(&hl_buffer)) {
LOG(ERROR) << "Failed to receive packet";
return false;
}
auto unpacker = messagepack::Unpacker::Create(hl_buffer.GetPayload());
if (!unpacker) {
LOG(ERROR) << "Unable to decode status message";
return false;
}
messagepack::Map error_log;
if (!unpacker->GetRoot<messagepack::Map>(&error_log)) {
LOG(ERROR) << "Failed to get upgrader status as map.";
return false;
}
int64_t status;
if (!error_log.GetValueAs<int64_t>("error", &status)) {
LOG(ERROR) << "Read error log failed. No 'error' field in map";
return false;
}
if (status != 0) {
LOG(ERROR) << "Read error log failed on device. Status code: " << status;
return false;
}
messagepack::Bin bin;
if (!error_log.GetValue<messagepack::Bin>("log", &bin)) {
LOG(ERROR) << "Read error log failed. No 'log' field in map";
return false;
}
std::vector<uint8_t> as_vector;
if (!bin.GetValues(&as_vector)) {
LOG(ERROR) << "Failed to get error log data as byte array";
return false;
}
std::string raw_log;
// Copy to string, but remove '\0' characters.
std::copy_if(std::cbegin(as_vector), std::cend(as_vector),
std::back_inserter(raw_log),
[](const uint8_t c) { return c != '\0'; });
// Strip ANSI SGR codes on a line-by line basis to avoid losing parts of
// the log if there are incomplete ANSI codes in the middle of the log.
std::istringstream iss(raw_log);
std::string line;
while (getline(iss, line)) {
*log += StripAnsiSgrCodes(line) += '\n';
}
log->pop_back(); // Remove extra '\n'.
return true;
}
bool HpkUpdater::EraseErrorLog() {
if (!ErrorLogReadSupported()) {
LOG(ERROR) << "Error log operations not supported on this version";
return false;
}
const std::string kCommand("error_logger/erase");
const std::string kStartedSubscription(kCommand + "_started");
const auto started_scoped_subscription =
message_bus::ScopedSubscribe::Create(hlink_.get(), kStartedSubscription);
if (!started_scoped_subscription) {
LOG(ERROR) << "Failed to subscribe to " << kStartedSubscription;
return false;
}
const std::string kDoneSubscription(kCommand + "_done");
const auto done_scoped_subscription =
message_bus::ScopedSubscribe::Create(hlink_.get(), kDoneSubscription);
if (!done_scoped_subscription) {
LOG(ERROR) << "Failed to subscribe to " << kDoneSubscription;
return false;
}
if (!hlink_->Send(kCommand, nullptr, 0)) {
LOG(ERROR) << "Failed to send '" << kCommand << "' command";
return false;
}
const auto timeout_ms = 30'000;
const auto start = base::TimeTicks::Now();
bool erase_done = false;
while (!erase_done) {
huddly::HLinkBuffer hl_buffer;
if (!hlink_->Receive(&hl_buffer)) {
LOG(ERROR) << "Failed to receive error log status packet";
return false;
}
const auto message_name = hl_buffer.GetMessageName();
if (message_name == kStartedSubscription) {
VLOG(1) << "Error log erase started";
}
if (message_name == kDoneSubscription) {
VLOG(1) << "Error log erase done";
// This message simply contains a 32 bit int as status value. No need to
// unpack as messagepack.
const auto status =
*reinterpret_cast<const int32_t*>(hl_buffer.GetPayload().data());
if (status != 0) {
LOG(WARNING) << "Error log erase failed";
return false;
}
erase_done = true;
}
if ((base::TimeTicks::Now() - start).InMilliseconds() > timeout_ms) {
LOG(ERROR) << "Error log erase timed out after " << timeout_ms << "ms.";
return false;
}
}
return true;
}
class UpgradeStatusHandler {
public:
// Returns false until finished, then true.
// Additional status information is communicated through the function
// object.
bool operator()(std::vector<uint8_t> payload) {
auto unpacker = messagepack::Unpacker::Create(payload);
if (!unpacker) {
LOG(ERROR) << "Unable to decode status message";
return false;
}
messagepack::Map upgrader_status;
if (!unpacker->GetRoot<messagepack::Map>(&upgrader_status)) {
LOG(ERROR) << "Failed to get upgrader status as map.";
return false;
}
last_status_ = upgrader_status.ToString();
VLOG(2) << "Status: " << last_status_;
std::string operation;
if (!upgrader_status.GetValue<std::string>("operation", &operation)) {
LOG(WARNING) << "No operation field in status message";
operation = "Unknown";
}
int64_t error = 0;
upgrader_status.GetValueAs<int64_t>("error", &error);
if (error) {
return true;
}
upgrader_status.GetValueAs<double>("total_points", &total_points_);
upgrader_status.GetValueAs<double>("elapsed_points", &elapsed_points_);
std::unique_ptr<IDfuNotification> dfu_notification =
IDfuNotification::For(kCameraName);
double elapsed_fraction = elapsed_points_ / total_points_;
double elapsed_percentage = 100.0 * elapsed_fraction;
if (elapsed_percentage - previous_reported_elapsed_percentage_ >= 5.0) {
dfu_notification->NotifyUpdateProgress(elapsed_fraction);
LOG(INFO) << "Progress: " << std::round(elapsed_percentage) << "%";
previous_reported_elapsed_percentage_ = elapsed_percentage;
}
if (operation == "done") {
LOG(INFO) << "Done!";
if (upgrader_status.GetValueAs<int64_t>("error_count", &error_count_)) {
if (error_count_ == 0) {
success_ = true;
}
}
upgrader_status.GetValue<bool>("reboot", &reboot_needed_);
return true;
}
return false;
}
bool success() const { return success_; }
bool reboot_needed() const { return reboot_needed_; }
const std::string& last_status() { return last_status_; }
private:
bool success_ = false;
std::string last_status_;
bool reboot_needed_ = false;
int64_t error_count_ = -1;
double total_points_ = 0.0;
double elapsed_points_ = 0.0;
double previous_reported_elapsed_percentage_ = 0.0;
};
bool HpkUpdater::DoSingleUpdatePass(const HpkFile& hpk_file,
bool* reboot_needed) {
VLOG(1) << "Send .hpk file";
if (!hcp::Write(hlink_.get(), "upgrade.hpk", hpk_file.GetContents())) {
LOG(ERROR) << "Failed to send .hpk file";
return false;
}
VLOG(1) << "File uploaded successfully";
UpgradeStatusHandler status_handler;
if (!HpkRun("upgrade.hpk", std::ref(status_handler))) {
LOG(ERROR) << "hpk/run failed";
return false;
}
if (!status_handler.success()) {
LOG(ERROR) << "Upgrade failed. Last status message: "
<< status_handler.last_status();
return false;
}
*reboot_needed = status_handler.reboot_needed();
return true;
}
bool HpkUpdater::HpkRun(
const std::string& filename,
std::function<bool(std::vector<uint8_t>)> status_handler) {
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
msgpack_packer pk;
msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
msgpack_pack_map(&pk, 1);
msgpack_pack_str(&pk, 8);
msgpack_pack_str_body(&pk, "filename", 8);
msgpack_pack_str(&pk, filename.size());
msgpack_pack_str_body(&pk, filename.c_str(), filename.size());
const std::string kSubscription("upgrader/status");
const auto scoped_subscription =
message_bus::ScopedSubscribe::Create(hlink_.get(), kSubscription);
if (!scoped_subscription) {
LOG(ERROR) << "Failed to subscribe to '" << kSubscription << "'";
return false;
}
if (!hlink_->Send("hpk/run", reinterpret_cast<uint8_t*>(sbuf.data),
sbuf.size)) {
LOG(ERROR) << "hpk/run failed";
return false;
}
while (1) {
huddly::HLinkBuffer hl_buffer;
if (!hlink_->Receive(&hl_buffer)) {
LOG(ERROR) << "Failed to receive packet";
return false;
}
std::vector<uint8_t> packet = hl_buffer.CreatePacket();
const bool finished = status_handler(hl_buffer.GetPayload());
if (finished) {
break;
}
}
return true;
}
bool HpkUpdater::IterateMediaKernelDrivers(
UsbDevice* usb_device,
std::function<void(const libusb_interface interface)> all_drivers) {
if (!usb_device) {
LOG(ERROR) << "Failed to obtain usb device";
return false;
}
auto config = usb_device->GetActiveConfigDescriptor();
if (!config) {
LOG(ERROR) << "Failed to get active configuration descriptor";
return false;
}
const int num_interfaces = config->bNumInterfaces;
const auto interface_begin = config->interface;
const auto interface_end = config->interface + num_interfaces;
std::for_each(interface_begin, interface_end, all_drivers);
return true;
}
bool HpkUpdater::DetachMediaKernelDrivers(UsbDevice* usb_device) {
return IterateMediaKernelDrivers(
usb_device, [this, usb_device](const libusb_interface interface) {
const auto interface_descr = &interface.altsetting[0];
const int interface_num = interface_descr->bInterfaceNumber;
bool driver_was_active = false;
if (interface_descr->bInterfaceClass != kVideoInterfaceClass &&
interface_descr->bInterfaceClass != kAudioInterfaceClass) {
return;
}
if (!usb_device->CheckKernelDriverActive(interface_num,
&driver_was_active)) {
LOG(WARNING)
<< "Unable to determine state of kernel driver at interface:"
<< interface_num;
return;
}
if (driver_was_active &&
!usb_device->DetachKernelDriver(interface_num)) {
LOG(ERROR) << "Unable to detach active kernel driver for interface:"
<< interface_num;
return;
}
// We must detach and claim ownership of the control interfaces to
// prevent another service from reclaiming it.
if (interface_descr->bInterfaceSubClass != kControlInterfaceSubClass) {
return;
}
// Claim ownership of the controllers
if (!usb_device->ClaimInterface(interface_num)) {
LOG(ERROR) << "Failed to claim interface " << interface_num;
return;
}
kernel_drivers_detached_ = true;
});
}
bool HpkUpdater::ReattachMediaKernelDrivers(UsbDevice* usb_device) {
if (!kernel_drivers_detached_) {
return true;
}
return IterateMediaKernelDrivers(
usb_device, [usb_device](const libusb_interface interface) {
const auto interface_descr = &interface.altsetting[0];
const int interface_num = interface_descr->bInterfaceNumber;
if ((interface_descr->bInterfaceClass != kVideoInterfaceClass &&
interface_descr->bInterfaceClass != kAudioInterfaceClass)
// We only need to reattach the controller classes as the rest will
// follow
||
interface_descr->bInterfaceSubClass != kControlInterfaceSubClass) {
return;
}
if (!usb_device->ReleaseInterface(interface_num)) {
LOG(WARNING) << "Failed to release interface " << interface_num;
return;
}
if (!usb_device->ReattachKernelDriver(interface_num)) {
LOG(ERROR) << "Unable to reattach active kernel driver for interface:"
<< interface_num;
return;
}
});
}
} // namespace huddly