| // 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. |
| // |
| // hammerd - A daemon to update the firmware of Hammer |
| |
| #include "hammerd/hammer_updater.h" |
| |
| #include <unistd.h> |
| |
| #include <pcrecpp.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/time/time.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "hammerd/uma_metric_names.h" |
| |
| namespace hammerd { |
| |
| const std::string HammerUpdater::TaskState::ToString() { |
| return base::StringPrintf( |
| "update_ro(%d) update_rw(%d) update_tp(%d) " |
| "inject_entropy(%d) post_rw_jump(%d)", |
| update_ro, update_rw, update_tp, inject_entropy, post_rw_jump); |
| } |
| |
| HammerUpdater::UpdateCondition HammerUpdater::ToUpdateCondition( |
| const std::string& s) { |
| if (s == "never") |
| return UpdateCondition::kNever; |
| if (s == "mismatch") |
| return UpdateCondition::kMismatch; |
| if (s == "always") |
| return UpdateCondition::kAlways; |
| return UpdateCondition::kUnknown; |
| } |
| |
| HammerUpdater::HammerUpdater(const std::string& ec_image, |
| const std::string& touchpad_image, |
| const std::string& touchpad_product_id, |
| const std::string& touchpad_fw_ver, |
| uint16_t vendor_id, |
| uint16_t product_id, |
| const std::string& path, |
| bool at_boot, |
| UpdateCondition update_condition) |
| : HammerUpdater( |
| ec_image, |
| touchpad_image, |
| touchpad_product_id, |
| touchpad_fw_ver, |
| at_boot, |
| update_condition, |
| std::make_unique<FirmwareUpdater>( |
| std::make_unique<UsbEndpoint>(vendor_id, product_id, path)), |
| std::make_unique<PairManager>(), |
| std::make_unique<DBusWrapper>(), |
| std::make_unique<MetricsLibrary>()) {} |
| |
| HammerUpdater::HammerUpdater( |
| const std::string& ec_image, |
| const std::string& touchpad_image, |
| const std::string& touchpad_product_id, |
| const std::string& touchpad_fw_ver, |
| bool at_boot, |
| UpdateCondition update_condition, |
| std::unique_ptr<FirmwareUpdaterInterface> fw_updater, |
| std::unique_ptr<PairManagerInterface> pair_manager, |
| std::unique_ptr<DBusWrapperInterface> dbus_wrapper, |
| std::unique_ptr<MetricsLibraryInterface> metrics) |
| : ec_image_(ec_image), |
| touchpad_image_(touchpad_image), |
| touchpad_product_id_(touchpad_product_id), |
| touchpad_fw_ver_(touchpad_fw_ver), |
| at_boot_(at_boot), |
| update_condition_(update_condition), |
| task_(HammerUpdater::TaskState()), |
| fw_updater_(std::move(fw_updater)), |
| pair_manager_(std::move(pair_manager)), |
| dbus_wrapper_(std::move(dbus_wrapper)), |
| dbus_notified_(false), |
| metrics_(std::move(metrics)) {} |
| |
| HammerUpdater::RunStatus HammerUpdater::Run() { |
| LOG(INFO) << "Load and validate the EC image."; |
| if (!fw_updater_->LoadEcImage(ec_image_)) { |
| LOG(ERROR) << "Failed to load EC image."; |
| return HammerUpdater::RunStatus::kInvalidFirmware; |
| } |
| |
| HammerUpdater::RunStatus status = RunLoop(); |
| WaitUsbReady(status); |
| if (update_condition_ != UpdateCondition::kNever) { |
| NotifyUpdateFinished(status == HammerUpdater::RunStatus::kNoUpdate); |
| } |
| return status; |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::UpdateRW() { |
| bool ret = fw_updater_->TransferImage(SectionName::RW); |
| task_.update_rw = !ret; |
| metrics_->SendEnumToUMA( |
| kMetricRWUpdateResult, |
| static_cast<int>(ret ? RWUpdateResult::kSucceeded |
| : RWUpdateResult::kTransferFailed), |
| static_cast<int>(RWUpdateResult::kCount)); |
| LOG(INFO) << "RW update " << (ret ? "passed." : "failed."); |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::RunLoop() { |
| constexpr unsigned int kMaximumRunCount = 20; |
| // Time it takes hammer to reset or jump to RW, before being |
| // available for the next USB connection. |
| constexpr unsigned int kResetTimeMs = 100; |
| bool criticality_checked = false; |
| bool can_update = update_condition_ != UpdateCondition::kNever; |
| // Set all update flags if update mode is forced. |
| if (update_condition_ == UpdateCondition::kAlways) { |
| task_.update_ro = true; |
| task_.update_rw = true; |
| task_.update_tp = true; |
| } |
| |
| HammerUpdater::RunStatus status; |
| for (int run_count = 0; run_count < kMaximumRunCount; ++run_count) { |
| UsbConnectStatus connect_status = fw_updater_->TryConnectUsb(); |
| if (connect_status != UsbConnectStatus::kSuccess) { |
| if (!criticality_checked && !can_update) { |
| metrics_->SendEnumToUMA( |
| kMetricPendingRWUpdate, |
| static_cast<int>(PendingRWUpdate::kCommunicationError), |
| static_cast<int>(PendingRWUpdate::kCount)); |
| } |
| LOG(ERROR) << "Failed to connect USB."; |
| fw_updater_->CloseUsb(); |
| |
| if (connect_status == UsbConnectStatus::kUsbPathEmpty) { |
| return HammerUpdater::RunStatus::kLostConnection; |
| } else if (connect_status == UsbConnectStatus::kInvalidDevice) { |
| LOG(ERROR) << "Invalid base connected."; |
| dbus_wrapper_->SendSignal(kInvalidBaseConnectedSignal); |
| } |
| |
| // If there is a "hammer-like" device attached, hammerd should |
| // try to avoid running again when hammer jumps to RW. Use kNeedJump |
| // to force this wait time before exiting. |
| return HammerUpdater::RunStatus::kNeedJump; |
| } |
| |
| // If this update is considered "critical", then we need to update the |
| // firmware. This block is only run once at the first round of loop. |
| if (!criticality_checked) { |
| criticality_checked = true; |
| |
| if (!can_update) { |
| PendingRWUpdate pending_metric = PendingRWUpdate::kCount; |
| |
| if (fw_updater_->IsCritical()) { |
| LOG(INFO) << "Critical update available but update condition " |
| << "is set to 'never'; notify UI."; |
| NotifyNeedUpdate(); |
| pending_metric = PendingRWUpdate::kCriticalUpdate; |
| } else if (fw_updater_->VersionMismatch(SectionName::RW) || |
| fw_updater_->CompareRollback() > 0) { |
| // In theory, an increase in rollback number should imply a version |
| // mismatch. Include both conditions here to simplify unittesting. |
| pending_metric = PendingRWUpdate::kNonCriticalUpdate; |
| } else { |
| pending_metric = PendingRWUpdate::kNoUpdate; |
| } |
| |
| metrics_->SendEnumToUMA(kMetricPendingRWUpdate, |
| static_cast<int>(pending_metric), |
| static_cast<int>(PendingRWUpdate::kCount)); |
| } |
| } |
| |
| DLOG(INFO) << "Current task state: " << task_.ToString(); |
| status = RunOnce(); |
| task_.post_rw_jump = (status == HammerUpdater::RunStatus::kNeedJump); |
| task_.post_rw_lock = (status == HammerUpdater::RunStatus::kNeedLock); |
| switch (status) { |
| case HammerUpdater::RunStatus::kNoUpdate: |
| LOG(INFO) << "Hammer does not need to update."; |
| fw_updater_->CloseUsb(); |
| return status; |
| |
| case HammerUpdater::RunStatus::kFatalError: |
| LOG(ERROR) << "Hammer encountered a fatal error!"; |
| // Send the reset signal to hammer, and then prevent the next hammerd |
| // process from being invoked. |
| fw_updater_->SendSubcommand(UpdateExtraCommand::kImmediateReset); |
| fw_updater_->CloseUsb(); |
| return HammerUpdater::RunStatus::kNeedReset; |
| |
| case HammerUpdater::RunStatus::kInvalidFirmware: |
| // Send the JumpToRW to hammer, and then prevent the next hammerd |
| // process from being invoked. |
| fw_updater_->SendSubcommand(UpdateExtraCommand::kJumpToRW); |
| fw_updater_->CloseUsb(); |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kResetTimeMs)); |
| return HammerUpdater::RunStatus::kNeedJump; |
| |
| case HammerUpdater::RunStatus::kNeedReset: |
| LOG(INFO) << "Reset hammer and run again. run_count=" << run_count; |
| fw_updater_->SendSubcommand(UpdateExtraCommand::kImmediateReset); |
| fw_updater_->CloseUsb(); |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kResetTimeMs)); |
| continue; |
| |
| case HammerUpdater::RunStatus::kNeedLock: |
| LOG(INFO) << "Request 'Jump to RW'. Hammer will reboot with locked RW. " |
| << "Run again. run_count=" << run_count; |
| fw_updater_->SendSubcommand(UpdateExtraCommand::kJumpToRW); |
| fw_updater_->CloseUsb(); |
| // TODO(kitching): Make RW jumps more robust by polling until |
| // the jump completes (or fails). |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kResetTimeMs)); |
| continue; |
| |
| case HammerUpdater::RunStatus::kNeedJump: |
| LOG(INFO) << "Jump to RW and run again. run_count=" << run_count; |
| fw_updater_->SendSubcommand(UpdateExtraCommand::kJumpToRW); |
| fw_updater_->CloseUsb(); |
| // TODO(kitching): Make RW jumps more robust by polling until |
| // the jump completes (or fails). |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kResetTimeMs)); |
| continue; |
| |
| case HammerUpdater::RunStatus::kTouchpadMismatched: |
| LOG(ERROR) << "Touchpad firmware is mismatched!"; |
| fw_updater_->CloseUsb(); |
| return HammerUpdater::RunStatus::kTouchpadMismatched; |
| |
| default: |
| LOG(ERROR) << "Unknown RunStatus: " << static_cast<int>(status); |
| fw_updater_->CloseUsb(); |
| return HammerUpdater::RunStatus::kFatalError; |
| } |
| } |
| |
| LOG(ERROR) << "Maximum run count exceeded (" << kMaximumRunCount << ")! "; |
| return status; |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::RunOnce() { |
| // The first time we use SendFirstPdu it is to gather information about |
| // hammer's running EC. We should use SendDone right away to get the EC |
| // back into a state where we can send a subcommand. |
| if (!fw_updater_->SendFirstPdu()) { |
| LOG(ERROR) << "Failed to send the first PDU."; |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| fw_updater_->SendDone(); |
| LOG(INFO) << "### Current Section: " |
| << ToString(fw_updater_->CurrentSection()) << " ###"; |
| |
| // ********************** UNKNOWN ********************** |
| // If the layout of the firmware has changed, we cannot handle this case. |
| if (fw_updater_->CurrentSection() == SectionName::Invalid) { |
| LOG(INFO) << "Hammer is in RO but the firmware layout has changed."; |
| return HammerUpdater::RunStatus::kInvalidFirmware; |
| } |
| |
| // After sending first PDU, we get the information of current EC. |
| // Check if the firmware version is mismatched or not. |
| if (update_condition_ == UpdateCondition::kMismatch) { |
| // In theory, an increase in rollback number should imply a version |
| // mismatch. Include both conditions here to simplify unittesting. |
| if (fw_updater_->VersionMismatch(SectionName::RW) || |
| fw_updater_->CompareRollback() > 0) |
| task_.update_rw = true; |
| if (fw_updater_->VersionMismatch(SectionName::RO)) |
| task_.update_ro = true; |
| } |
| |
| // ********************** RW ********************** |
| // If EC already entered the RW section, then check if RW needs updating. |
| // If an update is needed, request a hammer reset. Let the next invocation |
| // of Run handle the update. |
| if (fw_updater_->CurrentSection() == SectionName::RW) { |
| if (task_.update_rw) { |
| if (fw_updater_->ValidKey() && fw_updater_->CompareRollback() >= 0) { |
| LOG(INFO) << "RW section needs update. Rebooting to RO."; |
| if (fw_updater_->IsSectionLocked(SectionName::RW)) { |
| fw_updater_->UnlockRW(); |
| } |
| return HammerUpdater::RunStatus::kNeedReset; |
| } else { |
| task_.update_ro = true; |
| LOG(INFO) << "RW section needs update, but local image is " |
| << "incompatible. Continuing to post-RW process; maybe " |
| << "RO can be updated."; |
| } |
| } |
| return PostRWProcess(); |
| } |
| |
| // ********************** RO ********************** |
| // Current section is now guaranteed to be RO. Deal with |
| // each of three possible ongoing tasks: |
| // (1) jump to RW (failed, attempt update if possible) |
| // (2) inject entropy |
| // (3) update RW section |
| if (task_.post_rw_jump || task_.inject_entropy || |
| (task_.update_rw && fw_updater_->ValidKey() && |
| fw_updater_->CompareRollback() >= 0)) { |
| // If we have just finished a jump to RW, but we're still in RO, then |
| // we should log the failure. |
| if (task_.post_rw_jump) { |
| LOG(ERROR) << "Failed to jump to RW. Need to update RW section."; |
| if (update_condition_ == UpdateCondition::kNever) { |
| LOG(INFO) << "RW is broken but update condition is 'never', notify UI."; |
| NotifyNeedUpdate(); |
| return HammerUpdater::RunStatus::kFatalError; |
| } |
| if (!fw_updater_->ValidKey() || fw_updater_->CompareRollback() < 0) { |
| LOG(ERROR) << "RW section is unusable, but local image is " |
| << "incompatible. Giving up."; |
| // If both key and rollback are invalid, only the key will be |
| // reported to UMA as invalid. |
| metrics_->SendEnumToUMA( |
| kMetricRWUpdateResult, |
| static_cast<int>(fw_updater_->ValidKey() |
| ? RWUpdateResult::kRollbackDisallowed |
| : RWUpdateResult::kInvalidKey), |
| static_cast<int>(RWUpdateResult::kCount)); |
| return HammerUpdater::RunStatus::kFatalError; |
| } |
| } |
| |
| // EC is still running in RO section. Send "Stay in RO" command before |
| // continuing. |
| LOG(INFO) << "Sending stay in RO command."; |
| if (!fw_updater_->SendSubcommand(UpdateExtraCommand::kStayInRO)) { |
| LOG(ERROR) << "Failed to stay in RO."; |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| |
| if (task_.inject_entropy) { |
| bool ret = fw_updater_->InjectEntropy(); |
| if (ret) { |
| task_.inject_entropy = false; |
| LOG(INFO) << "Successfully injected entropy."; |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| LOG(ERROR) << "Failed to inject entropy."; |
| return HammerUpdater::RunStatus::kFatalError; |
| } |
| |
| if (fw_updater_->IsSectionLocked(SectionName::RW)) { |
| LOG(INFO) << "Unlock RW section, and reset EC."; |
| fw_updater_->UnlockRW(); |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| |
| // Now RW section needs an update, and it is not locked. Let's update! |
| return UpdateRW(); |
| } |
| |
| // Now we need to jump to RW section. When requesting 'Jump to RW', hammer |
| // responds differently depending on the state of RO and RW locks: |
| // (1) RO is unlocked: |
| // hammerd will jump to RW regardless of wether or not RW is locked. |
| // (2) RO is locked: |
| // (a) RW is locked: hammer will jump to RW. |
| // (b) RW is unlocked: hammer will set RW to be locked on next boot, and |
| // reset itself. |
| // In the case of (2)(b), after requesting the jump, hammer will reset itself |
| // and end up in RO. Now we fall under the case of (2)(1) and may request the |
| // jump again. |
| // TODO(b/117909308): add unittest. |
| if (fw_updater_->IsSectionLocked(SectionName::RO) && |
| !fw_updater_->IsSectionLocked(SectionName::RW)) { |
| if (task_.post_rw_lock) { |
| LOG(INFO) << "Failed to lock RW section... update RW section again."; |
| return UpdateRW(); |
| } |
| LOG(INFO) << "RO is locked but RW is not. " |
| << "Lock RW by asking hammer to reset."; |
| return HammerUpdater::RunStatus::kNeedLock; |
| } |
| task_.post_rw_lock = false; |
| |
| LOG(INFO) << "No need to update RW. Jump to RW section."; |
| return HammerUpdater::RunStatus::kNeedJump; |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::PostRWProcess() { |
| LOG(INFO) << "Start the post-RW process."; |
| HammerUpdater::RunStatus ret; |
| |
| // Update RO section. |
| ret = UpdateRO(); |
| if (ret != HammerUpdater::RunStatus::kNoUpdate) { |
| return ret; |
| } |
| |
| // Trigger the retry if update fails. |
| ret = RunTouchpadUpdater(); |
| if (ret != HammerUpdater::RunStatus::kTouchpadUpToDate) { |
| LOG(INFO) << "Touchpad update failure."; |
| return ret; |
| } |
| |
| // Pair with hammer. |
| if (!at_boot_) { |
| ret = Pair(); |
| if (ret != HammerUpdater::RunStatus::kNoUpdate) { |
| return ret; |
| } |
| } |
| |
| // TODO(akahuang): Rollback increment. |
| // All process are done. |
| return HammerUpdater::RunStatus::kNoUpdate; |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::UpdateRO() { |
| // RO section should be unlocked on dogfood devices -- no need to first run |
| // UnLockSection. |
| // TODO(kitching): Consider adding a UI warning to make sure a dogfood user |
| // does not detach the base at the wrong time, as that could brick it. |
| if (fw_updater_->IsSectionLocked(SectionName::RO)) { |
| LOG(INFO) << "RO section is locked. Update infeasible."; |
| return HammerUpdater::RunStatus::kNoUpdate; |
| } |
| if (!task_.update_ro) { |
| LOG(INFO) << "RO section is unlocked, but update not needed."; |
| return HammerUpdater::RunStatus::kNoUpdate; |
| } |
| LOG(INFO) << "RO is unlocked and update is needed. Starting update."; |
| NotifyUpdateStarted(); |
| bool ret = fw_updater_->TransferImage(SectionName::RO); |
| task_.update_ro = !ret; |
| metrics_->SendEnumToUMA( |
| kMetricROUpdateResult, |
| static_cast<int>(ret ? ROUpdateResult::kSucceeded |
| : ROUpdateResult::kTransferFailed), |
| static_cast<int>(ROUpdateResult::kCount)); |
| LOG(INFO) << "RO update " << (ret ? "passed." : "failed."); |
| // In the case that the update failed, a reset will either brick the device, |
| // or get it back into a normal state. |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::Pair() { |
| ChallengeStatus status = |
| pair_manager_->PairChallenge(fw_updater_.get(), dbus_wrapper_.get()); |
| PairResult metric_result = PairResult::kUnknownError; |
| HammerUpdater::RunStatus ret = HammerUpdater::RunStatus::kFatalError; |
| |
| switch (status) { |
| case ChallengeStatus::kChallengePassed: |
| metric_result = PairResult::kChallengePassed; |
| // TODO(akahuang): Check if the base is swapped. |
| ret = HammerUpdater::RunStatus::kNoUpdate; |
| break; |
| |
| case ChallengeStatus::kNeedInjectEntropy: |
| metric_result = PairResult::kNeedInjectEntropy; |
| if (fw_updater_->IsRollbackLocked()) { |
| if (!fw_updater_->UnlockRollback()) { |
| LOG(ERROR) << "Failed to unlock rollback. Skip injecting entropy."; |
| ret = HammerUpdater::RunStatus::kFatalError; |
| break; |
| } |
| } |
| task_.inject_entropy = true; |
| ret = HammerUpdater::RunStatus::kNeedReset; |
| break; |
| |
| case ChallengeStatus::kChallengeFailed: |
| metric_result = PairResult::kChallengeFailed; |
| break; |
| |
| case ChallengeStatus::kConnectionError: |
| // Do not send UMA if the base is disconnected. |
| metric_result = PairResult::kCount; |
| ret = HammerUpdater::RunStatus::kLostConnection; |
| break; |
| |
| case ChallengeStatus::kUnknownError: |
| break; |
| } |
| |
| if (metric_result != PairResult::kCount) { |
| metrics_->SendEnumToUMA(kMetricPairResult, static_cast<int>(metric_result), |
| static_cast<int>(PairResult::kCount)); |
| } |
| return ret; |
| } |
| |
| void HammerUpdater::WaitUsbReady(HammerUpdater::RunStatus status) { |
| // The time period after which hammer automatically jumps to RW section. |
| constexpr unsigned int kJumpToRWTimeMs = 1000; |
| // The time period from USB device ready to udev invoking hammerd. |
| constexpr unsigned int kUdevGuardTimeMs = 1500; |
| |
| // If hammerd send reset or jump to RW signal at the last run, hammer will |
| // re-connect to the AP and udev will trigger hammerd again. We MUST prohibit |
| // the next invocation, otherwise udev will invoke hammerd infinitely. |
| // |
| // The timing of invocation might be entering into RO section or RW section. |
| // Therefore we might wait for USB device once when sending JumpToRW, and wait |
| // twice when sending Reset signal. |
| if (status == HammerUpdater::RunStatus::kNeedReset || |
| status == HammerUpdater::RunStatus::kNeedJump) { |
| LOG(INFO) << "Wait for USB device ready..."; |
| UsbConnectStatus usb_connection = fw_updater_->TryConnectUsb(); |
| fw_updater_->CloseUsb(); |
| // If there is no device there, don't bother waiting. |
| if (usb_connection == UsbConnectStatus::kUsbPathEmpty) { |
| return; |
| } |
| if (status == HammerUpdater::RunStatus::kNeedReset) { |
| LOG(INFO) << "USB device probably in RO, waiting for it to enter RW."; |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kJumpToRWTimeMs)); |
| |
| usb_connection = fw_updater_->TryConnectUsb(); |
| fw_updater_->CloseUsb(); |
| // If there is no device there, don't bother waiting. |
| if (usb_connection == UsbConnectStatus::kUsbPathEmpty) { |
| return; |
| } |
| } |
| |
| LOG(INFO) << "Now USB device should be in RW. Wait " << kUdevGuardTimeMs |
| << "ms to prevent udev invoking next process."; |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kUdevGuardTimeMs)); |
| LOG(INFO) << "Finish the infinite loop prevention."; |
| } |
| } |
| |
| void HammerUpdater::NotifyNeedUpdate() { |
| DCHECK(update_condition_ == UpdateCondition::kNever); |
| if (!dbus_notified_) { |
| dbus_notified_ = true; |
| dbus_wrapper_->SendSignal(kBaseFirmwareNeedUpdateSignal); |
| } |
| } |
| |
| void HammerUpdater::NotifyUpdateStarted() { |
| DCHECK(update_condition_ != UpdateCondition::kNever); |
| if (!dbus_notified_) { |
| dbus_notified_ = true; |
| dbus_wrapper_->SendSignal(kBaseFirmwareUpdateStartedSignal); |
| } |
| } |
| |
| void HammerUpdater::NotifyUpdateFinished(bool is_success) { |
| DCHECK(update_condition_ != UpdateCondition::kNever); |
| // If we tried to update the firmware, send a signal to notify the updating is |
| // finished. |
| if (dbus_notified_) { |
| dbus_notified_ = false; |
| dbus_wrapper_->SendSignal(is_success ? kBaseFirmwareUpdateSucceededSignal |
| : kBaseFirmwareUpdateFailedSignal); |
| } |
| } |
| |
| std::string HammerUpdater::VersionString(TouchpadInfo info) { |
| std::string base_fw_ver; |
| if (info.vendor == ST_VENDOR_ID) { |
| base_fw_ver = |
| base::StringPrintf(kStFormatString, info.st.fw_version & 0x00ff, |
| (info.st.fw_version & 0xff00) >> 8); |
| } else { |
| base_fw_ver = base::StringPrintf(kElanFormatString, info.elan.fw_version); |
| } |
| return base_fw_ver; |
| } |
| |
| std::string HammerUpdater::VendorString(TouchpadInfo info) { |
| std::string vendor; |
| switch (info.vendor) { |
| case ST_VENDOR_ID: |
| return "ST"; |
| break; |
| case ELAN_VENDOR_ID: |
| return "ELAN"; |
| break; |
| default: |
| return "UNKNOWN"; |
| break; |
| } |
| } |
| |
| HammerUpdater::RunStatus HammerUpdater::RunTouchpadUpdater() { |
| if (!touchpad_image_.size()) { // We are missing the touchpad file. |
| LOG(INFO) << "Touchpad will remain unmodified as binary is not provided."; |
| return HammerUpdater::RunStatus::kTouchpadUpToDate; |
| } |
| |
| LOG(INFO) << "Loading touchpad firmware image."; |
| if (!fw_updater_->LoadTouchpadImage(touchpad_image_)) { |
| LOG(ERROR) << "Failed to load touchpad image."; |
| return HammerUpdater::RunStatus::kTouchpadMismatched; |
| } |
| |
| // Make request to get infomation from hammer. |
| TouchpadInfo response; |
| if (!fw_updater_->SendSubcommandReceiveResponse( |
| UpdateExtraCommand::kTouchpadInfo, "", |
| reinterpret_cast<void*>(&response), sizeof(response))) { |
| LOG(ERROR) << "Not able to get touchpad info from base."; |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| LOG(INFO) << "Current touchpad information from base:"; |
| LOG(INFO) << "status: 0x" << std::hex << static_cast<int>(response.status); |
| LOG(INFO) << "vendor: 0x" << std::hex << response.vendor << " " |
| << VendorString(response); |
| LOG(INFO) << "fw_address: 0x" << std::hex << response.fw_address; |
| LOG(INFO) << "fw_size: " << response.fw_size << " bytes"; |
| LOG(INFO) << "allowed_fw_hash: 0x" |
| << base::HexEncode(response.allowed_fw_hash, SHA256_DIGEST_LENGTH); |
| LOG(INFO) << "product_id: " << response.elan.id << ".0"; |
| |
| std::string base_fw_ver = VersionString(response); |
| LOG(INFO) << "fw_ver: " << base_fw_ver; |
| |
| LOG(INFO) << "fw_checksum: 0x" << std::hex << response.elan.fw_checksum; |
| |
| if (response.status != static_cast<uint8_t>(EcResponseStatus::kSuccess)) { |
| // EC must be really screw up to get this. |
| LOG(ERROR) << "Base can't read I2C bus normally. Abort touchpad update."; |
| return HammerUpdater::RunStatus::kNeedReset; |
| } |
| |
| // Check if the image size matches IC size. |
| if (touchpad_image_.size() != response.fw_size) { |
| LOG(ERROR) << "Local touchpad binary doesn't match remote IC size."; |
| LOG(ERROR) << "Local=" << touchpad_image_.size() << " bytes." |
| << "Remote=" << response.fw_size << " bytes."; |
| return HammerUpdater::RunStatus::kTouchpadMismatched; |
| } |
| |
| // Check if the SHA value of the touchpad firmware (entire file) has same |
| // hash as the record in RW firmware. We check this prior to update |
| // because if an individual chunk verification fail, the touchpad might |
| // get into a weird state (only part of the flash is updated). |
| uint8_t digest[SHA256_DIGEST_LENGTH]; |
| |
| SHA256(reinterpret_cast<const uint8_t*>(touchpad_image_.data()), |
| response.fw_size, reinterpret_cast<unsigned char*>(&digest)); |
| LOG(INFO) << "Computed local touchpad firmware hash: 0x" |
| << base::HexEncode(digest, SHA256_DIGEST_LENGTH); |
| if (std::memcmp(digest, response.allowed_fw_hash, SHA256_DIGEST_LENGTH)) { |
| LOG(ERROR) << "Touchpad firmware mismatches hash in RW EC."; |
| return HammerUpdater::RunStatus::kTouchpadMismatched; |
| } |
| |
| // Check if the product_id is matched. Currently, Elan uses numbers for |
| // product_id, but it might be different for other vendors. For example, |
| // in chromeos-touch-firmware-nyan package, Cypress uses product id like |
| // CYTRA-103006-00. |
| if (base::StringPrintf(kElanFormatString, response.elan.id) != |
| touchpad_product_id_) { |
| LOG(ERROR) << "product_id mismatch. Local: " << touchpad_product_id_; |
| return HammerUpdater::RunStatus::kTouchpadMismatched; |
| } |
| |
| if (!task_.update_tp) { |
| // If fw_ver match, then we skip the update. Otherwise, flash it. |
| |
| LOG(INFO) << base::StringPrintf( |
| "Checking touchpad firmware version: Local(%s) vs. Base(%s)", |
| touchpad_fw_ver_.c_str(), base_fw_ver.c_str()); |
| |
| if (base_fw_ver == touchpad_fw_ver_) { |
| LOG(INFO) << "Version matched, skip update."; |
| return HammerUpdater::RunStatus::kTouchpadUpToDate; |
| } |
| // Version mismatches. However, if update condition is "never", then |
| // we should notify UI when firmware is broken, or just skip update. |
| if (update_condition_ == UpdateCondition::kNever) { |
| if (response.elan.fw_version == kElanBrokenFwVersion) { |
| LOG(INFO) << "Touchpad firmware is broken but never update, notify UI."; |
| NotifyNeedUpdate(); |
| return HammerUpdater::RunStatus::kTouchpadMismatched; |
| } |
| LOG(INFO) << "Pretend touchpad firmware is up to date."; |
| return HammerUpdater::RunStatus::kTouchpadUpToDate; |
| } |
| // OK, we really need to update touchpad now. |
| task_.update_tp = true; |
| } |
| LOG(INFO) << "Update touchpad firmware, notify UI"; |
| NotifyUpdateStarted(); |
| bool ret = fw_updater_->TransferTouchpadFirmware(response.fw_address, |
| response.fw_size); |
| task_.update_tp = !ret; |
| return ret ? HammerUpdater::RunStatus::kTouchpadUpToDate |
| : HammerUpdater::RunStatus::kNeedReset; |
| } |
| |
| bool HammerUpdater::ParseTouchpadInfoFromFilename( |
| const std::string& filename, |
| std::string* touchpad_product_id, |
| std::string* touchpad_fw_ver) { |
| base::FilePath real_path; |
| bool ret = base::NormalizeFilePath(base::FilePath(filename), &real_path); |
| std::string basename = real_path.BaseName().value(); |
| |
| LOG(INFO) << "Canonical path for touchpad firmware : " << real_path.value(); |
| // Filename should be in format of <product_id>_<fw_ver>.bin |
| pcrecpp::RE re("(.+)_([\\.\\d]+?)\\.bin"); |
| ret &= re.FullMatch(basename, touchpad_product_id, touchpad_fw_ver); |
| LOG(INFO) << "Parsed product_id : " << *touchpad_product_id; |
| LOG(INFO) << "Parsed fw_ver : " << *touchpad_fw_ver; |
| |
| return ret; |
| } |
| |
| void HammerUpdater::SetInjectEntropyFlag(bool inject_entropy) { |
| LOG(INFO) << "inject_entropy is set to " << inject_entropy; |
| task_.inject_entropy = inject_entropy; |
| } |
| |
| } // namespace hammerd |