| // 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. |
| |
| #include <lib/driver_lab/driver_lab.h> |
| #include <lib/zx/vmar.h> |
| #include <zircon/errors.h> |
| |
| #include <atomic> |
| #include <utility> |
| #include <vector> |
| |
| namespace driver_lab { |
| |
| // ----------------------------------------------------------------------------- |
| // StateVmoBank |
| // ----------------------------------------------------------------------------- |
| |
| zx::result<StateVmoBank> StateVmoBank::Create(size_t size, bool register_global) { |
| if (size == 0 || (size % 4) != 0) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(size, 0, &vmo); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| zx_vaddr_t mapped_addr = 0; |
| status = |
| zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, size, &mapped_addr); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| if (register_global) { |
| driver_lab_global_register_state_bank(reinterpret_cast<uint8_t*>(mapped_addr), size); |
| } |
| return zx::ok(StateVmoBank(std::move(vmo), mapped_addr, size, register_global)); |
| } |
| |
| StateVmoBank::StateVmoBank(zx::vmo vmo, zx_vaddr_t mapped_addr, size_t size, bool registered_global) |
| : vmo_(std::move(vmo)), |
| mapped_addr_(mapped_addr), |
| size_(size), |
| registered_global_(registered_global) {} |
| |
| StateVmoBank::~StateVmoBank() { Reset(); } |
| |
| StateVmoBank::StateVmoBank(StateVmoBank&& other) noexcept |
| : vmo_(std::move(other.vmo_)), |
| mapped_addr_(std::exchange(other.mapped_addr_, 0)), |
| size_(std::exchange(other.size_, 0)), |
| registered_global_(std::exchange(other.registered_global_, false)) {} |
| |
| StateVmoBank& StateVmoBank::operator=(StateVmoBank&& other) noexcept { |
| if (this != &other) { |
| Reset(); |
| vmo_ = std::move(other.vmo_); |
| mapped_addr_ = std::exchange(other.mapped_addr_, 0); |
| size_ = std::exchange(other.size_, 0); |
| registered_global_ = std::exchange(other.registered_global_, false); |
| } |
| return *this; |
| } |
| |
| void StateVmoBank::Reset() { |
| if (mapped_addr_ != 0) { |
| if (registered_global_) { |
| driver_lab_global_unregister_state_bank(reinterpret_cast<const uint8_t*>(mapped_addr_)); |
| registered_global_ = false; |
| } |
| (void)zx::vmar::root_self()->unmap(mapped_addr_, size_); |
| mapped_addr_ = 0; |
| size_ = 0; |
| } |
| vmo_.reset(); |
| } |
| |
| void StateVmoBank::RegisterGlobal() { |
| if (mapped_addr_ != 0 && size_ > 0) { |
| driver_lab_global_register_state_bank(reinterpret_cast<uint8_t*>(mapped_addr_), size_); |
| registered_global_ = true; |
| } |
| } |
| |
| void StateVmoBank::SetState32(uint32_t offset, uint32_t value) const { |
| if (mapped_addr_ == 0 || (offset % 4) != 0 || static_cast<size_t>(offset) + 4 > size_) { |
| return; |
| } |
| auto* ptr = reinterpret_cast<uint32_t*>(mapped_addr_ + offset); |
| std::atomic_ref<uint32_t>(*ptr).store(value, std::memory_order_seq_cst); |
| } |
| |
| uint32_t StateVmoBank::GetState32(uint32_t offset, uint32_t default_value) const { |
| if (mapped_addr_ == 0 || (offset % 4) != 0 || static_cast<size_t>(offset) + 4 > size_) { |
| return default_value; |
| } |
| auto* ptr = reinterpret_cast<uint32_t*>(mapped_addr_ + offset); |
| return std::atomic_ref<uint32_t>(*ptr).load(std::memory_order_seq_cst); |
| } |
| |
| void StateVmoBank::SetKnob32(uint32_t offset, uint32_t value) const { SetState32(offset, value); } |
| |
| uint32_t StateVmoBank::GetKnob32(uint32_t offset, uint32_t default_value) const { |
| return GetState32(offset, default_value); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // EmbeddedServer |
| // ----------------------------------------------------------------------------- |
| |
| EmbeddedServer::EmbeddedServer(DriverLabServerHandle* handle, |
| std::unique_ptr<fit::function<void(bool)>> quiesce_hook) |
| : handle_(handle), quiesce_hook_(std::move(quiesce_hook)) {} |
| |
| EmbeddedServer::~EmbeddedServer() { Reset(); } |
| |
| EmbeddedServer::EmbeddedServer(EmbeddedServer&& other) noexcept |
| : handle_(std::exchange(other.handle_, nullptr)), |
| quiesce_hook_(std::move(other.quiesce_hook_)) {} |
| |
| EmbeddedServer& EmbeddedServer::operator=(EmbeddedServer&& other) noexcept { |
| if (this != &other) { |
| Reset(); |
| handle_ = std::exchange(other.handle_, nullptr); |
| quiesce_hook_ = std::move(other.quiesce_hook_); |
| } |
| return *this; |
| } |
| |
| void EmbeddedServer::Reset() { |
| if (handle_ != nullptr) { |
| driver_lab_server_destroy(handle_); |
| handle_ = nullptr; |
| } |
| quiesce_hook_.reset(); |
| } |
| |
| bool EmbeddedServer::is_enabled() const { return driver_lab_server_is_enabled(handle_); } |
| |
| bool EmbeddedServer::is_quiesced() const { return driver_lab_server_is_quiesced(handle_); } |
| |
| zx::result<> EmbeddedServer::Publish(component::OutgoingDirectory& outgoing, |
| std::string_view instance) const { |
| if (!is_enabled()) { |
| return zx::ok(); |
| } |
| DriverLabServerHandle* raw_handle = handle_; |
| return outgoing.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); |
| } |
| |
| zx::result<> EmbeddedServer::ServeProxy(fidl::ServerEnd<fuchsia_driver_lab::Proxy> request) const { |
| if (!request.is_valid()) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| zx_status_t status = driver_lab_server_serve_proxy(handle_, request.TakeChannel().release()); |
| return zx::make_result(status); |
| } |
| |
| void EmbeddedServer::NotifyInterrupt(uint32_t resource_id) const { |
| driver_lab_server_notify_interrupt(handle_, resource_id); |
| } |
| |
| void EmbeddedServer::Stop() const { driver_lab_server_stop(handle_); } |
| |
| // ----------------------------------------------------------------------------- |
| // Builder |
| // ----------------------------------------------------------------------------- |
| |
| Builder::Builder(std::string_view node_identity) |
| : handle_(driver_lab_builder_new(node_identity.data(), node_identity.size())) {} |
| |
| Builder::~Builder() { |
| if (handle_ != nullptr) { |
| driver_lab_builder_destroy(handle_); |
| handle_ = nullptr; |
| } |
| } |
| |
| Builder::Builder(Builder&& other) noexcept |
| : handle_(std::exchange(other.handle_, nullptr)), |
| quiesce_hook_(std::move(other.quiesce_hook_)) {} |
| |
| Builder& Builder::operator=(Builder&& other) noexcept { |
| if (this != &other) { |
| if (handle_ != nullptr) { |
| driver_lab_builder_destroy(handle_); |
| } |
| handle_ = std::exchange(other.handle_, nullptr); |
| quiesce_hook_ = std::move(other.quiesce_hook_); |
| } |
| return *this; |
| } |
| |
| Builder& Builder::SetEnabled(bool enabled) { |
| driver_lab_builder_with_enabled(handle_, enabled); |
| return *this; |
| } |
| |
| Builder& Builder::SetAllowMutatingSessions(bool allow) { |
| driver_lab_builder_with_allow_mutating_sessions(handle_, allow); |
| return *this; |
| } |
| |
| zx::result<uint32_t> Builder::AddMmioVmo(std::string_view name, zx::unowned_vmo vmo, size_t offset, |
| size_t size) { |
| if (handle_ == nullptr || !vmo->is_valid() || size == 0) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| uint32_t id = 0; |
| zx_status_t status = driver_lab_builder_with_mmio_vmo(handle_, name.data(), name.size(), |
| vmo->get(), offset, size, &id); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(id); |
| } |
| |
| zx::result<uint32_t> Builder::AddStateVmoBank(std::string_view name, const StateVmoBank& bank, |
| cpp20::span<const uint64_t> writable_knob_offsets) { |
| auto id_res = AddMmioVmo(name, bank.vmo(), 0, bank.size()); |
| if (id_res.is_error()) { |
| return id_res.take_error(); |
| } |
| if (!writable_knob_offsets.empty()) { |
| SetWritableRegisters(*id_res, writable_knob_offsets); |
| } |
| return id_res; |
| } |
| |
| Builder& Builder::SetWritableRegisters(uint32_t resource_id, cpp20::span<const uint64_t> offsets) { |
| driver_lab_builder_with_writable_registers(handle_, resource_id, offsets.data(), offsets.size()); |
| return *this; |
| } |
| |
| Builder& Builder::SetMaskedWritableRegisters(uint32_t resource_id, |
| cpp20::span<const MaskedRegister> registers) { |
| std::vector<uint64_t> offsets; |
| std::vector<uint32_t> masks; |
| offsets.reserve(registers.size()); |
| masks.reserve(registers.size()); |
| for (const auto& reg : registers) { |
| offsets.push_back(reg.offset); |
| masks.push_back(reg.allow_mask); |
| } |
| driver_lab_builder_with_masked_writable_registers(handle_, resource_id, offsets.data(), |
| masks.data(), offsets.size()); |
| return *this; |
| } |
| |
| Builder& Builder::SetHardDeniedRanges(uint32_t resource_id, cpp20::span<const ByteRange> ranges) { |
| std::vector<uint64_t> starts; |
| std::vector<uint64_t> ends; |
| starts.reserve(ranges.size()); |
| ends.reserve(ranges.size()); |
| for (const auto& range : ranges) { |
| starts.push_back(range.start); |
| ends.push_back(range.end); |
| } |
| driver_lab_builder_with_hard_denied_ranges(handle_, resource_id, starts.data(), ends.data(), |
| ranges.size()); |
| return *this; |
| } |
| |
| uint32_t Builder::AddInterrupt(std::string_view name) { |
| return driver_lab_builder_with_interrupt(handle_, name.data(), name.size()); |
| } |
| |
| void Builder::QuiesceTrampoline(void* context, bool paused) { |
| auto* fn = static_cast<fit::function<void(bool)>*>(context); |
| if (fn != nullptr && *fn) { |
| (*fn)(paused); |
| } |
| } |
| |
| Builder& Builder::SetQuiesceHook(fit::function<void(bool paused)> hook) { |
| quiesce_hook_ = std::make_unique<fit::function<void(bool)>>(std::move(hook)); |
| driver_lab_builder_with_quiesce_hook(handle_, &Builder::QuiesceTrampoline, quiesce_hook_.get()); |
| return *this; |
| } |
| |
| zx::result<EmbeddedServer> Builder::Build() { |
| if (handle_ == nullptr) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| DriverLabBuilderHandle* raw_builder = std::exchange(handle_, nullptr); |
| DriverLabServerHandle* server_handle = nullptr; |
| zx_status_t status = driver_lab_builder_build(raw_builder, &server_handle); |
| if (status != ZX_OK) { |
| quiesce_hook_.reset(); |
| return zx::error(status); |
| } |
| return zx::ok(EmbeddedServer(server_handle, std::move(quiesce_hook_))); |
| } |
| |
| } // namespace driver_lab |