blob: 6b59cea58b00f442398fd58db77ed9c7268c66d4 [file]
// Copyright 2019 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 "device.h"
#include <lib/ddk/debug.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <vector>
namespace wlan::simulation {
DeviceAddArgs::DeviceAddArgs(const DeviceAddArgs& args) { *this = args; }
DeviceAddArgs& DeviceAddArgs::operator=(const DeviceAddArgs& other) {
CopyRawDeviceAddArgs(other.Args());
return *this;
}
DeviceAddArgs::DeviceAddArgs(const device_add_args_t& args) { CopyRawDeviceAddArgs(args); }
DeviceAddArgs& DeviceAddArgs::operator=(const device_add_args_t& other) {
CopyRawDeviceAddArgs(other);
return *this;
}
void DeviceAddArgs::CopyRawDeviceAddArgs(const device_add_args_t& args) {
// Copy the known and safe parts of device_add_args_t to our copy of device_add_args_t and clear
// out the rest.
args_ = {
.version = args.version,
.ctx = args.ctx,
.proto_id = args.proto_id,
// Even though this is a plain void* it should be safe to copy. The DDK does the same thing,
// expecting the lifetime of the ops to outlast the device's lifetime.
.proto_ops = args.proto_ops,
.flags = args.flags,
};
// Update our local copies.
name_.clear();
if (args.name) {
name_ = args.name;
}
ops_.reset();
if (args.ops) {
ops_ = *args.ops;
}
power_states_.clear();
if (args.power_states) {
power_states_.assign(args.power_states, args.power_states + args.power_state_count);
}
performance_states_.clear();
if (args.performance_states) {
performance_states_.assign(args.performance_states,
args.performance_states + args.performance_state_count);
}
fidl_protocol_offer_strings_.clear();
if (args.fidl_protocol_offers) {
fidl_protocol_offer_strings_.assign(args.fidl_protocol_offers,
args.fidl_protocol_offers + args.fidl_protocol_offer_count);
}
proxy_args_.clear();
if (args.proxy_args) {
proxy_args_ = args.proxy_args;
}
// Update the raw pointers in our device_add_args_t to point to our locally copied data.
SetRawPointers();
}
void DeviceAddArgs::SetRawPointers() {
// Some of the information in device_add_args_t must point to our local copies
args_.name = name_.empty() ? nullptr : name_.c_str();
args_.ops = ops_.has_value() ? &ops_.value() : nullptr;
args_.power_states = power_states_.empty() ? nullptr : power_states_.data();
ZX_ASSERT(power_states_.size() <= std::numeric_limits<decltype(args_.power_state_count)>::max());
args_.power_state_count = static_cast<uint8_t>(power_states_.size());
args_.performance_states = performance_states_.empty() ? nullptr : performance_states_.data();
ZX_ASSERT(performance_states_.size() <=
std::numeric_limits<decltype(args_.performance_state_count)>::max());
args_.performance_state_count = static_cast<uint8_t>(performance_states_.size());
// This needs to be a two step process, we need to store the strings as std::strings to manage
// the memory and then we need to have an array of char* that fidl_protocol_offers can point to.
fidl_protocol_offers_.clear();
std::transform(fidl_protocol_offer_strings_.begin(), fidl_protocol_offer_strings_.end(),
std::back_inserter(fidl_protocol_offers_),
[](const std::string& str) { return str.c_str(); });
args_.fidl_protocol_offers =
fidl_protocol_offers_.empty() ? nullptr : fidl_protocol_offers_.data();
args_.fidl_protocol_offer_count = static_cast<uint8_t>(fidl_protocol_offers_.size());
args_.proxy_args = proxy_args_.empty() ? nullptr : proxy_args_.c_str();
}
FakeDevice::FakeDevice(uint64_t id, zx_device_t* parent, device_add_args_t dev_args,
wlan::simulation::FakeDevMgr* dev_mgr)
: id_(id), parent_(parent), dev_args_(dev_args), ref_count_(1), dev_mgr_(dev_mgr) {
ZX_ASSERT(dev_mgr_ != nullptr);
}
bool FakeDevice::IsRootParent() const { return dev_mgr_->GetRootDevice() == this; }
zx_status_t FakeDevice::AddChild(device_add_args_t* args, zx_device_t** out) {
return dev_mgr_->DeviceAdd(static_cast<zx_device_t*>(this), args, out);
}
void FakeDevice::AsyncRemove() { dev_mgr_->DeviceAsyncRemove(static_cast<zx_device_t*>(this)); }
enum class FakeDevMgr::DdkCallState {
kInvalid = 0,
kIdle = 1,
kCallPending = 2,
kReplyPending = 3,
kComplete = 4,
};
FakeDevMgr::FakeDevMgr()
: fake_root_dev_id_(1),
init_state_(DdkCallState::kIdle),
unbind_state_(DdkCallState::kIdle),
init_result_(ZX_OK) {
// This is the fake root device, which is the parent of all first-level devices added to
// FakeDevMgr.
devices_.emplace_back(
std::make_unique<zx_device_t>(fake_root_dev_id_, nullptr, device_add_args_t{}, this));
}
FakeDevMgr::~FakeDevMgr() {
zx_device_t* root = GetRootDevice();
if (!root) {
DBG_PRT("%s Fake root device is missing.\n", __func__);
return;
}
DeviceUnbind(root);
ZX_ASSERT(devices_.empty());
}
void FakeDevMgr::DeviceInitReply(zx_device_t* device, zx_status_t status,
const device_init_reply_args_t* args) {
// FakeDevMgr relies on synchronous completion of the init() reply.
ZX_DEBUG_ASSERT(std::this_thread::get_id() == init_thread_id_);
ZX_DEBUG_ASSERT(init_state_ == DdkCallState::kReplyPending);
init_state_ = DdkCallState::kComplete;
init_result_ = status;
}
void FakeDevMgr::DeviceUnbindReply(zx_device_t* device) {
// FakeDevMgr relies on synchronous completion of the unbind() reply.
ZX_DEBUG_ASSERT(std::this_thread::get_id() == unbind_thread_id_);
ZX_DEBUG_ASSERT(unbind_state_ == DdkCallState::kReplyPending);
unbind_state_ = DdkCallState::kComplete;
}
zx_status_t FakeDevMgr::DeviceAdd(zx_device_t* parent, device_add_args_t* args, zx_device_t** out) {
uint64_t id = dev_counter_++;
ZX_ASSERT(ContainsDevice(id) == false);
device_add_args_t dev_args = (args == nullptr ? device_add_args_t{} : *args);
zx_device_t* device =
devices_.emplace_back(std::make_unique<zx_device_t>(id, parent, dev_args, this)).get();
if (ContainsDevice(parent)) {
parent->AddRef();
} else {
DBG_PRT("%s Parent does not exist, might be root device.\n", __func__);
}
// Set the device in *out, since the synchronous call to init() may rely on it
// being set.
zx_device_t* const old_out = *out;
*out = device;
DBG_PRT("%s: Added SIM device. proto %d # devices: %lu Handle: %p\n", __func__,
args ? args->proto_id : 0, devices_.size(), out ? *out : nullptr);
// TODO(fxbug.dev/76420) - Add async support for Init()
if (args && args->ops && args->ops->init) {
init_thread_id_ = std::this_thread::get_id();
ZX_DEBUG_ASSERT(init_state_ == DdkCallState::kIdle);
init_state_ = DdkCallState::kReplyPending;
args->ops->init(args->ctx);
// The init reply should have arrived synchronously.
ZX_ASSERT(init_state_ == DdkCallState::kComplete);
// If the init failed, or an unbind was requested during the init call, do it now.
if (init_result_ != ZX_OK || unbind_state_ == DdkCallState::kCallPending) {
DeviceUnbind(device);
}
}
init_state_ = DdkCallState::kIdle;
// If the init reply was a failure, we restore the old value of the argument.
if (init_result_ != ZX_OK && out) {
*out = old_out;
}
// In the actual device manager, the call to the init hook is scheduled for after the device_add()
// call is complete. So the status we return is the status of the device_add() operation, without
// any consideration as to what the init reply returned.
return ZX_OK;
}
void FakeDevMgr::DeviceUnbind(zx_device_t* device) {
if (!ContainsDevice(device)) {
DBG_PRT("%s device %p does not exist\n", __func__, device);
return;
}
auto args = device->DevArgs();
if (args.ops && args.ops->unbind) {
unbind_thread_id_ = std::this_thread::get_id();
unbind_state_ = DdkCallState::kReplyPending;
(*args.ops->unbind)(args.ctx);
// The unbind reply should have arrived synchronously.
ZX_ASSERT(unbind_state_ == DdkCallState::kComplete);
}
unbind_state_ = DdkCallState::kIdle;
// Invoke unbind on a list of child devices.
{
std::vector<zx_device_t*> children = GetChildren(device);
for (auto child : children) {
DeviceUnbind(child);
}
}
ZX_ASSERT(GetChildren(device).empty());
// Now that all children have been unbound, we can unreference this device.
while (true) {
zx_device_t* parent_device = device->Parent();
// Only unreference the device here. For the initial device there are no children anyway and
// once we start moving up the parent chain we don't want to unreference other children of the
// parents.
if (!DeviceUnreference(device)) {
// This device isn't being removed, so we don't need to unreference its parent.
return;
}
device = parent_device;
if (!ContainsDevice(device)) {
DBG_PRT("%s device %p does not exist, we might reach the root\n", __func__, device);
return;
}
}
}
std::vector<zx_device_t*> FakeDevMgr::GetChildren(zx_device_t* device) {
std::vector<zx_device_t*> children;
for (const auto& dev : devices_) {
if (dev->Parent() == device) {
children.emplace_back(dev.get());
}
}
return children;
}
void FakeDevMgr::DeviceAsyncRemove(zx_device_t* device) {
if (!ContainsDevice(device)) {
DBG_PRT("%s device %p does not exist\n", __func__, device);
return;
}
// If we're in an init call, wait until it is complete to start unbinding.
if (init_state_ == DdkCallState::kReplyPending) {
unbind_state_ = DdkCallState::kCallPending;
return;
}
// TODO(fxbug.dev/76420) - Add async support for DeviceUnbind()
DeviceUnbind(device);
}
bool FakeDevMgr::DeviceUnreference(zx_device_t* device) {
if (device->RefCount() == 0) {
DBG_PRT("Error: device %s already has zero refcount\n", device->DevArgs().name);
return false;
}
device->Release();
if (device->RefCount() == 0) {
const auto args = device->DevArgs();
if (args.ops && args.ops->release) {
(*args.ops->release)(args.ctx);
}
auto it = std::remove_if(devices_.begin(), devices_.end(),
[&](const auto& dev) { return dev.get() == device; });
// This should only ever erase one device, otherwise there's a bug in the device manager.
ZX_ASSERT(std::distance(it, devices_.end()) == 1);
devices_.erase(it, devices_.end());
// Device released
return true;
}
// Just decrease ref_count, not actually released
return false;
}
zx_device_t* FakeDevMgr::FindFirst(const Predicate& pred) const {
auto iter = std::find_if(devices_.begin(), devices_.end(),
[pred](auto& device) -> bool { return pred(device.get()); });
if (iter == devices_.end()) {
return nullptr;
}
return iter->get();
}
zx_device_t* FakeDevMgr::FindFirstByProtocolId(uint32_t proto_id) const {
return FindFirst([proto_id](zx_device_t* dev) { return proto_id == dev->DevArgs().proto_id; });
}
zx_device_t* FakeDevMgr::FindLatest(const Predicate& pred) {
auto iter = std::find_if(devices_.rbegin(), devices_.rend(),
[pred](auto& device) -> bool { return pred(device.get()); });
if (iter == devices_.rend()) {
return nullptr;
}
return iter->get();
}
zx_device_t* FakeDevMgr::FindLatestByProtocolId(uint32_t proto_id) {
return FindLatest([proto_id](zx_device_t* dev) { return proto_id == dev->DevArgs().proto_id; });
}
bool FakeDevMgr::ContainsDevice(zx_device_t* device) const {
return FindFirst([device](zx_device_t* dev) { return device == dev; }) != nullptr;
}
bool FakeDevMgr::ContainsDevice(uint64_t id) const {
return FindFirst([id](zx_device_t* dev) { return dev->Id() == id; }) != nullptr;
}
// Return the number of devices other than fake root device.
size_t FakeDevMgr::DeviceCount() { return devices_.size() - 1; }
size_t FakeDevMgr::DeviceCountByProtocolId(uint32_t proto_id) {
return std::count_if(devices_.begin(), devices_.end(),
[proto_id](auto& device) { return device->DevArgs().proto_id == proto_id; });
}
zx_device_t* FakeDevMgr::GetRootDevice() {
ZX_ASSERT(devices_.front()->Id() == fake_root_dev_id_);
return devices_.front().get();
}
} // namespace wlan::simulation
// Provide our own implementation of this function in the global namespace. This will ensure that
// calls to device_add and DdkAdd ends up calling the FakeDevMgr associated with |parent|.
__EXPORT
zx_status_t device_add_from_driver(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) {
return parent->AddChild(args, out);
}
// Provide our own implementation of this function in the global namespace. This will ensure that
// device_async_remove and DdkAsyncRemove ends up calling the FakeDevMgr associated with |device|.
__EXPORT
void device_async_remove(zx_device_t* device) {
if (!device) {
zxlogf(ERROR, "Attempting to remove null device");
return;
}
if (device->IsRootParent()) {
zxlogf(ERROR, "Attempting to remove root parent");
return;
}
device->AsyncRemove();
}
__EXPORT
__WEAK zx_driver_rec __zircon_driver_rec__ = {
.ops = {},
.driver = {},
};