| // 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_ |