| // Copyright 2017 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "modemfwd/daemon.h" |
| |
| #include <signal.h> |
| #include <sysexits.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/containers/contains.h> |
| #include <base/containers/fixed_flat_map.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/functional/bind.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/task/single_thread_task_runner.h> |
| #include <base/time/time.h> |
| #include <cros_config/cros_config.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <sys/wait.h> |
| |
| #include "modemfwd/dlc_manager.h" |
| #include "modemfwd/error.h" |
| #include "modemfwd/firmware_directory.h" |
| #include "modemfwd/logging.h" |
| #include "modemfwd/metrics.h" |
| #include "modemfwd/modem.h" |
| #include "modemfwd/modem_flasher.h" |
| #include "modemfwd/modem_helper_directory.h" |
| #include "modemfwd/modem_sandbox.h" |
| #include "modemfwd/modem_tracker.h" |
| #include "modemfwd/notification_manager.h" |
| #include "modemfwd/prefs.h" |
| #include "modemfwd/proto_bindings/firmware_manifest_v2.pb.h" |
| |
| namespace { |
| |
| const char kManifestName[] = "firmware_manifest.textproto"; |
| const char kManifestNameLegacy[] = "firmware_manifest.prototxt"; |
| constexpr base::TimeDelta kWedgeCheckDelay = base::Minutes(2); |
| constexpr base::TimeDelta kRebootCheckDelay = base::Minutes(1); |
| constexpr base::TimeDelta kDlcRemovalDelay = base::Minutes(2); |
| |
| constexpr char kPrefsDir[] = "/var/lib/modemfwd/"; |
| // The existence of a device id in |kModemsSeenSinceOobeKey| is used to |
| // indicate if a modem that belongs to that variant was ever seen. |
| constexpr char kModemsSeenSinceOobeKey[] = "modems_seen_since_oobe"; |
| constexpr char kDisableAutoUpdateKey[] = "disable_auto_update"; |
| |
| // Returns the modem firmware variant for the current model of the device by |
| // reading the /modem/firmware-variant property of the current model via |
| // chromeos-config. Returns an empty string if it fails to read the modem |
| // firmware variant from chromeos-config or no modem firmware variant is |
| // specified. |
| std::string GetModemFirmwareVariant() { |
| brillo::CrosConfig config; |
| std::string variant; |
| if (!config.GetString("/modem", "firmware-variant", &variant)) { |
| LOG(INFO) << "No modem firmware variant is specified"; |
| return std::string(); |
| } |
| |
| LOG(INFO) << "Use modem firmware variant: " << variant; |
| return variant; |
| } |
| |
| std::string ToOnOffString(bool b) { |
| return b ? "on" : "off"; |
| } |
| |
| // Returns the delay to wait before rebooting the modem if it hasn't appeared |
| // on the USB bus by reading the /modem/wedge-reboot-delay-ms property of the |
| // current model via chromeos-config, or using the default `kWedgeCheckDelay` |
| // constant if it fails to read it from chromeos-config or nothing is specified. |
| base::TimeDelta GetModemWedgeCheckDelay() { |
| brillo::CrosConfig config; |
| std::string delay_ms; |
| if (!config.GetString("/modem", "wedge-reboot-delay-ms", &delay_ms)) { |
| return kWedgeCheckDelay; |
| } |
| |
| int64_t ms; |
| if (!base::StringToInt64(delay_ms, &ms)) { |
| LOG(WARNING) << "Invalid wedge-reboot-delay-ms attribute " << delay_ms |
| << " using default " << kWedgeCheckDelay; |
| return kWedgeCheckDelay; |
| } |
| |
| base::TimeDelta wedge_delay = base::Milliseconds(ms); |
| LOG(INFO) << "Use customized wedge reboot delay: " << wedge_delay; |
| return wedge_delay; |
| } |
| } // namespace |
| |
| namespace modemfwd { |
| |
| DBusAdaptor::DBusAdaptor(scoped_refptr<dbus::Bus> bus, Delegate* delegate) |
| : org::chromium::ModemfwdAdaptor(this), |
| dbus_object_(nullptr, bus, dbus::ObjectPath(kModemfwdServicePath)), |
| delegate_(delegate) { |
| DCHECK(delegate); |
| } |
| |
| void DBusAdaptor::RegisterAsync( |
| brillo::dbus_utils::AsyncEventSequencer::CompletionAction cb) { |
| RegisterWithDBusObject(&dbus_object_); |
| dbus_object_.RegisterAsync(std::move(cb)); |
| } |
| |
| void DBusAdaptor::SetDebugMode(bool debug_mode) { |
| g_extra_logging = debug_mode; |
| LOG(INFO) << "Debug mode is now " << ToOnOffString(ELOG_IS_ON()); |
| } |
| |
| bool DBusAdaptor::ForceFlash(const std::string& device_id, |
| const brillo::VariantDictionary& args) { |
| std::string carrier_uuid = |
| brillo::GetVariantValueOrDefault<std::string>(args, "carrier_uuid"); |
| std::string variant = |
| brillo::GetVariantValueOrDefault<std::string>(args, "variant"); |
| bool use_modems_fw_info = |
| brillo::GetVariantValueOrDefault<bool>(args, "use_modems_fw_info"); |
| return delegate_->ForceFlashForTesting(device_id, carrier_uuid, variant, |
| use_modems_fw_info); |
| } |
| |
| Daemon::Daemon(const std::string& journal_file, |
| const std::string& helper_directory, |
| const std::string& firmware_directory) |
| : DBusServiceDaemon(kModemfwdServiceName), |
| journal_file_path_(journal_file), |
| helper_dir_path_(helper_directory), |
| fw_manifest_dir_path_(firmware_directory), |
| weak_ptr_factory_(this) {} |
| |
| int Daemon::OnInit() { |
| int exit_code = brillo::DBusServiceDaemon::OnInit(); |
| if (exit_code != EX_OK) |
| return exit_code; |
| DCHECK(!helper_dir_path_.empty()); |
| |
| std::unique_ptr<MetricsLibraryInterface> metrics_library = |
| std::make_unique<MetricsLibrary>(); |
| metrics_ = std::make_unique<Metrics>(std::move(metrics_library)); |
| |
| notification_mgr_ = std::make_unique<NotificationManager>(dbus_adaptor_.get(), |
| metrics_.get()); |
| if (!base::DirectoryExists(helper_dir_path_)) { |
| auto err = Error::Create( |
| FROM_HERE, kErrorResultInitFailure, |
| base::StringPrintf( |
| "Supplied modem-specific helper directory %s does not exist", |
| helper_dir_path_.value().c_str())); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| |
| prefs_ = Prefs::CreatePrefs(base::FilePath(kPrefsDir)); |
| if (!prefs_) { |
| auto err = Error::Create(FROM_HERE, kErrorResultInitFailure, |
| "Prefs could not be created"); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| modems_seen_since_oobe_prefs_ = |
| Prefs::CreatePrefs(*prefs_, kModemsSeenSinceOobeKey); |
| if (!modems_seen_since_oobe_prefs_) { |
| auto err = Error::Create(FROM_HERE, kErrorResultInitFailure, |
| "ModemsSeenSinceOobe prefs could not be created"); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| |
| variant_ = GetModemFirmwareVariant(); |
| helper_directory_ = |
| CreateModemHelperDirectory(helper_dir_path_, variant_, bus_); |
| if (!helper_directory_) { |
| auto err = |
| Error::Create(FROM_HERE, |
| (variant_.empty() ? kErrorResultInitFailureNonLteSku |
| : kErrorResultInitFailure), |
| base::StringPrintf("No suitable helpers found in %s", |
| helper_dir_path_.value().c_str())); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| |
| // If no firmware directory was supplied, we can't run. |
| if (fw_manifest_dir_path_.empty()) |
| return EX_UNAVAILABLE; |
| |
| if (!base::DirectoryExists(fw_manifest_dir_path_)) { |
| auto err = Error::Create( |
| FROM_HERE, kErrorResultInitFailure, |
| base::StringPrintf("Supplied firmware directory %s does not exist", |
| fw_manifest_dir_path_.value().c_str())); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| |
| suspend_checker_ = SuspendChecker::Create(); |
| if (!suspend_checker_) { |
| auto err = Error::Create(FROM_HERE, kErrorResultInitFailure, |
| "Suspend checker could not be created"); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| |
| return SetupFirmwareDirectory(); |
| } |
| |
| int Daemon::SetupFirmwareDirectory() { |
| CHECK(!fw_manifest_dir_path_.empty()); |
| |
| std::map<std::string, Dlc> dlc_per_variant; |
| auto file_name = base::PathExists(fw_manifest_dir_path_.Append(kManifestName)) |
| ? kManifestName |
| : kManifestNameLegacy; |
| fw_index_ = ParseFirmwareManifestV2(fw_manifest_dir_path_.Append(file_name), |
| dlc_per_variant); |
| if (!fw_index_) { |
| auto err = Error::Create( |
| FROM_HERE, kErrorResultInitManifestFailure, |
| "Could not load firmware manifest directory (bad manifest?)"); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| return EX_UNAVAILABLE; |
| } |
| |
| if (!dlc_per_variant.empty()) { |
| LOG(INFO) << "Creating DLC manager"; |
| dlc_manager_ = std::make_unique<modemfwd::DlcManager>( |
| bus_, metrics_.get(), std::move(dlc_per_variant), variant_); |
| if (dlc_manager_->DlcId().empty()) { |
| LOG(ERROR) << "Unexpected empty DlcId value"; |
| auto err = Error::Create(FROM_HERE, error::kUnexpectedEmptyDlcId, |
| "Unexpected empty DlcId value"); |
| metrics_->SendDlcInstallResultFailure(err.get()); |
| } else { |
| InstallModemDlcOnceCallback cb = base::BindOnce( |
| &Daemon::InstallDlcCompleted, weak_ptr_factory_.GetWeakPtr()); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DlcManager::InstallModemDlc, |
| base::Unretained(dlc_manager_.get()), std::move(cb))); |
| return EX_OK; |
| } |
| } |
| metrics_->SendFwUpdateLocation(metrics::FwUpdateLocation::kRootFS); |
| CompleteInitialization(); |
| return EX_OK; |
| } |
| |
| void Daemon::InstallDlcCompleted(const std::string& mount_path, |
| const brillo::Error* error) { |
| if (error || mount_path.empty()) { |
| LOG(INFO) << "Failed to install DLC. Falling back to rootfs"; |
| metrics_->SendFwUpdateLocation( |
| metrics::FwUpdateLocation::kFallbackToRootFS); |
| CompleteInitialization(); |
| return; |
| } |
| |
| if (dlc_manager_->IsDlcEmpty()) { |
| LOG(INFO) << "Ignoring DLC contents, loading FW from rootfs"; |
| metrics_->SendFwUpdateLocation(metrics::FwUpdateLocation::kRootFS); |
| } else { |
| fw_manifest_directory_ = CreateFirmwareDirectory( |
| std::move(fw_index_), base::FilePath(mount_path), variant_); |
| metrics_->SendFwUpdateLocation(metrics::FwUpdateLocation::kDlc); |
| } |
| CompleteInitialization(); |
| } |
| |
| void Daemon::CompleteInitialization() { |
| if (!fw_manifest_directory_) { |
| fw_manifest_directory_ = CreateFirmwareDirectory( |
| std::move(fw_index_), fw_manifest_dir_path_, variant_); |
| } |
| DCHECK(fw_manifest_directory_); |
| |
| journal_ = OpenJournal(journal_file_path_, fw_manifest_directory_.get(), |
| helper_directory_.get()); |
| if (!journal_) { |
| auto err = Error::Create(FROM_HERE, kErrorResultInitJournalFailure, |
| "Could not open journal file"); |
| notification_mgr_->NotifyUpdateFirmwareCompletedFailure(err.get()); |
| QuitWithExitCode(EX_UNAVAILABLE); |
| } |
| |
| modem_flasher_ = CreateModemFlasher(fw_manifest_directory_.get(), |
| modems_seen_since_oobe_prefs_.get()); |
| |
| modem_tracker_ = std::make_unique<modemfwd::ModemTracker>( |
| bus_, |
| base::BindRepeating(&Daemon::OnModemCarrierIdReady, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&Daemon::OnModemDeviceSeen, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&Daemon::OnModemStateChange, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&Daemon::OnModemPowerStateChange, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| if (dlc_manager_) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&DlcManager::RemoveUnecessaryModemDlcs, |
| base::Unretained(dlc_manager_.get())), |
| kDlcRemovalDelay); |
| } |
| |
| // Check if we have any qcom soc based modems that require a flash before they |
| // boot. |
| const char kSocInternalDeviceId[] = "soc:*:* (Internal)"; |
| if (helper_directory_->GetHelperForDeviceId(kSocInternalDeviceId)) { |
| ForceFlash(kSocInternalDeviceId); |
| } else { |
| helper_directory_->ForEachHelper(base::BindRepeating( |
| &Daemon::ForceFlashIfInFlashMode, base::Unretained(this))); |
| } |
| |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&Daemon::CheckForWedgedModems, |
| weak_ptr_factory_.GetWeakPtr()), |
| GetModemWedgeCheckDelay()); |
| } |
| |
| void Daemon::RegisterOnModemReappearanceCallback( |
| const std::string& equipment_id, base::OnceClosure callback) { |
| modem_reappear_callbacks_[equipment_id] = std::move(callback); |
| } |
| |
| void Daemon::RunModemReappearanceCallback(const std::string& equipment_id) { |
| if (modem_reappear_callbacks_.count(equipment_id) > 0) { |
| std::move(modem_reappear_callbacks_[equipment_id]).Run(); |
| modem_reappear_callbacks_.erase(equipment_id); |
| } |
| } |
| |
| void Daemon::OnModemStateChange(const std::string device_id, |
| Modem::State new_state) { |
| if (modems_.count(device_id) == 0) { |
| return; |
| } |
| EVLOG(1) << __func__ << ": update modem with device id: " << device_id |
| << " to new modem state: " << new_state; |
| // Do not update heartbeat config when: |
| // 1. update to new modem state is not successful (no state change); |
| // 2. current power state is LOW, keep heartbeat stopped. |
| if (!modems_[device_id]->UpdateState(new_state) || |
| modems_[device_id]->GetPowerState() == Modem::PowerState::LOW) { |
| return; |
| } |
| StopHeartbeatTask(device_id); |
| // Apply new heartbeat check rate based on the new modem state |
| StartHeartbeatTask(device_id); |
| } |
| |
| void Daemon::OnModemPowerStateChange(const std::string device_id, |
| Modem::PowerState new_power_state) { |
| if (modems_.count(device_id) == 0) { |
| return; |
| } |
| EVLOG(1) << __func__ << ": update modem with device id: " << device_id |
| << " to new power state: " << new_power_state; |
| if (!modems_[device_id]->UpdatePowerState(new_power_state)) { |
| return; |
| } |
| |
| if (new_power_state == Modem::PowerState::LOW) { |
| StopHeartbeatTask(device_id); |
| } else { |
| StartHeartbeatTask(device_id); |
| } |
| // TODO(b/341753271): restart the task when there is a request to exit power |
| // LOW state. In this case, there is no power state change yet. Current power |
| // state is still LOW |
| } |
| |
| void Daemon::OnModemDeviceSeen(std::string device_id, |
| std::string equipment_id) { |
| ELOG(INFO) << "Modem seen with equipment ID \"" << equipment_id << "\"" |
| << " and device ID [" << device_id << "]"; |
| // Record that we've seen this modem so we don't reboot/auto-force-flash it. |
| device_ids_seen_.insert(device_id); |
| |
| // The modem that matches the variant has been seen. |
| if (fw_manifest_directory_->DeviceIdMatch(device_id) && |
| !modems_seen_since_oobe_prefs_->Exists(device_id)) { |
| if (!modems_seen_since_oobe_prefs_->Create(device_id)) |
| LOG(ERROR) << "Failed to create modem seen pref for modem: " << device_id; |
| } |
| |
| RunModemReappearanceCallback(equipment_id); |
| } |
| |
| void Daemon::OnModemCarrierIdReady( |
| std::unique_ptr<org::chromium::flimflam::DeviceProxyInterface> device) { |
| auto modem = |
| CreateModem(bus_.get(), std::move(device), helper_directory_.get()); |
| if (!modem) |
| return; |
| |
| std::string device_id = modem->GetDeviceId(); |
| std::string equipment_id = modem->GetEquipmentId(); |
| |
| // Store the modem object in case our flash gets delayed. |
| modems_[device_id] = std::move(modem); |
| SetupHeartbeatTask(device_id); |
| |
| ELOG(INFO) << "Modem with equipment ID \"" << equipment_id << "\"" |
| << " and device ID [" << device_id << "] ready to flash"; |
| |
| if (prefs_->Exists(kDisableAutoUpdateKey) && |
| prefs_->KeyValueMatches(kDisableAutoUpdateKey, "1")) { |
| LOG(INFO) << "Update disabled by pref"; |
| notification_mgr_->NotifyUpdateFirmwareCompletedSuccess(false, 0); |
| return; |
| } |
| |
| suspend_checker_->RunWhenNotSuspending( |
| base::BindOnce(&Daemon::DoFlash, weak_ptr_factory_.GetWeakPtr(), |
| device_id, equipment_id)); |
| } |
| |
| void Daemon::DoFlash(const std::string& device_id, |
| const std::string& equipment_id) { |
| StopHeartbeatTask(device_id); |
| brillo::ErrorPtr err; |
| auto flash_task = |
| std::make_unique<FlashTask>(this, journal_.get(), notification_mgr_.get(), |
| metrics_.get(), modem_flasher_.get()); |
| if (!flash_task->Start(modems_[device_id].get(), FlashTask::Options{}, |
| &err)) { |
| LOG(ERROR) << "Flashing errored out: " |
| << (err ? err->GetMessage() : "unknown"); |
| return; |
| } |
| |
| flash_tasks_.push_back(std::move(flash_task)); |
| StartHeartbeatTask(device_id); |
| } |
| |
| void Daemon::RegisterDBusObjectsAsync( |
| brillo::dbus_utils::AsyncEventSequencer* sequencer) { |
| dbus_adaptor_.reset(new DBusAdaptor(bus_, this)); |
| dbus_adaptor_->RegisterAsync( |
| sequencer->GetHandler("RegisterAsync() failed", true)); |
| } |
| |
| bool Daemon::ForceFlash(const std::string& device_id) { |
| auto stub_modem = CreateStubModem(device_id, helper_directory_.get(), false); |
| if (!stub_modem) |
| return false; |
| |
| ELOG(INFO) << "Force-flashing modem with device ID [" << device_id << "]"; |
| StopHeartbeatTask(device_id); |
| brillo::ErrorPtr err; |
| auto flash_task = |
| std::make_unique<FlashTask>(this, journal_.get(), notification_mgr_.get(), |
| metrics_.get(), modem_flasher_.get()); |
| if (!flash_task->Start(stub_modem.get(), |
| FlashTask::Options{.should_always_flash = true}, |
| &err)) { |
| LOG(ERROR) << "Force-flashing errored out: " |
| << (err ? err->GetMessage() : "unknown"); |
| return false; |
| } |
| flash_tasks_.push_back(std::move(flash_task)); |
| StartHeartbeatTask(device_id); |
| |
| // We don't know the real equipment ID of this modem, and if we're |
| // force-flashing then we probably already have a problem with the modem |
| // coming up, so cleaning up at this point is not a problem. Run the |
| // callback now if we got one. |
| RunModemReappearanceCallback(stub_modem->GetEquipmentId()); |
| return true; |
| } |
| |
| bool Daemon::ForceFlashForTesting(const std::string& device_id, |
| const std::string& carrier_uuid, |
| const std::string& variant, |
| bool use_modems_fw_info) { |
| // Just drop the request if we're suspending. Users can manually retry the |
| // force-flash after the device has resumed. |
| if (suspend_checker_->IsSuspendAnnounced()) |
| return false; |
| |
| auto stub_modem = |
| CreateStubModem(device_id, helper_directory_.get(), use_modems_fw_info); |
| if (!stub_modem) |
| return false; |
| |
| ELOG(INFO) << "Force-flashing modem with device ID [" << device_id << "], " |
| << "variant [" << variant << "], carrier_uuid [" << carrier_uuid |
| << "], use_modems_fw_info [" << use_modems_fw_info << "]"; |
| |
| fw_manifest_directory_->OverrideVariantForTesting(variant); |
| |
| StopHeartbeatTask(device_id); |
| brillo::ErrorPtr err; |
| auto flash_task = |
| std::make_unique<FlashTask>(this, journal_.get(), notification_mgr_.get(), |
| metrics_.get(), modem_flasher_.get()); |
| if (!flash_task->Start( |
| stub_modem.get(), |
| FlashTask::Options{.should_always_flash = true, |
| .carrier_override_uuid = carrier_uuid}, |
| &err)) { |
| LOG(ERROR) << "Force-flashing errored out: " |
| << (err ? err->GetMessage() : "unknown"); |
| return false; |
| } |
| |
| flash_tasks_.push_back(std::move(flash_task)); |
| StartHeartbeatTask(device_id); |
| |
| // We don't know the real equipment ID of this modem, and if we're |
| // force-flashing then we probably already have a problem with the modem |
| // coming up, so cleaning up at this point is not a problem. Run the |
| // callback now if we got one. |
| RunModemReappearanceCallback(stub_modem->GetEquipmentId()); |
| return true; |
| } |
| |
| bool Daemon::ResetModem(const std::string& device_id) { |
| auto helper = helper_directory_->GetHelperForDeviceId(device_id); |
| if (!helper) |
| return false; |
| |
| return helper->Reboot(); |
| } |
| |
| void Daemon::ForceFlashIfInFlashMode(const std::string& device_id, |
| ModemHelper* helper) { |
| EVLOG(1) << __func__ << "device_id: " << device_id; |
| if (!helper->FlashModeCheck()) { |
| return; |
| } |
| |
| metrics_->SendCheckForWedgedModemResult( |
| metrics::CheckForWedgedModemResult::kModemWedged); |
| LOG(INFO) << "Modem with device ID [" << device_id |
| << "] appears to be in flash mode, attempting recovery"; |
| ForceFlash(device_id); |
| } |
| |
| void Daemon::CheckForWedgedModems() { |
| EVLOG(1) << "Running wedged modems check..."; |
| |
| helper_directory_->ForEachHelper( |
| base::BindRepeating(&Daemon::ForceFlashIfWedged, base::Unretained(this))); |
| } |
| |
| void Daemon::ForceFlashIfWedged(const std::string& device_id, |
| ModemHelper* helper) { |
| if (device_ids_seen_.count(device_id) > 0) { |
| metrics_->SendCheckForWedgedModemResult( |
| metrics::CheckForWedgedModemResult::kModemPresent); |
| return; |
| } |
| |
| if (!helper->FlashModeCheck()) { |
| LOG(WARNING) << "Modem not found, trying to reset it..."; |
| if (helper->Reboot()) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&Daemon::ForceFlashIfNeverAppeared, |
| weak_ptr_factory_.GetWeakPtr(), device_id), |
| kRebootCheckDelay); |
| } else { |
| EVLOG(1) << "Couldn't reboot modem with device ID [" << device_id |
| << "], it may not be present"; |
| // |kFailedToRebootModem| will be sent only on devices with a modem |
| // firmware-variant, since devices without a modem will always fail to |
| // reboot the non existing modem and will pollute the metrics. |
| if (!variant_.empty()) { |
| metrics_->SendCheckForWedgedModemResult( |
| metrics::CheckForWedgedModemResult::kFailedToRebootModem); |
| } |
| } |
| return; |
| } |
| |
| metrics_->SendCheckForWedgedModemResult( |
| metrics::CheckForWedgedModemResult::kModemWedged); |
| LOG(INFO) << "Modem with device ID [" << device_id |
| << "] appears to be wedged, attempting recovery"; |
| ForceFlash(device_id); |
| } |
| |
| void Daemon::ForceFlashIfNeverAppeared(const std::string& device_id) { |
| if (device_ids_seen_.count(device_id) > 0) { |
| metrics_->SendCheckForWedgedModemResult( |
| metrics::CheckForWedgedModemResult::kModemPresentAfterReboot); |
| return; |
| } |
| |
| LOG(INFO) << "Modem with device ID [" << device_id |
| << "] did not appear after reboot, attempting recovery"; |
| metrics_->SendCheckForWedgedModemResult( |
| metrics::CheckForWedgedModemResult::kModemAbsentAfterReboot); |
| ForceFlash(device_id); |
| } |
| |
| void Daemon::SetupHeartbeatTask(const std::string& device_id) { |
| heartbeat_tasks_.erase(device_id); |
| |
| if (!modems_[device_id]->SupportsHealthCheck()) |
| return; |
| |
| // This modem has a port we can run health checks on. See if we need to |
| // set it up. |
| auto helper = helper_directory_->GetHelperForDeviceId(device_id); |
| if (!helper) |
| return; |
| |
| auto heartbeat_config = helper->GetHeartbeatConfig(); |
| if (!heartbeat_config.has_value()) |
| return; |
| |
| // We support heartbeat checks on this modem. Create a task to do these |
| // on a recurring basis. |
| auto heartbeat_task = std::make_unique<HeartbeatTask>( |
| this, modems_[device_id].get(), metrics_.get(), *heartbeat_config); |
| heartbeat_tasks_[device_id] = std::move(heartbeat_task); |
| } |
| |
| void Daemon::StartHeartbeatTask(const std::string& device_id) { |
| auto it = heartbeat_tasks_.find(device_id); |
| if (it == heartbeat_tasks_.end()) |
| return; |
| |
| it->second->Start(); |
| } |
| |
| void Daemon::StopHeartbeatTask(const std::string& device_id) { |
| auto it = heartbeat_tasks_.find(device_id); |
| if (it == heartbeat_tasks_.end()) |
| return; |
| |
| it->second->Stop(); |
| } |
| |
| } // namespace modemfwd |