| // Copyright 2021 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. |
| |
| /* |
| * Main command program. |
| */ |
| |
| #include <iostream> |
| #include <memory> |
| #include <utility> |
| |
| #include <getopt.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| |
| #include <base/check.h> |
| #include <base/command_line.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/time/time.h> |
| #include <brillo/flag_helper.h> |
| |
| #include "hps/hal/fake_dev.h" |
| #include "hps/hal/i2c.h" |
| #include "hps/hal/mcp.h" |
| #include "hps/hal/retry.h" |
| #include "hps/hps.h" |
| #include "hps/hps_impl.h" |
| #include "hps/util/command.h" |
| |
| // Static allocation of global command list head. |
| Command* Command::list_; |
| |
| namespace { |
| |
| class DownloadProgressIndicator { |
| public: |
| void Update(const base::FilePath& file_path, |
| uint64_t total_bytes, |
| uint64_t downloaded_bytes, |
| base::TimeDelta elapsed_time) { |
| bool done = downloaded_bytes == total_bytes; |
| |
| // Don't flood the console too frequently. |
| if (!done && (elapsed_time - last_update_).InMilliseconds() < 100) |
| return; |
| last_update_ = elapsed_time; |
| |
| constexpr const char kMessage[] = "Downloading "; |
| constexpr int kStatsWidth = 40; |
| std::string file = file_path.MaybeAsASCII(); |
| int term_width = GetTerminalWidth(); |
| int file_width = strlen(kMessage) + file.size(); |
| |
| // Bail out if the terminal is too narrow. |
| if (term_width <= file_width + kStatsWidth) |
| return; |
| |
| // Print the file name. |
| std::cout << kMessage << file; |
| |
| // Print the progress bar (if there's space). |
| int bar_width = term_width - file_width - kStatsWidth - 8; |
| if (total_bytes > 0 && bar_width > 0 && !done) { |
| std::cout << " ["; |
| for (int i = 0; i < bar_width; i++) { |
| std::cout << (i > (bar_width * downloaded_bytes) / total_bytes ? '-' |
| : '#'); |
| } |
| std::cout << "]" << std::setw(3) << std::right |
| << (100 * downloaded_bytes) / total_bytes << "%"; |
| } |
| |
| // Print statistics. |
| int seconds = elapsed_time.InSeconds(); |
| std::cout << " (" << downloaded_bytes << " in " << (seconds / 60) << ":" |
| << std::setw(2) << std::setfill('0') << (seconds % 60) |
| << std::setfill(' '); |
| if (elapsed_time.InSeconds()) { |
| std::cout << ", " << std::setprecision(1) << std::fixed |
| << downloaded_bytes / (1024 * elapsed_time.InSecondsF()) |
| << " KiB/s"; |
| } |
| std::cout << ")"; |
| |
| // Erase the rest of the line and move the cursor back to the beginning of |
| // the line for the next update (unless we're done). |
| std::cout << "\033[K"; |
| if (!done) { |
| std::cout << std::flush << "\r"; |
| } else { |
| std::cout << std::endl; |
| } |
| } |
| |
| private: |
| static int GetTerminalWidth() { |
| winsize window_size; |
| if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window_size) != 0) |
| return -1; |
| return window_size.ws_col; |
| } |
| |
| base::TimeDelta last_update_{}; |
| }; |
| |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| DEFINE_string(bus, "/dev/i2c-hps-controller", "I2C device"); |
| DEFINE_uint32(addr, 0x30, "I2C address of module"); |
| DEFINE_uint32(speed, 200, "I2C bus speed in KHz"); |
| DEFINE_uint32(retries, 0, "Max I2C retries"); |
| DEFINE_uint32(retry_delay, 10, "Delay in ms between retries"); |
| DEFINE_bool(mcp, false, "Use MCP2221A connection"); |
| DEFINE_string(test, "none", |
| "Use internal test fake, optionally setting the emulated state " |
| "to one of: boot, ready"); |
| brillo::FlagHelper::Init(argc, argv, |
| "usage: hps [ --mcp | --test[=<state>] | --bus " |
| "<i2c-bus> ] [ --addr <i2c-addr> ]\n" |
| " <command> <command arguments>\n\n" + |
| Command::GetHelp()); |
| |
| const logging::LoggingSettings ls; |
| logging::InitLogging(ls); |
| |
| auto args = base::CommandLine::ForCurrentProcess()->GetArgs(); |
| if (args.size() == 0) { |
| std::cerr << "no command, " << Command::GetHelp(); |
| return 1; |
| } |
| std::unique_ptr<hps::DevInterface> dev; |
| if (FLAGS_mcp) { |
| dev = hps::Mcp::Create(FLAGS_addr, FLAGS_speed); |
| } else if (FLAGS_test != "none") { |
| // Optionally initialise the fake device as already booted so that |
| // features can be enabled/disabled. |
| auto fake = std::make_unique<hps::FakeDev>(); |
| if (FLAGS_test == "ready" || FLAGS_test == "") { |
| fake->SkipBoot(); |
| } else if (FLAGS_test != "boot") { |
| std::cerr << "Unsupported fake device state: " << FLAGS_test << std::endl; |
| return 1; |
| } |
| dev = std::move(fake); |
| } else { |
| dev = hps::I2CDev::Create(FLAGS_bus, FLAGS_addr); |
| } |
| if (FLAGS_retries > 0) { |
| // If retries are required, add a retry device. |
| std::cout << "Enabling retries: " << FLAGS_retries |
| << ", delay per retry: " << FLAGS_retry_delay << " ms" |
| << std::endl; |
| auto baseDevice = std::move(dev); |
| dev = std::make_unique<hps::RetryDev>( |
| std::move(baseDevice), FLAGS_retries, |
| base::TimeDelta::FromMilliseconds(FLAGS_retry_delay)); |
| } |
| auto hps = std::make_unique<hps::HPS_impl>(std::move(dev)); |
| |
| // Show download progress when run interactively. |
| if (isatty(STDOUT_FILENO)) { |
| hps->SetDownloadObserver( |
| base::BindRepeating(&DownloadProgressIndicator::Update, |
| std::make_unique<DownloadProgressIndicator>())); |
| } |
| |
| // Pass args to the command for any following arguments. |
| // args[0] is command name. |
| return Command::Execute(args[0].c_str(), std::move(hps), args); |
| } |