blob: 4f06d1a3a6068fefc8a90a02583fb15817b6e40b [file] [log] [blame]
// Copyright 2020 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 "diagnostics/cros_healthd/routines/nvme_wear_level/nvme_wear_level.h"
#include <utility>
#include <vector>
#include <base/base64.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/string_split.h>
#include "diagnostics/common/mojo_utils.h"
namespace diagnostics {
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
constexpr char NvmeWearLevelRoutine::kNvmeWearLevelRoutineThresholdError[] =
"Wear-level status: ERROR, threshold in percentage should be under 100.";
constexpr char NvmeWearLevelRoutine::kNvmeWearLevelRoutineGetInfoError[] =
"Wear-level status: ERROR, cannot get wear level info.";
constexpr char NvmeWearLevelRoutine::kNvmeWearLevelRoutineFailed[] =
"Wear-level status: FAILED, exceed the limitation value.";
constexpr char NvmeWearLevelRoutine::kNvmeWearLevelRoutineSuccess[] =
"Wear-level status: PASS.";
// Page ID 202 is Dell specific for NVMe wear level status.
constexpr uint32_t NvmeWearLevelRoutine::kNvmeLogPageId = 202;
constexpr uint32_t NvmeWearLevelRoutine::kNvmeLogDataLength = 16;
constexpr bool NvmeWearLevelRoutine::kNvmeLogRawBinary = true;
NvmeWearLevelRoutine::NvmeWearLevelRoutine(DebugdAdapter* debugd_adapter,
uint32_t wear_level_threshold)
: debugd_adapter_(debugd_adapter),
wear_level_threshold_(wear_level_threshold) {
DCHECK(debugd_adapter_);
}
NvmeWearLevelRoutine::~NvmeWearLevelRoutine() = default;
void NvmeWearLevelRoutine::Start() {
if (wear_level_threshold_ >= 100) {
LOG(ERROR) << "Invalid threshold value (valid: 0-99): "
<< wear_level_threshold_;
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, kNvmeWearLevelRoutineThresholdError);
return;
}
status_ = mojo_ipc::DiagnosticRoutineStatusEnum::kRunning;
auto result_callback =
base::Bind(&NvmeWearLevelRoutine::OnDebugdResultCallback,
weak_ptr_routine_.GetWeakPtr());
debugd_adapter_->GetNvmeLog(/*page_id=*/kNvmeLogPageId,
/*length=*/kNvmeLogDataLength,
/*raw_binary=*/kNvmeLogRawBinary,
result_callback);
}
// The wear-level check can only be started.
void NvmeWearLevelRoutine::Resume() {}
void NvmeWearLevelRoutine::Cancel() {}
void NvmeWearLevelRoutine::UpdateStatus(
chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum status,
uint32_t percent,
std::string msg) {
status_ = status;
percent_ = percent;
status_message_ = std::move(msg);
}
void NvmeWearLevelRoutine::PopulateStatusUpdate(
mojo_ipc::RoutineUpdate* response, bool include_output) {
mojo_ipc::NonInteractiveRoutineUpdate update;
update.status = status_;
update.status_message = status_message_;
response->routine_update_union->set_noninteractive_update(update.Clone());
response->progress_percent = percent_;
if (include_output && !output_dict_.DictEmpty()) {
// If routine status is not at completed/cancelled then prints the debugd
// raw data with output.
if (status_ != mojo_ipc::DiagnosticRoutineStatusEnum::kPassed &&
status_ != mojo_ipc::DiagnosticRoutineStatusEnum::kCancelled) {
std::string json;
base::JSONWriter::WriteWithOptions(
output_dict_, base::JSONWriter::Options::OPTIONS_PRETTY_PRINT, &json);
response->output =
CreateReadOnlySharedMemoryRegionMojoHandle(base::StringPiece(json));
}
}
}
mojo_ipc::DiagnosticRoutineStatusEnum NvmeWearLevelRoutine::GetStatus() {
return status_;
}
void NvmeWearLevelRoutine::OnDebugdResultCallback(const std::string& result,
brillo::Error* error) {
if (error) {
LOG(ERROR) << "Debugd error: " << error->GetMessage();
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, error->GetMessage());
return;
}
base::Value result_dict(base::Value::Type::DICTIONARY);
result_dict.SetStringKey("rawData", result);
output_dict_.SetKey("resultDetails", std::move(result_dict));
std::string decoded_output;
if (!base::Base64Decode(result, &decoded_output)) {
LOG(ERROR) << "Base64 decoding failed. Base64 data: " << result;
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, kNvmeWearLevelRoutineGetInfoError);
return;
}
if (decoded_output.length() != kNvmeLogDataLength) {
LOG(ERROR) << "String size is not as expected(" << kNvmeLogDataLength
<< "). Size: " << decoded_output.length();
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, kNvmeWearLevelRoutineGetInfoError);
return;
}
const uint32_t level = static_cast<uint32_t>(decoded_output[5]);
if (level >= wear_level_threshold_) {
LOG(INFO) << "Wear level status is higher than threshold. Level: " << level
<< ", threshold: " << wear_level_threshold_;
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kFailed,
/*percent=*/100, kNvmeWearLevelRoutineFailed);
return;
}
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kPassed,
/*percent=*/100, kNvmeWearLevelRoutineSuccess);
}
} // namespace diagnostics