blob: 94074bb0f55cbafcf8d16aa73a83969529e66836 [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_self_test/nvme_self_test.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <base/base64.h>
#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/string_split.h>
#include <base/time/time.h>
#include "diagnostics/common/mojo_utils.h"
namespace diagnostics {
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
namespace {
mojo_ipc::DiagnosticRoutineStatusEnum CheckSelfTestPassed(uint8_t status) {
// Bits 3:0: 0x0 means pass without an error; otherwise the index of error.
return (status & 0xf) == 0 ? mojo_ipc::DiagnosticRoutineStatusEnum::kPassed
: mojo_ipc::DiagnosticRoutineStatusEnum::kFailed;
}
// Check the result and return corresponding complete message.
std::string GetCompleteMessage(uint8_t status) {
// Bits 3:0: indicates the result of the device self-test operation.
status &= 0xf;
// Check if result(complete value) is large than complete message array.
if (status >= NvmeSelfTestRoutine::kSelfTestRoutineCompleteLogSize) {
return NvmeSelfTestRoutine::kSelfTestRoutineCompleteUnknownStatus;
} else {
return NvmeSelfTestRoutine::kSelfTestRoutineCompleteLog[status];
}
}
} // namespace
constexpr char NvmeSelfTestRoutine::kNvmeSelfTestRoutineStarted[] =
"SelfTest status: self-test started.";
constexpr char NvmeSelfTestRoutine::kNvmeSelfTestRoutineStartError[] =
"SelfTest status: self-test failed to start.";
constexpr char NvmeSelfTestRoutine::kNvmeSelfTestRoutineAbortionError[] =
"SelfTest status: ERROR, self-test abortion failed.";
constexpr char NvmeSelfTestRoutine::kNvmeSelfTestRoutineRunning[] =
"SelfTest status: self-test is running.";
constexpr char NvmeSelfTestRoutine::kNvmeSelfTestRoutineGetProgressFailed[] =
"SelfTest status: ERROR, cannot get percent info.";
constexpr char NvmeSelfTestRoutine::kNvmeSelfTestRoutineCancelled[] =
"SelfTest status: self-test is cancelled.";
const char* const NvmeSelfTestRoutine::kSelfTestRoutineCompleteLog[] = {
"SelfTest status: Test PASS.",
"SelfTest status: Operation was aborted by Device Self-test command.",
"SelfTest status: Operation was aborted by a Controller Level Reset.",
"SelfTest status: Operation was aborted due to a removal of a namespace"
" from the namespace inventory.",
"SelfTest Status: Operation was aborted due to the processing of a Format"
" NVM command.",
"SelfTest status: A fatal error or unknown test error occurred while the"
" controller was executing the device self-test operation and the operation"
" did not complete.",
"SelfTest status: Operation completed with a segment that failed and the"
" segment that failed is not known.",
"SelfTest status: Operation completed with one or more failed segments and"
" the first segment that failed is indicated in the Segment Number field.",
"SelfTest status: Operation was aborted for an unknown reason.",
};
constexpr char NvmeSelfTestRoutine::kSelfTestRoutineCompleteUnknownStatus[] =
"SelfTest status: Unknown complete status.";
constexpr size_t NvmeSelfTestRoutine::kSelfTestRoutineCompleteLogSize =
base::size(NvmeSelfTestRoutine::kSelfTestRoutineCompleteLog);
// Page ID 6 if for self-test progress info.
constexpr uint32_t NvmeSelfTestRoutine::kNvmeLogPageId = 6;
constexpr uint32_t NvmeSelfTestRoutine::kNvmeLogDataLength = 16;
constexpr bool NvmeSelfTestRoutine::kNvmeLogRawBinary = true;
NvmeSelfTestRoutine::NvmeSelfTestRoutine(DebugdAdapter* debugd_adapter,
SelfTestType self_test_type)
: debugd_adapter_(debugd_adapter), self_test_type_(self_test_type) {
DCHECK(debugd_adapter_);
}
NvmeSelfTestRoutine::~NvmeSelfTestRoutine() = default;
void NvmeSelfTestRoutine::Start() {
status_ = mojo_ipc::DiagnosticRoutineStatusEnum::kRunning;
auto result_callback =
base::Bind(&NvmeSelfTestRoutine::OnDebugdNvmeSelfTestStartCallback,
weak_ptr_routine_.GetWeakPtr());
switch (self_test_type_) {
case kRunShortSelfTest:
debugd_adapter_->RunNvmeShortSelfTest(result_callback);
break;
case kRunLongSelfTest:
debugd_adapter_->RunNvmeLongSelfTest(result_callback);
break;
}
}
void NvmeSelfTestRoutine::Resume() {}
void NvmeSelfTestRoutine::Cancel() {
status_ = mojo_ipc::DiagnosticRoutineStatusEnum::kCancelling;
auto result_callback =
base::Bind(&NvmeSelfTestRoutine::OnDebugdNvmeSelfTestCancelCallback,
weak_ptr_routine_.GetWeakPtr());
debugd_adapter_->StopNvmeSelfTest(result_callback);
}
void NvmeSelfTestRoutine::PopulateStatusUpdate(
mojo_ipc::RoutineUpdate* response, bool include_output) {
// Request progress info if routine is running with the percentage below 100.
if (status_ == mojo_ipc::DiagnosticRoutineStatusEnum::kRunning &&
percent_ < 100) {
auto result_callback =
base::Bind(&NvmeSelfTestRoutine::OnDebugdResultCallback,
weak_ptr_routine_.GetWeakPtr());
debugd_adapter_->GetNvmeLog(/*page_id=*/kNvmeLogPageId,
/*length=*/kNvmeLogDataLength,
/*raw_binary=*/kNvmeLogRawBinary,
result_callback);
}
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 NvmeSelfTestRoutine::GetStatus() {
return status_;
}
bool NvmeSelfTestRoutine::CheckDebugdError(brillo::Error* error) {
if (error) {
LOG(ERROR) << "Debugd error: " << error->GetMessage();
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, error->GetMessage());
return true;
}
return false;
}
void NvmeSelfTestRoutine::OnDebugdNvmeSelfTestStartCallback(
const std::string& result, brillo::Error* error) {
if (CheckDebugdError(error))
return;
ResetOutputDictToValue(result);
// Checks whether self-test has already been launched.
if (!base::StartsWith(result, "Device self-test started",
base::CompareCase::SENSITIVE)) {
LOG(ERROR) << "self-test failed to start: " << result;
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, kNvmeSelfTestRoutineStartError);
return;
}
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kRunning, /*percent=*/0,
kNvmeSelfTestRoutineStarted);
}
void NvmeSelfTestRoutine::OnDebugdNvmeSelfTestCancelCallback(
const std::string& result, brillo::Error* error) {
if (CheckDebugdError(error))
return;
ResetOutputDictToValue(result);
// Checks abortion if successful
if (!base::StartsWith(result, "Aborting device self-test operation",
base::CompareCase::SENSITIVE)) {
LOG(ERROR) << "self-test abortion failed:" << result;
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, kNvmeSelfTestRoutineAbortionError);
return;
}
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kCancelled,
/*percent=*/100, kNvmeSelfTestRoutineCancelled);
}
bool NvmeSelfTestRoutine::CheckSelfTestCompleted(uint8_t progress,
uint8_t status) const {
// |progress|: Bits 7:4 are reserved; Bits 3:0 indicates the status of
// the current device self-test operation. |progress| is equal to 0 while
// self-test has been completed.
// |status|: Bits 7:4 indicates the type of operation: 0x1 for short-time
// self-test, 0x2 for long-time self-test; Bits 3:0 indicates the result of
// self-test.
return (progress & 0xf) == 0 && (status >> 4) == self_test_type_;
}
void NvmeSelfTestRoutine::OnDebugdResultCallback(const std::string& result,
brillo::Error* error) {
if (CheckDebugdError(error))
return;
ResetOutputDictToValue(result);
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, kNvmeSelfTestRoutineGetProgressFailed);
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, kNvmeSelfTestRoutineGetProgressFailed);
return;
}
const uint8_t progress = static_cast<uint8_t>(decoded_output[0]);
// Bits 6:0: percentage of self-test operation.
const uint8_t percent = static_cast<uint8_t>(decoded_output[1]) & 0x7f;
const uint8_t complete_status = static_cast<uint8_t>(decoded_output[4]);
if (CheckSelfTestCompleted(progress, complete_status)) {
UpdateStatus(CheckSelfTestPassed(complete_status), /*percent=*/100,
GetCompleteMessage(complete_status));
} else if ((progress & 0xf) == self_test_type_) {
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kRunning, percent,
kNvmeSelfTestRoutineRunning);
} else {
// It's not readable if uint8_t variables are not cast to uint32_t.
LOG(ERROR) << "No valid data is retrieved. progress: "
<< static_cast<uint32_t>(progress)
<< ", percent: " << static_cast<uint32_t>(percent)
<< ", status:" << static_cast<uint32_t>(complete_status);
UpdateStatus(mojo_ipc::DiagnosticRoutineStatusEnum::kError,
/*percent=*/100, kNvmeSelfTestRoutineGetProgressFailed);
}
}
void NvmeSelfTestRoutine::ResetOutputDictToValue(const std::string& value) {
base::Value result_dict(base::Value::Type::DICTIONARY);
result_dict.SetStringKey("rawData", value);
// TODO(crbug/1146080): Replace this with DictClear() once it's supported.
output_dict_.RemoveKey("resultDetails");
output_dict_.SetKey("resultDetails", std::move(result_dict));
}
void NvmeSelfTestRoutine::UpdateStatus(
chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum status,
uint32_t percent,
std::string msg) {
status_ = status;
percent_ = percent;
status_message_ = std::move(msg);
}
} // namespace diagnostics