blob: 755e0569a6d1f0c6eb44ab87f1178f8cfa1a0f0a [file]
// 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