blob: 81a7bf56b8f0c1ece88ae281f81ea4f2e45b106b [file] [log] [blame] [edit]
// Copyright 2024 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 "generic-suspend.h"
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fidl/fuchsia.kernel/cpp/wire.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/trace/event.h>
#include <zircon/errors.h>
#include <zircon/syscalls-next.h>
#include <zircon/time.h>
#include "fidl/fuchsia.hardware.power.suspend/cpp/markers.h"
#include "fidl/fuchsia.hardware.power.suspend/cpp/wire_types.h"
#include "fidl/fuchsia.kernel/cpp/markers.h"
#include "fidl/fuchsia.power.observability/cpp/fidl.h"
#include "fidl/fuchsia.power.observability/cpp/natural_types.h"
#include "lib/driver/component/cpp/prepare_stop_completer.h"
#include "lib/driver/incoming/cpp/namespace.h"
#include "lib/fidl/cpp/wire/arena.h"
#include "lib/fidl/cpp/wire/channel.h"
#include "lib/fidl/cpp/wire/vector_view.h"
#include "lib/fidl/cpp/wire/wire_messaging_declarations.h"
#include "zircon/status.h"
namespace suspend {
namespace fobs = fuchsia_power_observability;
namespace {
constexpr char kDeviceName[] = "generic-suspend-device";
constexpr uint64_t kInspectHistorySize = 128;
} // namespace
GenericSuspend::GenericSuspend(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher dispatcher)
: fdf::DriverBase("generic-suspend", std::move(start_args), std::move(dispatcher)),
inspect_events_(inspector().root().CreateChild(fobs::kSuspendEventsNode),
kInspectHistorySize),
devfs_connector_(fit::bind_member<&GenericSuspend::Serve>(this)) {}
zx::result<zx::resource> GenericSuspend::GetCpuResource() {
zx::result resource = incoming()->Connect<fuchsia_kernel::CpuResource>();
if (resource.is_error()) {
return resource.take_error();
}
fidl::WireResult result = fidl::WireCall(resource.value())->Get();
if (!result.ok()) {
return zx::error(result.status());
}
return zx::ok(std::move(result.value().resource));
}
zx::result<> GenericSuspend::CreateDevfsNode() {
fidl::Arena arena;
zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
FDF_LOG(ERROR, "Error creating devfs node");
return connector.take_error();
}
auto devfs = fuchsia_driver_framework::wire::DevfsAddArgs::Builder(arena).connector(
std::move(connector.value()));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, kDeviceName)
.devfs_args(devfs.Build())
.Build();
auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
zx::result node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>();
ZX_ASSERT_MSG(node_endpoints.is_ok(), "Failed to create endpoints: %s",
node_endpoints.status_string());
fidl::WireResult result = fidl::WireCall(node())->AddChild(
args, std::move(controller_endpoints.server), std::move(node_endpoints->server));
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child %s", result.status_string());
return zx::error(result.status());
}
controller_.Bind(std::move(controller_endpoints.client));
parent_.Bind(std::move(node_endpoints->client));
return zx::ok();
}
zx::result<> GenericSuspend::Start() {
fuchsia_hardware_power_suspend::SuspendService::InstanceHandler handler({
.suspender = suspend_bindings_.CreateHandler(this, dispatcher(), fidl::kIgnoreBindingClosure),
});
auto result =
outgoing()->AddService<fuchsia_hardware_power_suspend::SuspendService>(std::move(handler));
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add Suspender service %s", result.status_string());
return result.take_error();
}
AtStart();
zx::result resource = GetCpuResource();
if (!resource.is_ok()) {
FDF_LOG(ERROR, "Failed to get CPU Resource: %s", resource.status_string());
return resource.take_error();
}
cpu_resource_ = std::move(resource.value());
zx::result create_devfs_node_result = CreateDevfsNode();
if (create_devfs_node_result.is_error()) {
FDF_LOG(ERROR, "Failed to export to devfs %s", create_devfs_node_result.status_string());
return create_devfs_node_result.take_error();
}
FDF_LOG(INFO, "Started Generic Suspend Driver");
return zx::ok();
}
void GenericSuspend::Stop() {}
void GenericSuspend::PrepareStop(fdf::PrepareStopCompleter completer) { completer(zx::ok()); }
void GenericSuspend::GetSuspendStates(GetSuspendStatesCompleter::Sync& completer) {
fidl::Arena arena;
auto suspend_to_idle =
fuchsia_hardware_power_suspend::wire::SuspendState::Builder(arena).resume_latency(0).Build();
std::vector<fuchsia_hardware_power_suspend::wire::SuspendState> suspend_states = {
suspend_to_idle};
auto resp =
fuchsia_hardware_power_suspend::wire::SuspenderGetSuspendStatesResponse::Builder(arena)
.suspend_states(std::move(suspend_states))
.Build();
completer.ReplySuccess(resp);
}
zx::result<WakeSourceReport> GenericSuspend::SystemSuspendEnter() {
// LINT.IfChange
TRACE_DURATION("power", "generic-suspend:suspend");
// LINT.ThenChange(//src/performance/lib/trace_processing/metrics/suspend.py)
WakeSourceReport report;
const auto entry_count = sizeof(report.entries) / sizeof(report.entries[0]);
FDF_LOG(DEBUG, "entry_count requested: %lu", entry_count);
const zx_status_t status =
zx_system_suspend_enter(cpu_resource_.get(), ZX_TIME_INFINITE, /*options=*/0, &report.header,
report.entries, entry_count, &report.actual_entry_count);
FDF_LOG(DEBUG, "entry_count actual: %d", report.actual_entry_count);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(report);
}
void GenericSuspend::Suspend(SuspendRequestView request, SuspendCompleter::Sync& completer) {
fidl::Arena arena;
auto function_start = zx_clock_get_boot();
if (!request->has_state_index() || request->state_index() != 0) {
// This driver only supports one suspend state for now.
FDF_LOG(ERROR, "Invalid argument to suspend");
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
inspect_events_.CreateEntry([function_start](inspect::Node& n) {
n.RecordInt(fobs::kSuspendAttemptedAt, function_start);
});
FDF_LOG(INFO, "on suspend: current monotonic time %ld", zx_clock_get_monotonic());
auto suspend_start = zx_clock_get_boot();
zx::result<WakeSourceReport> result = SystemSuspendEnter();
auto suspend_return = zx_clock_get_boot();
FDF_LOG(INFO, "on resume: current monotonic time %ld", zx_clock_get_monotonic());
if (result.is_error()) {
auto error_value = result.error_value();
FDF_LOG(ERROR, "zx_system_suspend_enter failed: %s", zx_status_get_string(error_value));
inspect_events_.CreateEntry([suspend_return](inspect::Node& n) {
n.RecordInt(fobs::kSuspendFailedAt, suspend_return);
});
completer.ReplyError(error_value);
} else {
const auto& header = result.value().header;
inspect_events_.CreateEntry([suspend_return, &header](inspect::Node& n) {
n.RecordInt(fobs::kSuspendResumedAt, suspend_return);
n.RecordInt(fobs::kWakeReasonReportTime, header.report_time);
n.RecordInt(fobs::kWakeReasonWakeSourcesCount, header.total_wake_sources);
n.RecordInt(fobs::kWakeReasonWakeSourcesUnreportedCount,
header.unreported_wake_report_entries);
});
// Copy wake vector results into the response.
// TODO(b/343229277): Add support for soft_wake_vectors and other wake reports.
const auto& entries = result.value().entries;
auto reason_builder =
fuchsia_hardware_power_suspend::wire::WakeReason::Builder(arena).wake_vectors_type(
fuchsia_hardware_power_suspend::WakeVectorType::kKoid);
const auto entry_count = result.value().actual_entry_count;
const auto entry_count_clipped = std::min(entry_count, kMaxWakeSourceEntriesCount);
fidl::VectorView<uint64_t> koids(arena, entry_count_clipped);
for (uint32_t i = 0; i < entry_count_clipped; i++) {
koids[i] = entries[i].koid;
}
reason_builder.wake_vectors(koids).wake_vectors_overflow(entry_count_clipped < entry_count);
const auto reason = reason_builder.Build();
// Convert to a response to suspend data.
auto resp =
fuchsia_hardware_power_suspend::wire::SuspenderSuspendResponse::Builder(arena)
.suspend_duration(suspend_return - suspend_start)
.suspend_overhead(suspend_start - function_start + zx_clock_get_boot() - suspend_return)
.reason(reason)
.Build();
completer.ReplySuccess(resp);
}
}
void GenericSuspend::ForceLowestPowerMode(ForceLowestPowerModeRequestView request,
ForceLowestPowerModeCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void GenericSuspend::Serve(fidl::ServerEnd<fuchsia_hardware_power_suspend::Suspender> request) {
suspend_bindings_.AddBinding(dispatcher(), std::move(request), this, fidl::kIgnoreBindingClosure);
}
} // namespace suspend
// See driver-registration.cc for:
// FUCHSIA_DRIVER_EXPORT(suspend::GenericSuspend);