| // 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 <base/logging.h> |
| #include <brillo/syslog_logging.h> |
| #include <getopt.h> |
| |
| #include <cstdlib> |
| #include <string> |
| |
| #include "firmware.h" |
| #include "flasher.h" |
| #include "minicam_device.h" |
| #include "tools.h" |
| #include "usb_device.h" |
| |
| namespace { |
| |
| const int kWaitRebootingTimeoutSec = 10; |
| |
| } // namespace |
| |
| struct CmdlineFlags { |
| bool show_info; |
| bool forceful_upgrade; |
| bool dry_run; |
| std::string pkg_path; // Absolute path for huddly.pkg file. |
| std::string log_to; // Path of log file destination. stdout is legal. |
| bool prep_only; |
| bool burn_only; |
| bool switch_to_app_mode; |
| bool switch_to_boot_mode; |
| }; |
| |
| // Switch booting modes and reboot. |
| bool SwitchMode(huddly::BootMode from, |
| huddly::BootMode to, |
| std::string* err_msg); |
| |
| // Show firmware versions and hardware revisions of firmware package and |
| // the Huddly peripheral in use. |
| void ShowInfo(const CmdlineFlags& cmd_flags); |
| |
| // Configures the number of streams sent |
| // from Huddly camera to the desired value. |
| bool ConfigStreamMode(); |
| |
| // Show command line argument options. |
| void ShowUsage(const char* program_name); |
| |
| // Parse command line arguments. |
| void ParseArgs(int argc, char* argv[], CmdlineFlags* flags); |
| |
| int main(int argc, char* argv[]) { |
| // Hanlde command line options. |
| CmdlineFlags cmd_flags = {}; |
| ParseArgs(argc, argv, &cmd_flags); |
| |
| // In the simplified workflow, do both do_prep and do_burn steps. |
| // In CFM workflow, two steps are separated by two different udev rules |
| // to accommodate the minijail restrictions. |
| // TODO(porce): Check the exclusiveness of |burn_only| and |prep_only| and |
| // warn. |
| bool do_prep = !cmd_flags.burn_only; |
| bool do_burn = !cmd_flags.prep_only; |
| |
| if (cmd_flags.burn_only) { |
| cmd_flags.forceful_upgrade = true; // Override |
| } |
| |
| if (cmd_flags.log_to.empty()) { |
| // Default to Syslog. |
| brillo::InitLog(brillo::InitFlags::kLogToSyslog); |
| } else if (cmd_flags.log_to == "stdout") { |
| // Stdout. |
| logging::LoggingSettings logging_settings; |
| logging::InitLogging(logging_settings); |
| } else { |
| // A particular log file is specified. |
| logging::LoggingSettings logging_settings; |
| logging_settings.logging_dest = logging::LOG_TO_FILE; |
| logging_settings.log_file = cmd_flags.log_to.c_str(); |
| logging_settings.lock_log = logging::DONT_LOCK_LOG_FILE; |
| logging::InitLogging(logging_settings); |
| } |
| |
| LOG(INFO) << "Starting Huddly Package Updater .."; |
| |
| if (cmd_flags.show_info) { |
| LOG(INFO) << "Show info.."; |
| ShowInfo(cmd_flags); |
| return EXIT_SUCCESS; |
| } |
| |
| std::string err_msg; |
| // TODO(porce): Check the exclusiveness of |switch_to_app_mode| and |
| // |switch_to_boot_mode| and warn. |
| |
| // TODO(porce): Factor out into helper functions for return cases below. |
| if (cmd_flags.switch_to_app_mode) { |
| if (!SwitchMode(huddly::BootMode::BOOTLOADER, huddly::BootMode::APP, |
| &err_msg)) { |
| err_msg += ".. failed to initialize the mode"; |
| LOG(INFO) << err_msg; |
| return EXIT_FAILURE; |
| } |
| LOG(INFO) << ".. succeeded in switching mode to app"; |
| return EXIT_SUCCESS; |
| } |
| if (cmd_flags.switch_to_boot_mode) { |
| if (!SwitchMode(huddly::BootMode::APP, huddly::BootMode::BOOTLOADER, |
| &err_msg)) { |
| err_msg += ".. failed to initialize the mode"; |
| LOG(INFO) << err_msg; |
| return EXIT_FAILURE; |
| } |
| LOG(INFO) << ".. succeeded in switching mode to boot"; |
| return EXIT_SUCCESS; |
| } |
| |
| huddly::Firmware firmware(cmd_flags.pkg_path); |
| huddly::Flasher flasher(firmware, cmd_flags.forceful_upgrade, |
| cmd_flags.dry_run); |
| |
| if (!flasher.Init(&err_msg)) { |
| LOG(ERROR) << err_msg; |
| } |
| |
| if (do_prep) { |
| LOG(INFO) << flasher.GetUpgradeEligibilityMessage(); |
| if (!flasher.IsEligibleForUpgrade()) { |
| |
| // If configuring stream mode fails, or is not necessary, display message |
| // and quit. |
| if(!ConfigStreamMode()) |
| LOG(INFO) << ".. Exiting huddly-updater."; |
| |
| return EXIT_SUCCESS; |
| } |
| |
| if (!SwitchMode(huddly::BootMode::APP, huddly::BootMode::BOOTLOADER, |
| &err_msg)) { |
| err_msg += ".. failed to initialize the mode"; |
| LOG(ERROR) << err_msg; |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if (do_burn) { |
| if (!flasher.FlashAll(&err_msg)) { |
| err_msg += ".. failed to flash"; |
| LOG(ERROR) << err_msg; |
| |
| err_msg = ""; |
| if (!SwitchMode(huddly::BootMode::BOOTLOADER, huddly::BootMode::APP, |
| &err_msg)) { |
| err_msg += ".. failed to reboot back to APP mode"; |
| LOG(ERROR) << err_msg; |
| } |
| |
| return EXIT_FAILURE; |
| } |
| |
| LOG(INFO) << ".. wrote firmware"; |
| |
| if (!SwitchMode(huddly::BootMode::BOOTLOADER, huddly::BootMode::APP, |
| &err_msg)) { |
| err_msg += ".. failed to reboot back to APP mode"; |
| LOG(ERROR) << err_msg; |
| return EXIT_FAILURE; |
| } |
| |
| LOG(INFO) << ".. upgrade complete."; |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| bool SwitchMode(huddly::BootMode from, |
| huddly::BootMode to, |
| std::string* err_msg) { |
| huddly::MinicamDevice minidev_from(huddly::kVendorId, |
| BootModeToProductId(from)); |
| |
| if (!minidev_from.Setup(err_msg)) { |
| *err_msg += ".. failed to USB access setup"; |
| return false; |
| } |
| |
| LOG(INFO) << ".. switching mode from " << BootModeStr(from) << " to " |
| << BootModeStr(to) << ".. rebooting (timeout " |
| << kWaitRebootingTimeoutSec << " sec)"; |
| |
| if (!minidev_from.RebootInMode(to, err_msg)) { |
| *err_msg += ".. failed to reboot in mode: "; |
| *err_msg += BootModeStr(to); |
| return false; |
| } |
| |
| LOG(INFO) << ".. waiting to come up"; |
| |
| huddly::MinicamDevice minidev_to(huddly::kVendorId, BootModeToProductId(to)); |
| if (!minidev_to.WaitForOnline(kWaitRebootingTimeoutSec, err_msg)) { |
| *err_msg += ".. waited but "; |
| *err_msg += BootModeStr(to) + " mode did not come up"; |
| return false; |
| } |
| |
| LOG(INFO) << ".. entered " << BootModeStr(to) |
| << " mode. VID:PID=" << minidev_to.GetId(); |
| return true; |
| } |
| |
| void ShowInfo(const CmdlineFlags& cmd_flags) { |
| huddly::Firmware firmware(cmd_flags.pkg_path); |
| firmware.ShowInfo(); |
| |
| huddly::MinicamDevice minidev(huddly::kVendorId, huddly::kProductIdApp); |
| std::string err_msg; |
| if (!minidev.Setup(&err_msg)) { |
| LOG(ERROR) << err_msg; |
| return; |
| } |
| minidev.ShowInfo(); |
| } |
| |
| bool ConfigStreamMode() { |
| const huddly::StreamMode kMode = huddly::StreamMode::TRIPLE; |
| huddly::MinicamDevice minidev(huddly::kVendorId, huddly::kProductIdApp); |
| std::string err_msg; |
| if (!minidev.Setup(&err_msg)) { |
| LOG(ERROR) << err_msg; |
| return false; |
| } |
| |
| huddly::StreamMode current_mode; |
| if (!minidev.GetStreamMode(¤t_mode)) { |
| minidev.Teardown(&err_msg); |
| LOG(ERROR) << "Failed to get stream mode."; |
| return false; |
| } |
| |
| if (current_mode == kMode) { |
| minidev.Teardown(&err_msg); |
| LOG(INFO) << ".. Already in " << huddly::StreamModeToStr(kMode) << " mode."; |
| return false; |
| } |
| |
| if (!minidev.SetStreamMode(kMode, &err_msg)) { |
| minidev.Teardown(&err_msg); |
| LOG(ERROR) << "failed to set stream mode " << err_msg; |
| return false; |
| } |
| |
| LOG(INFO) << "Changed stream mode from " |
| << huddly::StreamModeToStr(current_mode) << " to " |
| << huddly::StreamModeToStr(kMode) << ". Rebooting."; |
| minidev.Reboot(&err_msg); |
| return true; |
| } |
| |
| void ShowUsage(const char* program_name) { |
| LOG(INFO) << "Show usage"; |
| /* clang-format off */ |
| printf("\n" |
| "Usage: %s [--option(s)]\n" |
| " --app: Switch to App mode and reboot.\n" |
| " --boot: Switch to Boot mode and reboot.\n" |
| " --burn: Burn only, without prepping step.\n" |
| " --dryrun: Dryrun. Do everything except for final committment.\n" |
| " --force: Force the upgrade ignoring the upgrade eligibility check.\n" |
| " --help: Show this message and quit.\n" |
| " --info: Show firmware and peripheral information.\n" |
| " --log_to: Path to log file. stdout is legal.\n" |
| " --pkg: Specify the absolute path for firmware package file.\n" |
| " --prep: Prep only, without entering the burning step.\n" |
| "\n", |
| program_name); |
| /* clang-format on */ |
| } |
| |
| // TODO(crbug.com/719567): Replace with base::CommandLine. |
| void ParseArgs(int argc, char* argv[], CmdlineFlags* flags) { |
| const char* kOptString = "h"; |
| const struct option long_options[] = { |
| {"app", no_argument, 0, 'a'}, |
| {"boot", no_argument, 0, 'b'}, |
| {"burn", no_argument, 0, 'u'}, |
| {"dryrun", no_argument, 0, 'd'}, |
| {"force", no_argument, 0, 'f'}, |
| {"help", no_argument, 0, 'h'}, |
| {"info", no_argument, 0, 'i'}, |
| {"log_to", required_argument, 0, 'l'}, |
| {"pkg", required_argument, 0, 'p'}, |
| {"prep", no_argument, 0, 'r'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| flags->pkg_path = ""; |
| flags->log_to = ""; |
| flags->prep_only = false; |
| flags->burn_only = false; |
| flags->switch_to_app_mode = false; |
| flags->switch_to_boot_mode = false; |
| |
| int opt; |
| int longoption_index = 0; |
| while ((opt = getopt_long(argc, argv, kOptString, long_options, |
| &longoption_index)) != -1) { |
| switch (opt) { |
| case 'h': |
| ShowUsage(argv[0]); |
| std::exit(EXIT_SUCCESS); |
| case 'i': |
| flags->show_info = true; |
| break; |
| case 'f': |
| flags->forceful_upgrade = true; |
| break; |
| case 'd': |
| flags->dry_run = true; |
| break; |
| case 'p': |
| flags->pkg_path = std::string(optarg); |
| break; |
| case 'l': |
| flags->log_to = std::string(optarg); |
| break; |
| case 'r': |
| flags->prep_only = true; |
| break; |
| case 'u': |
| flags->burn_only = true; |
| break; |
| case 'a': |
| flags->switch_to_app_mode = true; |
| break; |
| case 'b': |
| flags->switch_to_boot_mode = true; |
| break; |
| default: |
| ShowUsage(argv[0]); |
| std::exit(EXIT_FAILURE); |
| break; |
| } |
| } |
| } |