blob: 881657b5f4d94ae51917b5b19b59681ff899a715 [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.
#ifndef LIB_DRIVER_POWER_CPP_WAKE_LEASE_H_
#define LIB_DRIVER_POWER_CPP_WAKE_LEASE_H_
#include <fidl/fuchsia.power.system/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/inspect/cpp/vmo/types.h>
#include <lib/zx/clock.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/result.h>
#include <zircon/errors.h>
#include <string>
#if FUCHSIA_API_LEVEL_AT_LEAST(HEAD)
namespace fdf_power {
// This is probably not the implementation you're looking for, consider using
// `WakeLeaseProvider`. `ManualWakeLease` may be appropriate if
// `WakeLeaseProvider` does not fit your needs.
//
// ManualWakeLease can be used to prevent the system from suspending. After a
// call to `Start()` returns, the system will keep running until after `End()`
// is called or the instance is dropped. Users can use the same instance to
// perform multiple atomic operations, for example by calling `Start()` after
// `End()`.
//
// If doing multiple atomic operations, a single `ManualWakeLease` can have
// performance advantages over using a WakeLease directly because the
// `ManualWakeLease` monitors system state over its lifetime, allowing it to
// avoid certain operations vs a series of shorter-lived `WakeLease` instances.
class ManualWakeLease : public fidl::WireServer<fuchsia_power_system::SuspendBlocker> {
public:
ManualWakeLease(async_dispatcher_t* dispatcher, std::string_view name,
fidl::ClientEnd<fuchsia_power_system::ActivityGovernor> sag,
inspect::Node* parent_node = nullptr, bool log = false);
// Start an atomic operation. If `ActivityGovernor` protocol connection is
// valid the system is guaranteed to stay running until `End()` is called or
// this instance is dropped.
//
// Returns `true` if one of the following is true:
// * It was not necessary to take a SAG wake lease at this time because the
// system is resumed
// * A SAG wake lease was acquired
// * A SAG wake lease was previously acquired
// Returns `false` if it was necessary to acquire a SAG wake lease, but
// an error occurred during acquisition. In this case, the `ManualWakeLease`
// instance should be replaced with a new one.
bool Start(bool ignore_system_state = false);
// Indicate that the operation is complete. The system may suspend after this
// call is made. Calling `End()` makes sense in the case the caller wants to
// use this instance to start a new atomic operation in the future. Returns
// the wake lease currently currently held, if any.
zx::result<zx::eventpair> End();
// Stores the SAG wake lease in the instance. The wake lease will be retained
// until `End` or `TakeWakeLease` is called.
void DepositWakeLease(zx::eventpair wake_lease);
// Consider whether End() is more appropriate for the use case.
//
// Returns ZX_ERR_BAD_HANDLE if we don't currently have a wake lease.
// IMPORTANT: This does not implicitly call `End()`, meaning that at the next
// system transition to suspend, this class will take a SAG wake lease and
// cause the suspend to abort.
zx::result<zx::eventpair> TakeWakeLease();
// Get a duplicate of the stored SAG wake lease. Returns ZX_ERR_BAD_HANDLE if
// we don't currently have a wake lease.
zx::result<zx::eventpair> GetWakeLeaseCopy();
// fuchsia.power.system/SuspendBlocker implementation. This is used to avoid
// creatingwake leases in cases where the system is resumed and `HandleInterrupt`
// is called.
void AfterResume(AfterResumeCompleter::Sync& completer) override;
void BeforeSuspend(BeforeSuspendCompleter::Sync& completer) override;
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_power_system::SuspendBlocker> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override;
void SetSuspended(bool suspended) { system_suspended_ = suspended; }
bool IsSuspended() { return system_suspended_; }
private:
void ResetSagClient();
bool AcquireLease(bool ignore_system_state = false);
std::string lease_name_;
bool log_;
fidl::WireSyncClient<fuchsia_power_system::ActivityGovernor> sag_client_;
std::optional<fidl::ServerBinding<fuchsia_power_system::SuspendBlocker>> suspend_blocker_binding_;
bool system_suspended_ = true;
bool active_ = false;
zx::eventpair lease_;
inspect::UintProperty total_lease_acquisitions_;
inspect::BoolProperty wake_lease_held_;
inspect::BoolProperty wake_lease_grabbable_;
inspect::UintProperty wake_lease_last_attempted_acquisition_timestamp_;
inspect::UintProperty wake_lease_last_acquired_timestamp_;
inspect::UintProperty wake_lease_last_refreshed_timestamp_;
};
// Wrapper around usage of fuchsia.power.system/ActivityGovernor.AcquireWakeLease. The wrapper
// reduces SAG wake lease creation by allowing callers to set timeout after which to drop the
// lease and provides mechanisms to extend that timeout.
class TimeoutWakeLease {
public:
// If |log| is set to true, logs will be emitted when acquiring leases and when lease times out.
// An invalid |sag_client| will result in silently disabling wake lease acquisition.
TimeoutWakeLease(async_dispatcher_t* dispatcher, std::string_view lease_name,
fidl::ClientEnd<fuchsia_power_system::ActivityGovernor> sag_client,
inspect::Node* parent_node = nullptr, bool log = false);
// Ensure that the system stays awake until the timeout is reached. To accomplish this we either:
// * obtain a SAG wake lease immediately if the system is currently suspended
// * obtain a SAG wake lease in the future if the system begins suspension before the timeout
// is reached
// If a SAG wake lease is acquired, it is dropped when the timeout is reached. The timeout may be
// extended by additional calls to `HandleInterrupt` or `AcquireWakeLease`. If the system is not
// suspended and does not attempt suspension during the timeout period, no wake lease is obtained.
//
// Ideally this is only called after we wake up due to an interrupt. Note that a duration is
// taken because the deadline is computed once the lease is acquired, rather than at the point
// this method is called.
//
// Returns `false` if it was necessary to obtain a SAG wake lease and it failed to do so. Returns
// `true` if it already had a wake lease or if it did not and it succeeded in obtaining one.
bool HandleInterrupt(zx::duration timeout);
// Acquire a SAG wake lease and automatically drop it after the specified timeout. If a lease was
// still already held, it will be extended until the new timeout. Note that a duration is taken
// because the deadline is computed once the SAG lease is acquired,rather than at the point this
// method is called.
//
// Returns `false` if it was necessary to obtain a SAG wake lease and it failed to do so. Returns
// `true` if it already had a wake lease or if it did not and it succeeded in obtaining one.
bool AcquireWakeLease(zx::duration timeout);
// Provide a SAG wake lease to the `TimeWakeLease` which will be dropped either:
// * immediately if there is already a SAG wake lease with a later deadline
// * at the specified deadline
// In the latter case any previous SAG lease held by this object is dropped immediately.
void DepositWakeLease(zx::eventpair wake_lease, zx::time timeout_deadline);
// Cancel timeout and take the SAG wake lease. Returns ZX_ERR_BAD_HANDLE if we don't currently
// have a wake lease.
zx::result<zx::eventpair> TakeWakeLease();
// Get a duplicate of the stored SAG wake lease. Returns ZX_ERR_BAD_HANDLE if we don't currently
// have a SAG wake lease.
zx::result<zx::eventpair> GetWakeLeaseCopy();
// Get the time our next timeout will occur. If there is no currently active
// timeout this will be ZX_TIME_INFINITE.
zx_time_t GetNextTimeout();
// Returns `true` if the instance thinks the system is currently resumed.
// This value may be wrong if the instance has not observed any system state
// changes since it was created.
bool IsResumed() { return !lease_.IsSuspended(); }
void SetSuspended(bool suspended) { lease_.SetSuspended(suspended); }
private:
void HandleTimeout();
void ResetTimeout(zx::duration timeout);
void ResetTimeout(zx::time time);
ManualWakeLease lease_;
async_dispatcher_t* dispatcher_;
bool log_;
std::string lease_name_;
async::TaskClosureMethod<TimeoutWakeLease, &TimeoutWakeLease::HandleTimeout> lease_task_{this};
};
// `WakeLease` wraps a WakeLease. When `WakeLease`
// destructs, the underlying SAG wake lease, if any currently held, is
// dropped.
class WakeLease {
public:
explicit WakeLease(const std::shared_ptr<ManualWakeLease>& lease) : lease_(lease) {}
zx::result<zx::eventpair> GetDuplicateLeaseHandle() { return lease_->GetWakeLeaseCopy(); }
// Intended for testing, gets a shared pointer to the wrapped
// fdf_power::TimeoutWakeLease object.
std::shared_ptr<ManualWakeLease> GetWakeLease() { return lease_; }
~WakeLease() { zx::result<zx::eventpair> obsolete_lease = lease_->End(); }
private:
std::shared_ptr<ManualWakeLease> lease_;
};
// This class is **not** threadsafe! `WakeLeaseProvider` and
// `WakeLease` pointers returned from `StartOperation` must be used
// on the same thread!
//
// Provides shared pointers to `WakeLeases` which are effectively
// fdf_power::WakeLease objects. This provides RAII semantics to manage the
// lifecycle of the underlying wake lease such that for a given
// `WakeLeaseProvider` there is _at_ _most_ one WakeLease in
// existence at any moment. It is worth noting that WakeLease only acquires a
// wake lease from the system if the system starts to suspend while the
// WakeLease is held.
//
// Using `WakeLeaseProvider` can have performance advantages over
// using a WakeLease directly because the provider monitors system state
// over its lifetime, allowing it to avoid certain operations vs a series of
// shorter-lived `WakeLease` instances which would each only observe system
// state while they exist.
//
// The main difference between the shared pointer to a `WakeLease`
// from `WakeLeaseProvider` and an `ManualWakeLease` is that with
// `WakeLease` we get long-lived system state monitoring and
// ergonomic RAII management of the actual lease. With `ManualWakeLease` you
// can get long-lived system state monitoring with `End` calls and sacrifice
// RAII ergonomics **or** get RAII ergonomics, but lose system state knowledge
// after the `ManualWakeLease` is destroyed.
class WakeLeaseProvider {
public:
// See comment in ManualWakeLease::Start
WakeLeaseProvider(async_dispatcher_t* dispatcher, std::string_view name,
fidl::ClientEnd<fuchsia_power_system::ActivityGovernor> sag,
bool ignore_system_state = false, inspect::Node* parent_node = nullptr,
bool log = false)
: fdf_lease_(
std::make_shared<ManualWakeLease>(dispatcher, name, std::move(sag), parent_node, log)),
ignore_system_state_(ignore_system_state) {}
std::shared_ptr<WakeLease> StartOperation() {
// WakeLeaseProvider works by holding and owning a
// fdf_power::WakeLease and creating, but not owning, a WakeLease.
// The provider vends pointers to the WakeLease, if it exists,
// otherwise creating it. When the WakeLease destructs, it drops the
// actual wake lease held by the fdf_power::WakeLease that it wraps.
// If we currently hold a WakeLease, return a pointer to it,
// otherwise create a new one with a reference to the wake lease we hold.
std::shared_ptr<WakeLease> op = atomic_op_.lock();
if (!op) {
// If we don't have a WakeLease, call HandleInterrupt on the
// WakeLease so that it is "active". Any previous WakeLease
// that destructed would have retrieved and dropped the actual wake lease
// from the WakeLease object.
fdf_lease_->Start(ignore_system_state_);
op = std::make_shared<WakeLease>(fdf_lease_);
atomic_op_ = op;
}
return op;
}
private:
std::weak_ptr<WakeLease> atomic_op_;
std::shared_ptr<ManualWakeLease> fdf_lease_;
const bool ignore_system_state_;
};
} // namespace fdf_power
#endif
#endif // LIB_DRIVER_POWER_CPP_WAKE_LEASE_H_