blob: b1b538578ee7271be1a8428a1599a15c16a1e81c [file] [log] [blame]
// 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_POWER_SUPPORT_H_
#define LIB_DRIVER_POWER_CPP_POWER_SUPPORT_H_
#include <fidl/fuchsia.power.broker/cpp/fidl.h>
#include <lib/driver/incoming/cpp/namespace.h>
#include <lib/driver/power/cpp/element-description-builder.h>
#include <lib/driver/power/cpp/types.h>
#include <lib/fidl/cpp/wire/internal/transport_channel.h>
#include <lib/fit/function.h>
#include <lib/inspect/cpp/vmo/types.h>
#include <lib/zx/event.h>
#include <lib/zx/handle.h>
#if FUCHSIA_API_LEVEL_AT_LEAST(HEAD)
/// Collection of helpers for driver authors working with the power framework.
/// The basic usage model is
/// * use `fuchsia.hardware.platform.device/Device.GetPowerConfiguration` to
/// retrieve the config supplied by the board driver.
/// * For each power element in the driver's config
/// - Call `PowerAdapter::GetDependencyTokens` to get the element's
/// parents' access tokens.
/// - Calling `PowerAdapter::AddElement` and supplying the configuration,
/// token set from `GetDependencyTokens` and any access tokens the
/// driver needs to declare.
namespace fdf_power {
enum class Error : uint8_t {
/// The power configuration appears to be invalid. A non-exhaustive list of
/// possible reasons is it contained no elements, the element definition
/// appears malformed, or other reasons.
INVALID_ARGS,
/// A general I/O error happened which we're not sure about. This should be
/// a rare occurrence and typically more specific errors should be returned.
IO,
/// The configuration has a dependency, but we couldn't get access to the
/// tokens for it. Maybe a parent didn't offer something expected or SAG
/// didn't make something available.
DEPENDENCY_NOT_FOUND,
/// No token services capability available, maybe it wasn't routed?
TOKEN_SERVICE_CAPABILITY_NOT_FOUND,
/// An unexpected error occurred listing service instances.
READ_INSTANCES,
/// We were able to access the token service capability, but no instances
/// were available. Did the parents offer any?
NO_TOKEN_SERVICE_INSTANCES,
/// Requesting a token from the provider protocol failed. Maybe the token
/// provider is not implemented correctly?
TOKEN_REQUEST,
/// Couldn't access the capability for System Activity Governor tokens.
ACTIVITY_GOVERNOR_UNAVAILABLE,
/// Request to System Activity Governor returned an error.
ACTIVITY_GOVERNOR_REQUEST,
/// fuchsia.power.broker/Topology could not be connected to.
TOPOLOGY_UNAVAILABLE,
/// The power configuration could not be retrieved.
CONFIGURATION_UNAVAILABLE,
/// Could not access the CpuElementManager capability.
CPU_ELEMENT_MANAGER_UNAVAILABLE,
/// There was an error making a request to the CpuElementManager protocol.
CPU_ELEMENT_MANAGER_REQUEST,
};
// Convenience methods that provide an approximate mapping to Zircon error values.
zx::error<zx_status_t> ErrorToZxError(Error e);
zx::error<zx_status_t> LeaseErrorToZxError(fuchsia_power_broker::LeaseError e);
zx::error<zx_status_t> AddElementErrorToZxError(fuchsia_power_broker::AddElementError e);
// Convenience methods for printing errors.
const char* ErrorToString(Error e);
const char* LeaseErrorToString(fuchsia_power_broker::LeaseError e);
const char* AddElementErrorToString(fuchsia_power_broker::AddElementError e);
inline fit::result<zx_status_t, uint8_t> default_level_changer(uint8_t level) {
return fit::ok(level);
}
/// ElementRunner implementation that only updates PowerBroker immediately.
///
/// This helper class can be used to create an ElementRunner server that has no side effects or
/// conditions when the power level of the given power element changes.
class BasicElementRunner : public fidl::Server<fuchsia_power_broker::ElementRunner> {
public:
void SetLevel(SetLevelRequest& request, SetLevelCompleter::Sync& completer) override;
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_power_broker::ElementRunner> md,
fidl::UnknownMethodCompleter::Sync& completer) override;
};
/// |LeaseHelper| wraps the collection of channels that represents a power
/// element. When used with |CreateLeaseHelper| the caller just supplies
/// the level of the required element(s) and dependency token(s). The caller
/// does not need to create an element it manages to get the dependencies
/// to the level it needs, instead it can use the |LeaseHelper| returned from
/// |CreateLeaseHelper|.
///
/// The |LeaseHelper| runs an element internally and exposes a convenient
/// interface, |AcquireLease| which leases the wrapped element at the "on"
/// state and consequentally drives the dependencies to their desired state.
class LeaseHelper {
public:
/// Creates a |LeaseHelper| running on the supplied |dispatcher|. The lease
/// is **not** active when the constructor returns, use |AcquireLease| for.
/// this. Blocking the dispatcher while waiting for the callback from
/// |AcquireLease| will result in a deadlock.
///
/// |error_callback| is called if |LeaseHelper| encounters an error running
/// its wrapped power element. If the error handler is invokes, the
/// |LeaseHelper| should be dropped and a new one created.
LeaseHelper(const std::string& lease_name,
fidl::ClientEnd<fuchsia_power_broker::ElementControl> element_control,
fidl::ClientEnd<fuchsia_power_broker::Lessor> lessor,
fidl::ServerEnd<fuchsia_power_broker::ElementRunner> element_runner_server,
async_dispatcher_t* dispatcher, fit::function<void()> error_callback,
inspect::Node* parent)
: dispatcher_(dispatcher),
element_control_(fidl::Client<fuchsia_power_broker::ElementControl>(
std::move(element_control), dispatcher_)),
lessor_(fidl::Client<fuchsia_power_broker::Lessor>(std::move(lessor), dispatcher_)),
runner_binding_(dispatcher_, std::move(element_runner_server), &runner_,
fidl::kIgnoreBindingClosure) {}
/// Trigger the lease acquisition. The lease is **created**, but not active,
/// when |callback| is invoked. The lease is active when
/// `fuchsia.power.broker/LeaseControl` channel given to |callback| reports
/// `fuchsia.power.broker/LeaseStatus::Satisfied` from
/// `fuchsia.power.broker/LeaseControl.WatchStatus()`.
///
/// Release the lease by closing the `LeaseControl` channel. |AcquireLease|
/// can be called more than once and creates a lease for each call.
void AcquireLease(
fit::function<void(fidl::Result<fuchsia_power_broker::Lessor::Lease>&)> callback);
private:
async_dispatcher_t* dispatcher_;
fidl::Client<fuchsia_power_broker::ElementControl> element_control_;
fidl::Client<fuchsia_power_broker::Lessor> lessor_;
BasicElementRunner runner_;
fidl::ServerBinding<fuchsia_power_broker::ElementRunner> runner_binding_;
};
class LeaseDependency {
public:
std::vector<fuchsia_power_broker::PowerLevel> levels_by_preference;
fuchsia_power_broker::DependencyToken token;
fuchsia_power_broker::DependencyType type;
};
/// Uses the provided namespace to add the power elements described in
/// |power_configs| to the power topology and returns corresponding
/// `ElementDesc` instances.
/// This function:
/// * Retrieves the tokens of any dependencies via
/// `fuchsia.hardware.power/PowerTokenProvider` instances
/// * Adds the power element via `fuchsia.power.broker/Topology`
///
/// In effect, this function converts the provided |power_configs| into their
/// corresponding `ElementDesc` objects and returns them.
fit::result<Error, std::vector<ElementDesc>> ApplyPowerConfiguration(
const fdf::Namespace& ns, cpp20::span<PowerElementConfiguration> power_configs,
bool use_element_runner = false);
/// Create a lease based on the set of dependencies represented by
/// |dependencies|. When the lease is fulfilled those dependencies will be at
/// the level specified. The lease is **not** active when this function returns,
/// use |LeaseHelper::AcquireLease| to trigger lease activation.
///
/// The |dispatcher| passed in is used to run a power element, so blocking
/// the dispatcher while acquiring a lease with |LeaseHelper::AcquireLease|
/// will result in a deadlock.
///
/// |error_callback| is **not** invoked if |LeaseHelper| creation fails,
/// instead it is called if the running the internal power element encounters
/// an error. If this error occurs, future lease acquisitions will likely fail
/// and the |LeaseHelper| should be replaced with a new instance.
///
/// RETURN VALUES
/// On error returns a tuple representing whether it was a FIDL error or a
/// protocol error. If the |fidl::Status| is not ZX_OK, this was a FIDL error,
/// and the second member of the tuple will be `nullopt`. Otherwise, this is a
/// protocol error from adding the power element that the direct lease wraps
/// and will be an error value from `fuchsia.power.broker/Topology.AddElement`.
/// On success returns a |LeaseHelper|.
fit::result<std::tuple<fidl::Status, std::optional<fuchsia_power_broker::AddElementError>>,
std::unique_ptr<LeaseHelper>>
CreateLeaseHelper(const fidl::ClientEnd<fuchsia_power_broker::Topology>& topology,
std::vector<LeaseDependency> dependencies, std::string lease_name,
async_dispatcher_t* dispatcher, fit::function<void()> error_callback,
inspect::Node* parent = nullptr);
/// Given a `PowerElementConfiguration` from driver framework, convert this
/// into a set of Power Broker's `LevelDependency` objects. The map is keyed
/// by the name of the parent/dependency.
///
/// If the `PowerElementConfiguration` expresses no dependencies, we return an
/// empty map.
///
/// NOTE: The `requires_token` of each of the `LevelDependency` objects is
/// **not** populated and must be filled in before providing this map to
/// `AddElement`.
///
/// Error returns:
/// - Error::INVALID_ARGS if `element_config` is missing fields, for example
/// if a level dependency doesn't have a parent level.
fit::result<Error, ElementDependencyMap> LevelDependencyFromConfig(
const PowerElementConfiguration& element_config);
/// Given a `PowerElementConfiguration` from driver framework, convert this
/// into a set of Power Broker's `PowerLevel` objects.
///
/// If the `PowerElementConfiguration` expresses no levels, we return an
/// empty vector.
std::vector<fuchsia_power_broker::PowerLevel> PowerLevelsFromConfig(
PowerElementConfiguration element_config);
/// For the Power Element represented by `element_config`, get the tokens for
/// the element's dependencies (ie. "parents") from
/// `fuchsia.hardware.power/PowerTokenProvider` instances in `ns`.
///
/// If the power element represented by `element_config` has no dependencies,
/// this function returns an empty set. If any dependency's token can not be
/// be retrieved we return an error.
/// Error returns:
/// - `Error::INVALID_ARGS` if the element_config appears invalid
/// - `Error::IO` if there is a communication failure when talking to a
/// service or a protocol required to get a token.
/// - `Error::DEPENDENCY_NOT_FOUND` if a token for a required dependency is
/// not available.
fit::result<Error, TokenMap> GetDependencyTokens(const fdf::Namespace& ns,
const PowerElementConfiguration& element_config);
/// For the Power Element represented by `element_config`, get the tokens for
/// the
/// element's dependencies (ie. "parents") from
/// `fuchsia.hardware.power/PowerTokenProvider` instances in `svcs_dir`.
/// `svcs_dir` should contain an entry for
/// `fuchsia.hardware.power/PowerTokenService`.
///
/// Returns a set of tokens from services instances found in `svcs_dir`. If
/// the power element represented by `element_config` has no dependencies, this
/// function returns an empty set. If any dependency's token can not be
/// be retrieved we return an error.
/// Error returns:
/// - `Error::INVALID_ARGS` if the element_config appears invalid
/// - `Error::IO` if there is a communication failure when talking to a
/// service or a protocol required to get a token.
/// - `Error::DEPENDENCY_NOT_FOUND` if a token for a required dependency is
/// not available.
fit::result<Error, TokenMap> GetDependencyTokens(const PowerElementConfiguration& element_config,
fidl::ClientEnd<fuchsia_io::Directory> svcs_dir);
/// Call `AddElement` on the `power_broker` channel passed in.
/// This function uses the `config` and `tokens` arguments to properly construct
/// the call to `fuchsia.power.broker/Topology.AddElement`. Optionally callers
/// can pass in tokens to be registered for granting assertive and opportunistic
/// dependency access on the created element.
///
/// Error
/// - Error::DEPENDENCY_NOT_FOUND if there is a dependency specified by
/// `config` which is to found in `tokens`.
/// - Error::INVALID_ARGS if `config` appears to be invalid, we fail to
/// duplicate a token and therefore assume it must have been invalid, or
/// the call to power broker fails for any reason *other* than a closed
/// channel.
fit::result<Error> AddElement(
const fidl::ClientEnd<fuchsia_power_broker::Topology>& power_broker,
const PowerElementConfiguration& config, TokenMap tokens,
const zx::unowned_event& assertive_token, const zx::unowned_event& opportunistic_token,
std::optional<fidl::ServerEnd<fuchsia_power_broker::Lessor>> lessor,
std::optional<fidl::ServerEnd<fuchsia_power_broker::ElementControl>> element_control,
std::optional<fidl::UnownedClientEnd<fuchsia_power_broker::ElementControl>>
element_control_client,
std::optional<fidl::ClientEnd<fuchsia_power_broker::ElementRunner>> element_runner);
/// Call `AddElement` on the `power_broker` channel passed in.
/// This function uses `ElementDescription` passed in to make the proper call
/// to `fuchsia.power.broker/Topology.AddElement`. See `ElementDescription` for
/// more information about what fields are inputs to `AddElement`.
///
/// Error
/// - Error::DEPENDENCY_NOT_FOUND if there is a dependency specified by
/// `config` which is to found in `tokens`.
/// - Error::INVALID_ARGS if `config` appears to be invalid, we fail to
/// duplicate a token and therefore assume it must have been invalid, or
/// the call to power broker fails for any reason *other* than a closed
/// channel.
fit::result<Error> AddElement(fidl::ClientEnd<fuchsia_power_broker::Topology>& power_broker,
ElementDesc& description);
} // namespace fdf_power
#endif
#endif // LIB_DRIVER_POWER_CPP_POWER_SUPPORT_H_