| // 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 <stdint.h> |
| #include <stdlib.h> |
| |
| #include <string> |
| |
| #include <base/command_line.h> |
| #include <base/logging.h> |
| #include <base/time/time.h> |
| #include <base/timer/elapsed_timer.h> |
| #include <brillo/daemons/daemon.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/syslog_logging.h> |
| #include <base/strings/stringprintf.h> |
| #include <cros_config/cros_config.h> |
| |
| #include "biod/biod_metrics.h" |
| #include "biod/biod_version.h" |
| #include "biod/cros_fp_device.h" |
| #include "biod/updater/cros_fp_updater.h" |
| #include "biod/updater/update_reason.h" |
| #include "biod/updater/update_status.h" |
| |
| using UpdateStatus = biod::updater::UpdateStatus; |
| using UpdateReason = biod::updater::UpdateReason; |
| using FwUpdaterStatus = biod::BiodMetrics::FwUpdaterStatus; |
| using FindFirmwareFileStatus = biod::updater::FindFirmwareFileStatus; |
| |
| namespace { |
| |
| constexpr char kHelpText[] = |
| "bio_fw_updater ensures the fingerprint mcu has the latest firmware\n"; |
| |
| void LogFWFileVersion(const biod::CrosFpFirmware& fw) { |
| biod::CrosFpFirmware::ImageVersion ver = fw.GetVersion(); |
| LOG(INFO) << "FWFile RO Version: '" << ver.ro_version << "'"; |
| LOG(INFO) << "FWFile RW Version: '" << ver.rw_version << "'"; |
| } |
| |
| void LogFPMCUVersion(const biod::CrosFpDevice::EcVersion& ver) { |
| LOG(INFO) << "FPMCU RO Version: '" << ver.ro_version << "'"; |
| LOG(INFO) << "FPMCU RW Version: '" << ver.rw_version << "'"; |
| LOG(INFO) << "FPMCU Active Image: " |
| << biod::CrosFpDeviceUpdate::EcCurrentImageToString( |
| ver.current_image); |
| } |
| |
| void LogSetupDirectory(const base::FilePath& log_dir_path) { |
| const auto log_file_path = log_dir_path.Append(base::StringPrintf( |
| "bio_fw_updater.%s", |
| brillo::GetTimeAsLogString(base::Time::Now()).c_str())); |
| |
| brillo::UpdateLogSymlinks(log_dir_path.Append("bio_fw_updater.LATEST"), |
| log_dir_path.Append("bio_fw_updater.PREVIOUS"), |
| log_file_path); |
| |
| logging::LoggingSettings logging_settings; |
| logging_settings.logging_dest = logging::LOG_TO_FILE; |
| #if BASE_VER < 780000 |
| logging_settings.log_file = log_file_path.value().c_str(); |
| #else |
| logging_settings.log_file_path = log_file_path.value().c_str(); |
| #endif |
| logging_settings.lock_log = logging::DONT_LOCK_LOG_FILE; |
| logging::InitLogging(logging_settings); |
| logging::SetLogItems(true, // process ID |
| true, // thread ID |
| true, // timestamp |
| false); // tickcount |
| } |
| |
| class UpdaterMetrics { |
| public: |
| void SetUpdateReason(UpdateReason reason) { update_reason_ = reason; } |
| void Finished(FwUpdaterStatus status) { |
| uint64_t overall = runtime_.Elapsed().InMilliseconds(); |
| DLOG(INFO) << "Runtime took " << overall << "ms."; |
| metrics_.SendFwUpdaterStatus(status, update_reason_, |
| static_cast<int>(overall)); |
| } |
| |
| private: |
| biod::BiodMetrics metrics_; |
| UpdateReason update_reason_ = UpdateReason::kNone; |
| base::ElapsedTimer runtime_; |
| }; |
| |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| UpdaterMetrics metrics; |
| |
| DEFINE_string(log_dir, "/var/log/biod", "Directory where logs are written."); |
| brillo::FlagHelper::Init(argc, argv, kHelpText); |
| |
| const auto log_dir_path = base::FilePath(FLAGS_log_dir); |
| if (base::DirectoryExists(log_dir_path)) { |
| LogSetupDirectory(log_dir_path); |
| } else { |
| LOG(ERROR) << "Log directory '" << log_dir_path.value() |
| << "' does not exist, using syslog and stderr logging."; |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty | |
| brillo::kLogHeader); |
| } |
| |
| biod::LogVersion(); |
| |
| // Check for firmware disable mechanism |
| if (biod::updater::UpdateDisallowed()) { |
| LOG(INFO) << "FPMCU update disabled, exiting."; |
| return EXIT_SUCCESS; |
| } |
| |
| // Check if model supports fingerprint |
| brillo::CrosConfig cros_config; |
| if (!cros_config.Init()) { |
| LOG(WARNING) << "Cros config is not supported on this model, continuing " |
| "with legacy update."; |
| } |
| if (biod::updater::FingerprintUnsupported(&cros_config)) { |
| LOG(INFO) << "Fingerprint is not supported on this model, exiting."; |
| return EXIT_SUCCESS; |
| } |
| |
| // Find a firmware file that matches the firmware file pattern |
| base::FilePath file; |
| auto status = biod::updater::FindFirmwareFile( |
| base::FilePath(biod::updater::kFirmwareDir), &cros_config, &file); |
| |
| if (status == FindFirmwareFileStatus::kNoDirectory || |
| status == FindFirmwareFileStatus::kFileNotFound) { |
| LOG(INFO) << "No firmware " |
| << ((status == FindFirmwareFileStatus::kNoDirectory) ? "directory" |
| : "file") |
| << " on rootfs, exiting."; |
| |
| return EXIT_SUCCESS; |
| } |
| if (status == FindFirmwareFileStatus::kMultipleFiles) { |
| LOG(ERROR) << "Found more than one firmware file, aborting."; |
| metrics.Finished(FwUpdaterStatus::kFailureFirmwareFileMultiple); |
| return EXIT_FAILURE; |
| } |
| |
| biod::CrosFpFirmware fw(file); |
| if (!fw.IsValid()) { |
| LOG(ERROR) << "Failed to load firmware file '" << fw.GetPath().value() |
| << "': " << fw.GetStatusString(); |
| LOG(ERROR) << "We are aborting update."; |
| } |
| switch (fw.GetStatus()) { |
| case biod::CrosFpFirmware::Status::kUninitialized: |
| NOTREACHED(); |
| return EXIT_FAILURE; |
| case biod::CrosFpFirmware::Status::kOk: |
| break; |
| case biod::CrosFpFirmware::Status::kNotFound: |
| metrics.Finished(FwUpdaterStatus::kFailureFirmwareFileNotFound); |
| return EXIT_FAILURE; |
| case biod::CrosFpFirmware::Status::kOpenError: |
| metrics.Finished(FwUpdaterStatus::kFailureFirmwareFileOpen); |
| return EXIT_FAILURE; |
| case biod::CrosFpFirmware::Status::kBadFmap: |
| metrics.Finished(FwUpdaterStatus::kFailureFirmwareFileFmap); |
| return EXIT_FAILURE; |
| } |
| LogFWFileVersion(fw); |
| |
| biod::CrosFpDeviceUpdate ec_device; |
| biod::CrosFpBootUpdateCtrl boot_ctrl; |
| |
| biod::CrosFpDevice::EcVersion ecver; |
| if (!ec_device.GetVersion(&ecver)) { |
| LOG(INFO) << "Failed to fetch EC version, aborting."; |
| metrics.Finished(FwUpdaterStatus::kFailurePreUpdateVersionCheck); |
| return EXIT_FAILURE; |
| } |
| LogFPMCUVersion(ecver); |
| |
| auto result = biod::updater::DoUpdate(ec_device, boot_ctrl, fw); |
| metrics.SetUpdateReason(result.reason); |
| switch (result.status) { |
| case UpdateStatus::kUpdateFailedGetVersion: |
| LOG(INFO) << "Failed to fetch EC version, aborting."; |
| metrics.Finished(FwUpdaterStatus::kFailureUpdateVersionCheck); |
| return EXIT_FAILURE; |
| case UpdateStatus::kUpdateFailedFlashProtect: |
| LOG(ERROR) << "Failed to fetch flash protect status, aborting."; |
| metrics.Finished(FwUpdaterStatus::kFailureUpdateFlashProtect); |
| return EXIT_FAILURE; |
| case UpdateStatus::kUpdateFailedRO: |
| LOG(ERROR) << "Failed to update RO image, aborting."; |
| metrics.Finished(FwUpdaterStatus::kFailureUpdateRO); |
| return EXIT_FAILURE; |
| case UpdateStatus::kUpdateFailedRW: |
| LOG(ERROR) << "Failed to update RW image, aborting."; |
| metrics.Finished(FwUpdaterStatus::kFailureUpdateRW); |
| return EXIT_FAILURE; |
| case UpdateStatus::kUpdateSucceeded: |
| if (!ec_device.GetVersion(&ecver)) { |
| LOG(ERROR) << "Failed to fetch final EC version, update failed."; |
| metrics.Finished(FwUpdaterStatus::kFailurePostUpdateVersionCheck); |
| return EXIT_FAILURE; |
| } |
| LogFPMCUVersion(ecver); |
| LOG(INFO) << "The update was successful."; |
| metrics.Finished(FwUpdaterStatus::kSuccessful); |
| return EXIT_SUCCESS; |
| case UpdateStatus::kUpdateNotNecessary: |
| LOG(INFO) << "Update was not necessary."; |
| metrics.Finished(FwUpdaterStatus::kUnnecessary); |
| return EXIT_SUCCESS; |
| } |
| |
| NOTREACHED(); |
| return EXIT_SUCCESS; |
| } |