blob: 92e0cb47f5ae4ab2f0ee528f75aa9f3f29cd7091 [file] [log] [blame]
// Copyright 2018 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/fake_ddk/fake_ddk.h>
#include <stdarg.h>
#include <stdlib.h>
#include <zircon/assert.h>
#include <zircon/syscalls/log.h>
#include <zircon/types.h>
#include <utility>
#include <zxtest/zxtest.h>
namespace fake_ddk {
zx_device_t* kFakeDevice = reinterpret_cast<zx_device_t*>(0x55);
zx_device_t* kFakeParent = reinterpret_cast<zx_device_t*>(0xaa);
fx_log_severity_t kMinLogSeverity = FX_LOG_INFO;
size_t kFakeFWSize = 0x1000;
zx_device_t* FakeDevice() {
ZX_ASSERT_MSG(Bind::Instance() != nullptr,
"Attemping to access FakeDevice before fake_ddk::Bind instance was initialized. "
"Double check initialization ordering!");
return kFakeDevice;
}
zx_device_t* FakeParent() {
ZX_ASSERT_MSG(Bind::Instance() != nullptr,
"Attemping to access FakeParent before fake_ddk::Bind instance was initialized. "
"Double check initialization ordering!");
return kFakeParent;
}
Bind* Bind::instance_ = nullptr;
Bind::Bind() {
ZX_ASSERT(!instance_);
instance_ = this;
}
Bind::~Bind() {
JoinUnbindThread();
instance_ = nullptr;
}
void Bind::JoinUnbindThread() {
// Make sure the unbind hook has returned. Users might not have called |WaitUntilRemove| if they
// reply to unbind from the same thread.
if (unbind_op_ && unbind_started_ && !unbind_thread_joined_) {
thrd_join(unbind_thread_, NULL);
unbind_thread_joined_ = true;
}
}
bool Bind::Ok() {
JoinUnbindThread();
EXPECT_TRUE(add_called_);
EXPECT_EQ(has_init_hook_, init_reply_.has_value());
// We do not check the actual value of |init_reply_|, as the test may wish to test
// scenarios where the init failure is handled.
EXPECT_TRUE(remove_called_);
EXPECT_FALSE(bad_parent_);
EXPECT_FALSE(bad_device_);
// TODO(fxbug.dev/34344): Remove and make void once all dependent tests migrate to zxtest.
return !zxtest::Runner::GetInstance()->CurrentTestHasFailures();
}
zx_status_t Bind::WaitUntilInitComplete() {
return sync_completion_wait_deadline(&init_replied_sync_, zx::time::infinite().get());
}
zx_status_t Bind::WaitUntilRemove() {
return sync_completion_wait_deadline(&remove_called_sync_, zx::time::infinite().get());
}
zx_status_t Bind::WaitUntilSuspend() {
return sync_completion_wait_deadline(&suspend_called_sync_, zx::time::infinite().get());
}
void Bind::ExpectMetadata(const void* data, size_t data_length) {
metadata_ = data;
metadata_length_ = data_length;
}
void Bind::GetMetadataInfo(int* num_calls, size_t* length) {
*num_calls = add_metadata_calls_;
*length = metadata_length_;
}
void Bind::SetProtocol(uint32_t id, const void* proto) {
if (proto) {
protocols_[id] = *static_cast<const Protocol*>(proto);
} else {
protocols_.erase(id);
}
}
void Bind::SetFragments(fbl::Array<FragmentEntry>&& fragments) {
fragments_ = std::move(fragments);
for (auto& fragment : fragments_) {
fragment_lookup_.insert(&fragment);
}
}
void Bind::SetSize(zx_off_t size) { size_ = size; }
void Bind::SetMetadata(uint32_t type, const void* data, size_t data_length) {
get_metadata_[type] = std::make_pair(data, data_length);
}
void Bind::SetMetadata(const void* data, size_t data_length) {
get_metadata_old_ = data;
get_metadata_length_old_ = data_length;
}
void Bind::StartUnbindIfNeeded() {
// We need to call unbind from a separate thread, as some drivers may call DdkAsyncRemove
// from a worker thread that they then try to join with in their unbind hook.
// This will only be run once.
auto unbind_thread = [](void* arg) -> int {
auto bind = reinterpret_cast<Bind*>(arg);
bind->unbind_op_(bind->op_ctx_);
return 0;
};
// Only call the unbind hook once.
if (unbind_op_ && !unbind_started_) {
unbind_started_ = true;
int res = thrd_create_with_name(&unbind_thread_, unbind_thread, this, "fake-ddk-unbind-thread");
ZX_ASSERT(res == thrd_success);
} else if (!unbind_op_) {
// The unbind hook is optional. If not present, we should mark the device as removed.
remove_called_ = true;
}
}
zx_status_t Bind::DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) {
if (args) {
zx_handle_close(args->inspect_vmo);
}
zx_status_t status;
if (parent != kFakeParent) {
bad_parent_ = true;
}
if (args && args->ops) {
if (args->ops->init) {
has_init_hook_ = true;
}
if (args->ops->message) {
std::optional<zx::channel> remote_channel = std::nullopt;
if (args->client_remote) {
remote_channel.emplace(args->client_remote);
}
if ((status = fidl_.SetMessageOp(args->ctx, args->ops->message, std::move(remote_channel))) <
0) {
return status;
}
}
if (args->ops->unbind) {
unbind_op_ = args->ops->unbind;
op_ctx_ = args->ctx;
}
}
*out = kFakeDevice;
add_called_ = true;
// This needs to come after setting |out|, as this sets the device's internal |zxdev_|,
// which needs to be present for the InitTxn.
if (has_init_hook_) {
args->ops->init(args->ctx);
}
return ZX_OK;
}
void Bind::DeviceInitReply(zx_device_t* device, zx_status_t status,
const device_init_reply_args_t* args) {
if (device != kFakeDevice) {
bad_device_ = true;
}
init_reply_ = status;
if (init_reply_ != ZX_OK) {
// If the init fails, the device should be automatically unbound and removed.
unbind_requested_ = true;
}
if (unbind_requested_) {
StartUnbindIfNeeded();
}
sync_completion_signal(&init_replied_sync_);
}
zx_status_t Bind::DeviceRemove(zx_device_t* device) {
if (device != kFakeDevice) {
bad_device_ = true;
}
remove_called_ = true;
sync_completion_signal(&remove_called_sync_);
return ZX_OK;
}
void Bind::DeviceUnbindReply(zx_device_t* device) { DeviceRemove(device); }
void Bind::DeviceAsyncRemove(zx_device_t* device) {
if (device != kFakeDevice) {
bad_device_ = true;
}
unbind_requested_ = true;
// We should not call unbind until the init hook has been replied to.
if (!has_init_hook_ || init_reply_.has_value()) {
StartUnbindIfNeeded();
}
}
zx_status_t Bind::DeviceAddMetadata(zx_device_t* device, uint32_t type, const void* data,
size_t length) {
if (device != kFakeDevice) {
bad_device_ = true;
}
if (metadata_) {
if (length != metadata_length_ || memcmp(data, metadata_, length) != 0) {
fprintf(stderr, "Unexpected metadata\n");
return ZX_ERR_BAD_STATE;
}
} else {
metadata_length_ += length;
}
add_metadata_calls_++;
return ZX_OK;
}
zx_status_t Bind::DeviceGetMetadata(zx_device_t* dev, uint32_t type, void* buf, size_t buflen,
size_t* actual) {
get_metadata_calls_++;
auto itr = get_metadata_.find(type);
if (itr != get_metadata_.end()) {
auto [metadata, size] = itr->second;
*actual = size;
if (buflen < size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(buf, metadata, size);
return ZX_OK;
}
// Fallback to old mechanism
if (get_metadata_old_ == nullptr) {
return ZX_ERR_NOT_FOUND;
}
*actual = get_metadata_length_old_;
if (buflen < get_metadata_length_old_) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(buf, get_metadata_old_, get_metadata_length_old_);
return ZX_OK;
}
zx_status_t Bind::DeviceGetMetadataSize(zx_device_t* dev, uint32_t type, size_t* out_size) {
auto itr = get_metadata_.find(type);
if (itr != get_metadata_.end()) {
auto [_, size] = itr->second;
*out_size = size;
return ZX_OK;
}
if (get_metadata_old_ == nullptr) {
return ZX_ERR_BAD_STATE;
}
*out_size = get_metadata_length_old_;
return ZX_OK;
}
void Bind::DeviceMakeVisible(zx_device_t* device) {
if (device != kFakeDevice) {
bad_device_ = true;
}
make_visible_called_ = true;
return;
}
void Bind::DeviceSuspendComplete(zx_device_t* device, zx_status_t status, uint8_t out_state) {
if (device != kFakeDevice) {
bad_device_ = true;
}
sync_completion_signal(&suspend_called_sync_);
return;
}
void Bind::DeviceResumeComplete(zx_device_t* device, zx_status_t status, uint8_t out_power_state,
uint32_t out_perf_state) {
if (device != kFakeDevice) {
bad_device_ = true;
}
resume_complete_called_ = true;
return;
}
zx_status_t Bind::DeviceGetProtocol(const zx_device_t* device, uint32_t proto_id, void* protocol) {
auto out = reinterpret_cast<Protocol*>(protocol);
if (device == kFakeParent) {
auto itr = protocols_.find(proto_id);
if (itr == protocols_.end()) {
return ZX_ERR_NOT_SUPPORTED;
}
*out = itr->second;
return ZX_OK;
}
const auto& fragment = fragment_lookup_.find(reinterpret_cast<const FragmentEntry*>(device));
if (fragment != fragment_lookup_.end()) {
for (const auto& proto : (*fragment)->protocols) {
if (proto_id == proto.id) {
out->ops = proto.proto.ops;
out->ctx = proto.proto.ctx;
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
bad_device_ = true;
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Bind::DeviceOpenProtocolSessionMultibindable(const zx_device_t* device,
uint32_t proto_id, void* protocol) {
if (device != kFakeDevice) {
bad_device_ = true;
}
device_open_protocol_session_multibindable_ = true;
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Bind::DeviceRebind(zx_device_t* device) {
if (device != kFakeDevice) {
bad_device_ = true;
}
rebind_called_ = true;
return ZX_OK;
}
const char* Bind::DeviceGetName(zx_device_t* device) {
if (device != kFakeParent) {
bad_device_ = true;
}
return "";
}
zx_off_t Bind::DeviceGetSize(zx_device_t* device) {
if (device != kFakeParent) {
bad_device_ = true;
}
return size_;
}
uint32_t Bind::DeviceGetFragmentCount(zx_device_t* device) {
if (device != kFakeParent) {
bad_device_ = true;
}
return static_cast<uint32_t>(fragment_lookup_.size());
}
void Bind::DeviceGetFragments(zx_device_t* device, composite_device_fragment_t* comp_list,
size_t comp_count, size_t* comp_actual) {
if (device != kFakeParent) {
bad_device_ = true;
}
*comp_actual = fragments_.size();
for (size_t i = 0; i < std::min(fragments_.size(), comp_count); i++) {
strncpy(comp_list[i].name, fragments_[i].name.c_str(),
std::min(sizeof(comp_list[i].name), fragments_[i].name.size()));
comp_list[i].device = reinterpret_cast<zx_device_t*>(&fragments_[i]);
}
}
bool Bind::DeviceGetFragment(zx_device_t* device, const char* name, zx_device_t** out) {
if (device != kFakeParent) {
bad_device_ = true;
}
for (auto& fragment : fragments_) {
if (fragment.name.compare(name) == 0) {
*out = reinterpret_cast<zx_device_t*>(&fragment);
return true;
}
}
return false;
}
} // namespace fake_ddk
__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) {
if (!fake_ddk::Bind::Instance()) {
return ZX_OK;
}
return fake_ddk::Bind::Instance()->DeviceAdd(drv, parent, args, out);
}
__EXPORT
void device_async_remove(zx_device_t* device) {
if (!fake_ddk::Bind::Instance()) {
return;
}
return fake_ddk::Bind::Instance()->DeviceAsyncRemove(device);
}
__EXPORT
void device_init_reply(zx_device_t* device, zx_status_t status,
const device_init_reply_args_t* args) {
if (!fake_ddk::Bind::Instance()) {
return;
}
return fake_ddk::Bind::Instance()->DeviceInitReply(device, status, args);
}
__EXPORT
void device_unbind_reply(zx_device_t* device) {
if (!fake_ddk::Bind::Instance()) {
return;
}
fake_ddk::Bind::Instance()->DeviceUnbindReply(device);
}
__EXPORT void device_suspend_reply(zx_device_t* dev, zx_status_t status, uint8_t out_state) {
if (!fake_ddk::Bind::Instance()) {
return;
}
return fake_ddk::Bind::Instance()->DeviceSuspendComplete(dev, status, out_state);
}
__EXPORT void device_resume_reply(zx_device_t* dev, zx_status_t status, uint8_t out_power_state,
uint32_t out_perf_state) {
if (!fake_ddk::Bind::Instance()) {
return;
}
return fake_ddk::Bind::Instance()->DeviceResumeComplete(dev, status, out_power_state,
out_perf_state);
}
__EXPORT
zx_status_t device_add_metadata(zx_device_t* device, uint32_t type, const void* data,
size_t length) {
if (!fake_ddk::Bind::Instance()) {
return ZX_OK;
}
return fake_ddk::Bind::Instance()->DeviceAddMetadata(device, type, data, length);
}
__EXPORT
void device_make_visible(zx_device_t* device, const device_make_visible_args_t* args) {
if (fake_ddk::Bind::Instance()) {
fake_ddk::Bind::Instance()->DeviceMakeVisible(device);
}
return;
}
__EXPORT
zx_status_t device_get_protocol(const zx_device_t* device, uint32_t proto_id, void* protocol) {
if (!fake_ddk::Bind::Instance()) {
return ZX_ERR_NOT_SUPPORTED;
}
return fake_ddk::Bind::Instance()->DeviceGetProtocol(device, proto_id, protocol);
}
__EXPORT
zx_status_t device_open_protocol_session_multibindable(const zx_device_t* dev, uint32_t proto_id,
void* protocol) {
if (!fake_ddk::Bind::Instance()) {
return ZX_ERR_NOT_SUPPORTED;
}
return fake_ddk::Bind::Instance()->DeviceOpenProtocolSessionMultibindable(dev, proto_id,
protocol);
}
__EXPORT
const char* device_get_name(zx_device_t* device) {
if (!fake_ddk::Bind::Instance()) {
return nullptr;
}
return fake_ddk::Bind::Instance()->DeviceGetName(device);
}
__EXPORT
zx_off_t device_get_size(zx_device_t* device) {
if (!fake_ddk::Bind::Instance()) {
return 0;
}
return fake_ddk::Bind::Instance()->DeviceGetSize(device);
}
__EXPORT
zx_status_t device_get_metadata(zx_device_t* device, uint32_t type, void* buf, size_t buflen,
size_t* actual) {
if (!fake_ddk::Bind::Instance()) {
return ZX_ERR_NOT_SUPPORTED;
}
return fake_ddk::Bind::Instance()->DeviceGetMetadata(device, type, buf, buflen, actual);
}
__EXPORT
zx_status_t device_get_metadata_size(zx_device_t* device, uint32_t type, size_t* out_size) {
if (!fake_ddk::Bind::Instance()) {
return ZX_ERR_NOT_SUPPORTED;
}
return fake_ddk::Bind::Instance()->DeviceGetMetadataSize(device, type, out_size);
}
__EXPORT
void device_state_clr_set(zx_device_t* dev, zx_signals_t clearflag, zx_signals_t setflag) {
// This is currently a no-op.
}
__EXPORT
zx_status_t device_get_profile(zx_device_t* device, uint32_t priority, const char* name,
zx_handle_t* out_profile) {
// This is currently a no-op.
*out_profile = ZX_HANDLE_INVALID;
return ZX_OK;
}
__EXPORT
zx_status_t device_get_deadline_profile(zx_device_t* device, uint64_t capacity, uint64_t deadline,
uint64_t period, const char* name,
zx_handle_t* out_profile) {
// This is currently a no-op.
*out_profile = ZX_HANDLE_INVALID;
return ZX_OK;
}
__EXPORT
zx_status_t device_set_profile_by_role(zx_device_t* device, zx_handle_t thread, const char* role,
size_t role_size) {
// This is currently a no-op.
return ZX_OK;
}
__EXPORT
void device_fidl_transaction_take_ownership(fidl_txn_t* txn, device_fidl_txn_t* new_txn) {
auto fidl_txn = fake_ddk::FromDdkInternalTransaction(ddk::internal::Transaction::FromTxn(txn));
ZX_ASSERT_MSG(std::holds_alternative<fidl::Transaction*>(fidl_txn),
"Can only take ownership of transaction once\n");
auto result = std::get<fidl::Transaction*>(fidl_txn)->TakeOwnership();
// We call this to mimic what devhost does.
result->EnableNextDispatch();
auto new_ddk_txn = fake_ddk::MakeDdkInternalTransaction(std::move(result));
*new_txn = *new_ddk_txn.DeviceFidlTxn();
}
__EXPORT __WEAK zx_status_t load_firmware(zx_device_t* device, const char* path, zx_handle_t* fw,
size_t* size) {
// This is currently a no-op.
*fw = ZX_HANDLE_INVALID;
*size = fake_ddk::kFakeFWSize;
return ZX_OK;
}
__EXPORT
zx_status_t device_rebind(zx_device_t* device) {
if (!fake_ddk::Bind::Instance()) {
return ZX_OK;
}
return fake_ddk::Bind::Instance()->DeviceRebind(device);
}
__EXPORT uint32_t device_get_fragment_count(zx_device_t* dev) {
if (!fake_ddk::Bind::Instance()) {
return 0;
}
return fake_ddk::Bind::Instance()->DeviceGetFragmentCount(dev);
}
__EXPORT void device_get_fragments(zx_device_t* dev, composite_device_fragment_t* comp_list,
size_t comp_count, size_t* comp_actual) {
ZX_ASSERT(comp_list != nullptr);
ZX_ASSERT(comp_actual != nullptr);
if (!fake_ddk::Bind::Instance()) {
*comp_actual = 0;
return;
}
return fake_ddk::Bind::Instance()->DeviceGetFragments(dev, comp_list, comp_count, comp_actual);
}
__EXPORT bool device_get_fragment(zx_device_t* dev, const char* name, zx_device_t** out) {
if (!fake_ddk::Bind::Instance()) {
return false;
}
return fake_ddk::Bind::Instance()->DeviceGetFragment(dev, name, out);
}
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
__EXPORT
zx_handle_t get_root_resource() { return ZX_HANDLE_INVALID; }
extern "C" bool driver_log_severity_enabled_internal(const zx_driver_t* drv,
fx_log_severity_t flag) {
return flag >= fake_ddk::kMinLogSeverity;
}
extern "C" void driver_logvf_internal(const zx_driver_t* drv, fx_log_severity_t flag,
const char* file, int line, const char* msg, va_list args) {
vfprintf(stdout, msg, args);
putchar('\n');
fflush(stdout);
}
extern "C" void driver_logf_internal(const zx_driver_t* drv, fx_log_severity_t flag,
const char* file, int line, const char* msg, ...) {
va_list args;
va_start(args, msg);
driver_logvf_internal(drv, flag, file, line, msg, args);
va_end(args);
}
__EXPORT
__WEAK zx_driver_rec __zircon_driver_rec__ = {
.ops = {},
.driver = {},
.log_flags = 0,
};