blob: 21db4cad27c7b308ace7b4606596547752962537 [file] [log] [blame]
// Copyright 2021 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 "src/devices/testing/mock-ddk/mock-device.h"
#include <lib/async/cpp/task.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdf/cpp/protocol.h>
#include <zircon/types.h>
#include <algorithm>
#include <latch>
namespace {
std::mutex g_mutex;
std::weak_ptr<fdf_testing::DriverRuntime> g_runtime __TA_GUARDED(g_mutex);
} // namespace
namespace mock_ddk {
std::shared_ptr<fdf_testing::DriverRuntime> GetDriverRuntime() {
std::lock_guard guard(g_mutex);
std::shared_ptr shared = g_runtime.lock();
if (shared) {
return shared;
}
shared = std::make_shared<fdf_testing::DriverRuntime>();
g_runtime = shared;
return shared;
}
} // namespace mock_ddk
MockDevice::MockDevice(device_add_args_t* args, MockDevice* parent)
: parent_(parent),
ops_(args->ops),
ctx_(args->ctx),
name_(args->name),
inspect_(zx::vmo{args->inspect_vmo}),
outgoing_(zx::channel{args->outgoing_dir_channel}),
driver_runtime_(parent_->driver_runtime_) {
if (args->proto_id && args->proto_ops) {
AddProtocol(args->proto_id, args->proto_ops, ctx_);
}
if (args->props) {
props_.insert(props_.begin(), args->props, args->props + args->prop_count);
}
if (args->str_props) {
str_props_.insert(str_props_.begin(), args->str_props, args->str_props + args->str_prop_count);
}
if (args->metadata_list && args->metadata_count > 0) {
for (size_t i = 0; i < args->metadata_count; ++i) {
SetMetadata(args->metadata_list[i].type, args->metadata_list[i].data,
args->metadata_list[i].length);
}
}
}
// Static member function.
zx_status_t MockDevice::Create(device_add_args_t* args, MockDevice* parent, MockDevice** out_dev) {
// We only check the minimum requirements to make sure the mock does not crash:
if (!parent || !args || !args->name) {
return ZX_ERR_INVALID_ARGS;
}
// Using `new` to access a non-public constructor.
auto new_device = std::shared_ptr<MockDevice>(new MockDevice(args, parent));
if (out_dev) {
*out_dev = new_device.get();
}
parent->children_.emplace_back(std::move(new_device));
// PropagateMetadata to last child:
for (const auto& [key, value] : parent->metadata_) {
parent->children().back()->metadata_[key] = value;
}
parent->children().back()->PropagateMetadata();
return ZX_OK;
}
MockDevice* MockDevice::GetLatestChild() {
if (child_count()) {
return children_.back().get();
}
return nullptr;
}
// Templates that dispatch the protocol operations if they were set.
// If they were not set, the fallback parameter is returned to the caller
// (usually ZX_ERR_NOT_SUPPORTED)
template <typename RetType, typename... ArgTypes>
RetType Dispatch(void* ctx, RetType (*op)(void* ctx, ArgTypes...), RetType fallback,
ArgTypes... args) {
if (!op) {
return fallback;
}
return (*op)(ctx, args...);
}
template <typename... ArgTypes>
void Dispatch(void* ctx, void (*op)(void* ctx, ArgTypes...), ArgTypes... args) {
if (!op) {
return;
}
(*op)(ctx, args...);
}
void MockDevice::InitOp() { Dispatch(ctx_, ops_->init); }
void MockDevice::UnbindOp() { Dispatch(ctx_, ops_->unbind); }
void MockDevice::ReleaseOp() {
if (!IsRootParent()) {
while (!children_.empty()) {
children_.front()->ReleaseOp();
}
parent_->ChildPreReleaseOp(ctx_);
parent_->RecordChildPreRelease(ZX_OK);
}
Dispatch(ctx_, ops_->release);
// Delete instance from parent's children_ accounting.
for (auto it = parent_->children_.begin(); it != parent_->children_.end(); ++it) {
if (it->get() == this) {
parent_->children_.erase(it);
return;
}
}
}
MockDevice::~MockDevice() {
while (!children_.empty()) {
children_.front()->ReleaseOp();
}
}
void MockDevice::SuspendNewOp(uint8_t requested_state, bool enable_wake, uint8_t suspend_reason) {
Dispatch(ctx_, ops_->suspend, requested_state, enable_wake, suspend_reason);
}
zx_status_t MockDevice::ConfigureAutoSuspendOp(bool enable, uint8_t requested_state) {
return Dispatch(ctx_, ops_->configure_auto_suspend, ZX_ERR_NOT_SUPPORTED, enable,
requested_state);
}
void MockDevice::ResumeNewOp(uint32_t requested_state) {
Dispatch(ctx_, ops_->resume, requested_state);
}
bool MockDevice::MessageOp(fidl::IncomingHeaderAndMessage msg, device_fidl_txn_t txn) {
if (ops_->message) {
Dispatch(ctx_, ops_->message, std::move(msg).ReleaseToEncodedCMessage(), txn);
return true;
}
return false;
}
void MockDevice::ChildPreReleaseOp(void* child_ctx) {
Dispatch(ctx_, ops_->child_pre_release, child_ctx);
}
void MockDevice::SetMetadata(uint32_t type, const void* data, size_t data_length) {
std::vector<uint8_t> owned(data_length);
memcpy(owned.data(), data, data_length);
metadata_[type] = std::move(owned);
PropagateMetadata();
}
void MockDevice::SetConfigVmo(zx::vmo config_vmo) { config_vmo_ = std::move(config_vmo); }
void MockDevice::SetVariable(const char* name, const char* data) {
if (data) {
std::string owned(data);
variables_[name] = std::move(owned);
} else {
auto itr = variables_.find(name);
if (itr != variables_.end()) {
variables_.erase(itr);
}
}
}
zx_status_t MockDevice::GetMetadata(uint32_t type, void* buf, size_t buflen, size_t* actual) {
auto itr = metadata_.find(type);
if (itr != metadata_.end()) {
auto& metadata = itr->second;
*actual = metadata.size();
if (buflen < metadata.size()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(buf, metadata.data(), metadata.size());
return ZX_OK;
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t MockDevice::GetMetadataSize(uint32_t type, size_t* out_size) {
auto itr = metadata_.find(type);
if (itr != metadata_.end()) {
auto metadata = itr->second;
*out_size = metadata.size();
return ZX_OK;
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t MockDevice::GetVariable(const char* name, char* out, size_t size, size_t* actual) {
auto itr = variables_.find(name);
if (itr == variables_.end()) {
return ZX_ERR_NOT_FOUND;
}
auto& variable = itr->second;
if (actual) {
*actual = variable.size();
}
if (size < variable.size()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
strncpy(out, variable.data(), size);
return ZX_OK;
}
void MockDevice::PropagateMetadata() {
for (auto& child : children_) {
for (const auto& [key, value] : metadata_) {
child->metadata_[key] = value;
}
child->PropagateMetadata();
}
}
void MockDevice::AddProtocol(uint32_t id, const void* ops, void* ctx, const char* fragment_name) {
protocols_[fragment_name].push_back(mock_ddk::ProtocolEntry{id, {ops, ctx}});
}
void MockDevice::AddFidlService(const char* service_name, fidl::ClientEnd<fuchsia_io::Directory> ns,
const char* name) {
fidl_services_.try_emplace(
name, std::unordered_map<std::string, fidl::ClientEnd<fuchsia_io::Directory>>());
fidl_services_[name][service_name] = std::move(ns);
}
void MockDevice::SetFirmware(std::vector<uint8_t> firmware, std::string_view path) {
firmware_[path] = std::move(firmware);
}
void MockDevice::SetFirmware(std::string firmware, std::string_view path) {
std::vector<uint8_t> vec(firmware.begin(), firmware.end());
SetFirmware(vec, path);
}
zx_status_t MockDevice::LoadFirmware(std::string_view path, zx_handle_t* fw, size_t* size) {
auto firmware = firmware_.find(path);
// If a match is not found to 'path', check if there is a firmware that was loaded with
// path == nullptr:
if (firmware == firmware_.end()) {
firmware = firmware_.find("");
}
if (firmware == firmware_.end()) {
return ZX_ERR_NOT_FOUND;
}
zx_status_t status = ZX_OK;
zx_handle_t vmo = ZX_HANDLE_INVALID;
if ((status = zx_vmo_create(firmware->second.size(), 0, &vmo)) != ZX_OK) {
return status;
}
if ((status = zx_vmo_write(vmo, firmware->second.data(), 0, firmware->second.size())) != ZX_OK) {
return status;
}
*fw = vmo;
*size = firmware->second.size();
return ZX_OK;
}
zx_status_t MockDevice::GetProtocol(uint32_t proto_id, void* protocol,
const char* fragment_name) const {
// Check if there are protocols for the fragment/device:
auto protocol_set = protocols_.find(fragment_name);
if (protocol_set == protocols_.end()) {
return ZX_ERR_NOT_SUPPORTED;
}
auto out = reinterpret_cast<mock_ddk::Protocol*>(protocol);
// First we check if the user has added protocols:
for (const auto& proto : protocol_set->second) {
if (proto_id == proto.id) {
out->ops = proto.proto.ops;
out->ctx = proto.proto.ctx;
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t MockDevice::ConnectToFidlProtocol(const char* service_name, const char* protocol_name,
zx::channel request, const char* fragment_name) {
// Check if there are protocols for the fragment/device:
auto service_set = fidl_services_.find(fragment_name);
if (service_set == fidl_services_.end()) {
return ZX_ERR_NOT_SUPPORTED;
}
auto ns = service_set->second.find(service_name);
if (ns == service_set->second.end()) {
return ZX_ERR_NOT_FOUND;
}
const std::string path = std::string("svc/") + service_name + "/default/" + protocol_name;
return component::internal::ConnectAtRaw(ns->second, std::move(request), path).status_value();
}
zx_status_t MockDevice::ConnectToRuntimeProtocol(const char* service_name,
const char* protocol_name, fdf::Channel request,
const char* fragment_name) {
zx::channel client_token, server_token;
auto status = zx::channel::create(0, &client_token, &server_token);
if (status != ZX_OK) {
return status;
}
status = fdf::ProtocolConnect(std::move(client_token), std::move(request));
if (status != ZX_OK) {
return status;
}
return ConnectToFidlProtocol(service_name, protocol_name, std::move(server_token), fragment_name);
}
size_t MockDevice::descendant_count() const {
size_t count = child_count();
for (auto& child : children_) {
count += child->descendant_count();
}
return count;
}
// helper functions:
namespace {
zx_status_t ProcessDeviceRemoval(MockDevice* device, async_dispatcher_t* dispatcher) {
// deleting children, so use a while loop:
while (!device->children().empty()) {
auto status = ProcessDeviceRemoval(device->children().back().get(), dispatcher);
if (status != ZX_OK) {
return status;
}
}
if (dispatcher != nullptr) {
std::latch done(1);
async::PostTask(dispatcher, [&]() {
device->UnbindOp();
done.count_down();
});
done.wait();
} else {
device->UnbindOp();
}
if (device->HasUnbindOp()) {
zx_status_t status = device->WaitUntilUnbindReplyCalled();
if (status != ZX_OK) {
return status;
}
}
if (dispatcher != nullptr) {
std::latch done(1);
async::PostTask(dispatcher, [&]() {
device->ReleaseOp();
done.count_down();
});
done.wait();
} else {
device->ReleaseOp();
}
return ZX_OK;
}
} // anonymous namespace
zx_status_t mock_ddk::ReleaseFlaggedDevices(MockDevice* device, async_dispatcher_t* dispatcher) {
if (device->AsyncRemoveCalled()) {
return ProcessDeviceRemoval(device, dispatcher);
}
// Make a vector of the child device pointers, because we might delete the child:
std::vector<MockDevice*> children;
std::transform(device->children().begin(), device->children().end(), std::back_inserter(children),
[](std::shared_ptr<MockDevice> c) -> MockDevice* { return c.get(); });
for (auto child : children) {
auto ret = ReleaseFlaggedDevices(child, dispatcher);
if (ret != ZX_OK) {
return ret;
}
}
return ZX_OK;
}