blob: c6bd7f0419850b4f1adbf8ccc171a12bb4bf0738 [file]
// Copyright 2026 The Fuchsia 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 <lib/async/cpp/task.h>
#include <lib/driver/component/cpp/health_alert_latch.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/zx/clock.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <optional>
#include <utility>
namespace fdf {
namespace {
fuchsia_driver_framework::HealthStatus NormalizeFaultStatus(
std::optional<fuchsia_driver_framework::HealthStatus> status) {
// An omitted status counts as kUnhealthy (see HealthCondition in health.fidl).
if (status == fuchsia_driver_framework::HealthStatus::kOk ||
status == fuchsia_driver_framework::HealthStatus::kDegraded) {
return *status;
}
return fuchsia_driver_framework::HealthStatus::kUnhealthy;
}
} // namespace
std::vector<fuchsia_driver_framework::HealthCondition>::iterator
HealthAlertLatch::State::FindCondition(std::string_view code) {
return std::find_if(conditions.begin(), conditions.end(),
[code](const fuchsia_driver_framework::HealthCondition& cond) {
return cond.reason_code().has_value() && *cond.reason_code() == code;
});
}
void HealthAlertLatch::State::PostSelfFaultLocked(const std::shared_ptr<State>& self) const {
if (driver == nullptr || dispatcher == nullptr) {
return;
}
fuchsia_driver_framework::HealthCheckResponse response;
if (!conditions.empty()) {
response.conditions(conditions);
}
async::PostTask(dispatcher, [self, response = std::move(response)]() mutable {
DriverBase2* current_driver = nullptr;
{
std::lock_guard lock(self->mutex);
current_driver = self->driver;
}
// 1) Unlocking before ReportHealthAlert avoids stalling IRQ/worker threads calling
// Raise()/Clear() across FIDL channel writes and Inspect VMO updates.
// 2) `current_driver` cannot be destroyed after releasing `mutex` while this task runs because
// DriverBase2 (and its member HealthAlertLatch) must be destroyed on this same
// synchronized driver_dispatcher, which serializes all tasks.
if (current_driver != nullptr) {
current_driver->ReportHealthAlert(
fuchsia_driver_framework::HealthAlert::WithSelfFault(std::move(response)));
}
});
}
void HealthAlertLatch::State::PostWarningLocked(const std::shared_ptr<State>& self,
std::string message) const {
if (driver == nullptr || dispatcher == nullptr) {
return;
}
async::PostTask(dispatcher, [self, message = std::move(message)]() {
DriverBase2* current_driver = nullptr;
{
std::lock_guard lock(self->mutex);
current_driver = self->driver;
}
if (current_driver != nullptr && current_driver->has_logger()) {
FDF_LOGL(WARNING, current_driver->logger(), "%s", message.c_str());
}
});
}
HealthAlertLatch::HealthAlertLatch(DriverBase2& driver)
: state_(std::make_shared<State>(&driver)) {}
HealthAlertLatch::~HealthAlertLatch() {
std::lock_guard lock(state_->mutex);
state_->driver = nullptr;
}
void HealthAlertLatch::Raise(fuchsia_driver_framework::HealthCondition condition) {
const zx::time_boot now = zx::clock::get_boot();
const auto normalized_status = NormalizeFaultStatus(condition.status());
std::string code = condition.reason_code().has_value() && !condition.reason_code()->empty()
? *condition.reason_code()
: "UNKNOWN";
if (normalized_status == fuchsia_driver_framework::HealthStatus::kOk) {
Clear(code);
return;
}
std::lock_guard lock(state_->mutex);
auto it = state_->FindCondition(code);
if (it != state_->conditions.end()) {
auto& existing = *it;
const auto prev_status = NormalizeFaultStatus(existing.status());
const zx::time_boot first = existing.first_observed().value_or(now);
const zx::time_boot last = condition.last_observed().value_or(now);
const uint64_t supplied_count = condition.occurrence_count().value_or(0);
const uint64_t existing_count = existing.occurrence_count().value_or(1);
const uint64_t incremented_count =
existing_count < std::numeric_limits<uint64_t>::max() ? existing_count + 1 : existing_count;
const uint64_t next_count = std::max(incremented_count, supplied_count);
existing.status(normalized_status);
existing.first_observed(first);
existing.last_observed(last);
existing.occurrence_count(next_count);
if (condition.message().has_value()) {
existing.message(*std::move(condition.message()));
}
if (condition.diagnostics().has_value()) {
existing.diagnostics(*std::move(condition.diagnostics()));
}
if (normalized_status != prev_status) {
state_->PostSelfFaultLocked(state_);
}
return;
}
if (state_->conditions.size() >= kMaxConditions) {
if (normalized_status == fuchsia_driver_framework::HealthStatus::kUnhealthy) {
auto degraded_it =
std::find_if(state_->conditions.begin(), state_->conditions.end(),
[](const fuchsia_driver_framework::HealthCondition& entry) {
return entry.status() == fuchsia_driver_framework::HealthStatus::kDegraded;
});
if (degraded_it != state_->conditions.end()) {
std::string evicted_code = degraded_it->reason_code().value_or("UNKNOWN");
state_->conditions.erase(degraded_it);
state_->PostWarningLocked(
state_, "HealthAlertLatch: evicting DEGRADED condition '" + evicted_code +
"' to make room for UNHEALTHY condition '" + code + "' (kMaxConditions)");
}
}
if (state_->conditions.size() >= kMaxConditions) {
state_->PostWarningLocked(state_, "HealthAlertLatch: dropping condition '" + code +
"': kMaxConditions active conditions already");
return;
}
}
condition.status(normalized_status);
condition.reason_code(std::move(code));
if (!condition.first_observed().has_value()) {
condition.first_observed(now);
}
if (!condition.last_observed().has_value()) {
condition.last_observed(now);
}
if (!condition.occurrence_count().has_value() || *condition.occurrence_count() == 0) {
condition.occurrence_count(1);
}
state_->conditions.push_back(std::move(condition));
state_->PostSelfFaultLocked(state_);
}
void HealthAlertLatch::RaiseDegraded(std::string_view reason_code, std::string_view message) {
fuchsia_driver_framework::HealthCondition condition;
condition.status(fuchsia_driver_framework::HealthStatus::kDegraded);
condition.reason_code(std::string(reason_code));
if (!message.empty()) {
condition.message(std::string(message));
}
Raise(std::move(condition));
}
void HealthAlertLatch::RaiseUnhealthy(std::string_view reason_code, std::string_view message) {
fuchsia_driver_framework::HealthCondition condition;
condition.status(fuchsia_driver_framework::HealthStatus::kUnhealthy);
condition.reason_code(std::string(reason_code));
if (!message.empty()) {
condition.message(std::string(message));
}
Raise(std::move(condition));
}
void HealthAlertLatch::Clear(std::string_view reason_code) {
std::string_view normalized_code = reason_code.empty() ? "UNKNOWN" : reason_code;
std::lock_guard lock(state_->mutex);
auto it = state_->FindCondition(normalized_code);
if (it == state_->conditions.end()) {
return;
}
state_->conditions.erase(it);
state_->PostSelfFaultLocked(state_);
}
void HealthAlertLatch::ClearAll() {
std::lock_guard lock(state_->mutex);
if (state_->conditions.empty()) {
return;
}
state_->conditions.clear();
state_->PostSelfFaultLocked(state_);
}
std::vector<fuchsia_driver_framework::HealthCondition> HealthAlertLatch::Snapshot() const {
std::lock_guard lock(state_->mutex);
return state_->conditions;
}
fuchsia_driver_framework::HealthCheckResponse HealthAlertLatch::ToResponse() const {
std::lock_guard lock(state_->mutex);
fuchsia_driver_framework::HealthCheckResponse response;
if (!state_->conditions.empty()) {
response.conditions(state_->conditions);
}
return response;
}
void HealthAlertLatch::MergeInto(fuchsia_driver_framework::HealthCheckResponse& response) const {
std::lock_guard lock(state_->mutex);
if (state_->conditions.empty()) {
return;
}
if (!response.conditions().has_value()) {
response.conditions().emplace();
}
auto& out = *response.conditions();
const size_t merged_begin = out.size();
auto is_degraded = [](const fuchsia_driver_framework::HealthCondition& entry) {
return entry.status() == fuchsia_driver_framework::HealthStatus::kDegraded;
};
for (const auto& cond : state_->conditions) {
if (!cond.reason_code().has_value()) {
continue;
}
const std::string_view code = *cond.reason_code();
const bool already_present = std::any_of(
out.begin(), out.end(), [code](const fuchsia_driver_framework::HealthCondition& existing) {
return existing.reason_code().has_value() && *existing.reason_code() == code;
});
if (already_present) {
continue;
}
if (out.size() < kMaxConditions) {
out.push_back(cond);
continue;
}
if (NormalizeFaultStatus(cond.status()) != fuchsia_driver_framework::HealthStatus::kUnhealthy) {
continue;
}
const auto merged_first = out.begin() + static_cast<std::ptrdiff_t>(merged_begin);
auto degraded_it = std::find_if(merged_first, out.end(), is_degraded);
if (degraded_it == out.end()) {
degraded_it = std::find_if(out.begin(), merged_first, is_degraded);
if (degraded_it == merged_first) {
continue;
}
}
*degraded_it = cond;
}
}
fuchsia_driver_framework::HealthStatus HealthAlertLatch::status() const {
std::lock_guard lock(state_->mutex);
auto overall = fuchsia_driver_framework::HealthStatus::kOk;
for (const auto& cond : state_->conditions) {
const auto s = NormalizeFaultStatus(cond.status());
if (s == fuchsia_driver_framework::HealthStatus::kUnhealthy) {
return fuchsia_driver_framework::HealthStatus::kUnhealthy;
}
if (s == fuchsia_driver_framework::HealthStatus::kDegraded) {
overall = fuchsia_driver_framework::HealthStatus::kDegraded;
}
}
return overall;
}
} // namespace fdf