| // 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. |
| |
| #ifndef SRC_DEVICES_DRIVER_LAB_CPP_INCLUDE_LIB_DRIVER_LAB_DRIVER_LAB_H_ |
| #define SRC_DEVICES_DRIVER_LAB_CPP_INCLUDE_LIB_DRIVER_LAB_DRIVER_LAB_H_ |
| |
| #include <fidl/fuchsia.driver.lab/cpp/wire.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/driver_lab/driver_lab_c.h> |
| #include <lib/fit/function.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <initializer_list> |
| #include <memory> |
| #include <string_view> |
| |
| namespace driver_lab { |
| |
| // Half-open byte offset range `[start, end)` for hard-denied MMIO regions. |
| struct ByteRange { |
| uint64_t start; |
| uint64_t end; |
| }; |
| |
| // A 32-bit writable register whose host-writable bits are limited to |
| // `allow_mask`. |
| struct MaskedRegister { |
| uint64_t offset; |
| uint32_t allow_mask; |
| }; |
| |
| // Shared-memory VMO bank for exposing internal driver state/telemetry and |
| // host-tunable fault-injection knobs over `fuchsia.driver.lab` without custom |
| // FIDL changes (Spec Phase 2 Section 3.2). |
| // |
| // Also supports optional registration with the process-wide C singleton |
| // (`driver_lab_global_set_state_u32` / `driver_lab_global_get_knob_u32`) so |
| // legacy `.c` translation units can read knobs and write telemetry in one line |
| // of C without plumbing C++ pointers through vendor structs. |
| class StateVmoBank final { |
| public: |
| static constexpr size_t kDefaultSize = 4096; |
| |
| // Allocates a zero-initialized `zx::vmo` of `size` bytes, maps it read/write |
| // into the current process root VMAR, and optionally registers it as the |
| // process-wide global C state bank. |
| static zx::result<StateVmoBank> Create(size_t size = kDefaultSize, bool register_global = false); |
| |
| StateVmoBank() = default; |
| ~StateVmoBank(); |
| |
| StateVmoBank(StateVmoBank&& other) noexcept; |
| StateVmoBank& operator=(StateVmoBank&& other) noexcept; |
| |
| StateVmoBank(const StateVmoBank&) = delete; |
| StateVmoBank& operator=(const StateVmoBank&) = delete; |
| |
| // Atomically writes a 32-bit telemetry/state word at `offset` (must be |
| // 4-byte aligned and within `[0, size() - 4]`). |
| void SetState32(uint32_t offset, uint32_t value) const; |
| |
| // Atomically reads a 32-bit telemetry/state word at `offset`. |
| uint32_t GetState32(uint32_t offset, uint32_t default_value = 0) const; |
| |
| // Atomically writes a 32-bit knob word at `offset`. |
| void SetKnob32(uint32_t offset, uint32_t value) const; |
| |
| // Atomically reads a 32-bit host-tunable knob word at `offset`. |
| uint32_t GetKnob32(uint32_t offset, uint32_t default_value = 0) const; |
| |
| // Registers this bank's mapped buffer with `driver_lab_global_*` C helpers. |
| void RegisterGlobal(); |
| |
| zx::unowned_vmo vmo() const { return vmo_.borrow(); } |
| size_t size() const { return size_; } |
| uint8_t* data() const { return reinterpret_cast<uint8_t*>(mapped_addr_); } |
| |
| private: |
| StateVmoBank(zx::vmo vmo, zx_vaddr_t mapped_addr, size_t size, bool registered_global); |
| void Reset(); |
| |
| zx::vmo vmo_; |
| zx_vaddr_t mapped_addr_ = 0; |
| size_t size_ = 0; |
| bool registered_global_ = false; |
| }; |
| |
| // RAII handle to a running embedded `fuchsia.driver.lab` server and its |
| // background FIDL executor thread. |
| class EmbeddedServer final { |
| public: |
| EmbeddedServer() = default; |
| ~EmbeddedServer(); |
| |
| EmbeddedServer(EmbeddedServer&& other) noexcept; |
| EmbeddedServer& operator=(EmbeddedServer&& other) noexcept; |
| |
| EmbeddedServer(const EmbeddedServer&) = delete; |
| EmbeddedServer& operator=(const EmbeddedServer&) = delete; |
| |
| // Returns whether the embedded server is enabled. |
| bool is_enabled() const; |
| |
| // Returns whether the driver is currently quiesced by an active mutating |
| // host session. |
| bool is_quiesced() const; |
| |
| // Publishes `fuchsia.driver.lab.Service` onto a `component::OutgoingDirectory`. |
| // When disabled, this is a no-op and returns `zx::ok()`. |
| zx::result<> Publish( |
| component::OutgoingDirectory& outgoing, |
| std::string_view instance = component::OutgoingDirectory::kDefaultServiceInstance) const; |
| |
| // Publishes `fuchsia.driver.lab.Service` onto a DFv2 `fdf::OutgoingDirectory` |
| // (or any outgoing directory wrapper providing `AddService<Service>(...)`). |
| // When disabled, this is a no-op and returns `zx::ok()`. |
| template <typename OutgoingDir> |
| zx::result<> Publish( |
| OutgoingDir& outgoing, |
| std::string_view instance = component::OutgoingDirectory::kDefaultServiceInstance) const { |
| if (!is_enabled()) { |
| return zx::ok(); |
| } |
| DriverLabServerHandle* raw_handle = handle_; |
| return outgoing.template AddService<fuchsia_driver_lab::Service>( |
| fuchsia_driver_lab::Service::InstanceHandler({ |
| .proxy = |
| [raw_handle](fidl::ServerEnd<fuchsia_driver_lab::Proxy> request) { |
| if (request.is_valid()) { |
| (void)driver_lab_server_serve_proxy(raw_handle, |
| request.TakeChannel().release()); |
| } |
| }, |
| }), |
| instance); |
| } |
| |
| // Serves a single `fuchsia.driver.lab/Proxy` server-end channel on the |
| // embedded server's background executor. |
| zx::result<> ServeProxy(fidl::ServerEnd<fuchsia_driver_lab::Proxy> request) const; |
| |
| // Taps an interrupt event from the driver's interrupt handler. |
| void NotifyInterrupt(uint32_t resource_id) const; |
| |
| // Stops accepting new sessions, releases any active mutation lease and |
| // quiesce hook, and cancels pending interrupt waiters. |
| void Stop() const; |
| |
| private: |
| friend class Builder; |
| |
| EmbeddedServer(DriverLabServerHandle* handle, |
| std::unique_ptr<fit::function<void(bool)>> quiesce_hook); |
| void Reset(); |
| |
| DriverLabServerHandle* handle_ = nullptr; |
| std::unique_ptr<fit::function<void(bool)>> quiesce_hook_; |
| }; |
| |
| // Builder for configuring and instantiating an `EmbeddedServer` inside a C++ |
| // DFv2 driver. |
| class Builder final { |
| public: |
| explicit Builder(std::string_view node_identity); |
| ~Builder(); |
| |
| Builder(Builder&& other) noexcept; |
| Builder& operator=(Builder&& other) noexcept; |
| |
| Builder(const Builder&) = delete; |
| Builder& operator=(const Builder&) = delete; |
| |
| // Explicitly enables or disables the embedded server. |
| Builder& SetEnabled(bool enabled); |
| |
| // Configures whether mutating sessions are permitted on this embedded server. |
| Builder& SetAllowMutatingSessions(bool allow); |
| |
| // Shares a driver MMIO VMO with the embedded server. `vmo` is duplicated |
| // internally with `ZX_RIGHT_SAME_RIGHTS`; the caller retains ownership. |
| zx::result<uint32_t> AddMmioVmo(std::string_view name, zx::unowned_vmo vmo, size_t offset, |
| size_t size); |
| |
| // Convenience overload for `fdf::MmioBuffer` / `fdf::MmioView`-shaped types |
| // exposing `get_vmo()`, `get_offset()`, and `get_size()`. |
| template <typename MmioLike> |
| zx::result<uint32_t> AddMmioBuffer(std::string_view name, const MmioLike& mmio) { |
| return AddMmioVmo(name, mmio.get_vmo(), mmio.get_offset(), mmio.get_size()); |
| } |
| |
| // Registers a `StateVmoBank` and optionally marks 32-bit knob offsets as |
| // host-writable under mutating sessions. |
| zx::result<uint32_t> AddStateVmoBank(std::string_view name, const StateVmoBank& bank, |
| cpp20::span<const uint64_t> writable_knob_offsets = {}); |
| |
| zx::result<uint32_t> AddStateVmoBank(std::string_view name, const StateVmoBank& bank, |
| std::initializer_list<uint64_t> writable_knob_offsets) { |
| return AddStateVmoBank( |
| name, bank, |
| cpp20::span<const uint64_t>(writable_knob_offsets.begin(), writable_knob_offsets.size())); |
| } |
| |
| // Configures the permitted 32-bit writable register offsets on `resource_id`. |
| Builder& SetWritableRegisters(uint32_t resource_id, cpp20::span<const uint64_t> offsets); |
| |
| Builder& SetWritableRegisters(uint32_t resource_id, std::initializer_list<uint64_t> offsets) { |
| return SetWritableRegisters(resource_id, |
| cpp20::span<const uint64_t>(offsets.begin(), offsets.size())); |
| } |
| |
| // Configures 32-bit writable registers on `resource_id` that each permit |
| // host changes only to the bits in `allow_mask`. Use this for control |
| // registers that also hold reset or run/stop bits. |
| // |
| // Like `SetWritableRegisters()`, this replaces any writable registers |
| // previously configured on `resource_id`. To mix full-width and masked |
| // registers on one resource, give the full-width ones a mask of |
| // `0xffffffff`. |
| Builder& SetMaskedWritableRegisters(uint32_t resource_id, |
| cpp20::span<const MaskedRegister> registers); |
| |
| Builder& SetMaskedWritableRegisters(uint32_t resource_id, |
| std::initializer_list<MaskedRegister> registers) { |
| return SetMaskedWritableRegisters( |
| resource_id, cpp20::span<const MaskedRegister>(registers.begin(), registers.size())); |
| } |
| |
| // Configures the hard-denied byte ranges (`[start, end)`) on `resource_id`. |
| Builder& SetHardDeniedRanges(uint32_t resource_id, cpp20::span<const ByteRange> ranges); |
| |
| Builder& SetHardDeniedRanges(uint32_t resource_id, std::initializer_list<ByteRange> ranges) { |
| return SetHardDeniedRanges(resource_id, |
| cpp20::span<const ByteRange>(ranges.begin(), ranges.size())); |
| } |
| |
| // Registers a named interrupt resource for event tapping and returns its |
| // assigned resource ID. |
| uint32_t AddInterrupt(std::string_view name); |
| |
| // Registers a synchronous quiesce hook invoked when a mutating host session |
| // acquires (`paused = true`) or releases (`paused = false`) the mutation |
| // lease. |
| Builder& SetQuiesceHook(fit::function<void(bool paused)> hook); |
| |
| // Validates registered resources and builds the `EmbeddedServer`. |
| zx::result<EmbeddedServer> Build(); |
| |
| private: |
| static void QuiesceTrampoline(void* context, bool paused); |
| |
| DriverLabBuilderHandle* handle_ = nullptr; |
| std::unique_ptr<fit::function<void(bool)>> quiesce_hook_; |
| }; |
| |
| } // namespace driver_lab |
| |
| #endif // SRC_DEVICES_DRIVER_LAB_CPP_INCLUDE_LIB_DRIVER_LAB_DRIVER_LAB_H_ |