blob: de585f45be21d569e59fb5297b5da7f59a3d2974 [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 <threads.h>
#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddktl/device.h>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <fbl/unique_ptr.h>
#include <fbl/vector.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <zircon/process.h>
#include <ddk/protocol/test.h>
#include <zircon/thread_annotations.h>
#include <zircon/types.h>
#include "fidl.h"
namespace mock_device {
class MockDevice;
using MockDeviceType = ddk::FullDevice<MockDevice>;
class MockDevice : public MockDeviceType {
public:
MockDevice(zx_device_t* device, zx::channel controller);
static zx_status_t Create(zx_device_t* parent, zx::channel controller,
fbl::unique_ptr<MockDevice>* out);
// Device protocol implementation.
void DdkRelease();
zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
zx_status_t DdkOpen(zx_device_t** dev_out, uint32_t flags);
zx_status_t DdkOpenAt(zx_device_t** dev_out, const char* path, uint32_t flags);
zx_status_t DdkClose(uint32_t flags);
void DdkUnbind();
zx_status_t DdkRead(void* buf, size_t count, zx_off_t off, size_t* actual);
zx_status_t DdkWrite(const void* buf, size_t count, zx_off_t off, size_t* actual);
zx_off_t DdkGetSize();
zx_status_t DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
size_t out_len, size_t* actual);
zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn);
zx_status_t DdkSuspend(uint32_t flags);
zx_status_t DdkResume(uint32_t flags);
zx_status_t DdkRxrpc(zx_handle_t channel);
// Generate an invocation record for a hook RPC
fuchsia_device_mock_HookInvocation ConstructHookInvocation();
static fuchsia_device_mock_HookInvocation ConstructHookInvocation(uint64_t device_id);
// Create a new thread that will serve a MockDeviceThread interface over |c|
void CreateThread(zx::channel channel);
private:
// Retrieve the current thread's process and thread koids
static void GetThreadKoids(zx_koid_t* process, zx_koid_t* thread);
static int ThreadFunc(void* arg);
struct ThreadFuncArg {
// channel that the thread will use to serve a MockDeviceThread
// interface over
zx::channel channel;
// Device this thread is executing for
MockDevice* dev;
};
fbl::Mutex lock_;
// List of threads spawned by Actions
fbl::Vector<thrd_t> threads_ TA_GUARDED(lock_);
// Our half of the controller channel. We will send requests for input on
// it.
zx::channel controller_;
};
struct ProcessActionsContext {
ProcessActionsContext(const zx::channel& channel, bool has_hook_status, MockDevice* mock_device,
zx_device_t* device)
: channel(zx::unowned_channel(channel)), has_hook_status(has_hook_status),
mock_device(mock_device), device(device) {
}
// IN: The channel that these actions came from. Used for acknowledging
// add/remove device requests.
zx::unowned_channel channel;
bool has_hook_status = false;
// OUT: What should be returned by the hook
zx_status_t hook_status = ZX_ERR_INTERNAL;;
// IN: A buffer that can be written to by actions (nullptr if none)
void* associated_buf = nullptr;
size_t associated_buf_count = 0;
// OUT: Number of bytes written by actions
size_t associated_buf_actual = 0;
// IN: MockDevice to use for associating threads with
MockDevice* mock_device = nullptr;
// IN: Device to use for invoking add_device/remove_device
zx_device_t* device = nullptr;
};
// Execute the actions returned by a hook
zx_status_t ProcessActions(fbl::Array<const fuchsia_device_mock_Action> actions,
ProcessActionsContext* context);
MockDevice::MockDevice(zx_device_t* device, zx::channel controller)
: MockDeviceType(device), controller_(std::move(controller)) {
}
int MockDevice::ThreadFunc(void* raw_arg) {
auto arg = fbl::unique_ptr(static_cast<ThreadFuncArg*>(raw_arg));
// TODO(teisenbe): Implement MockDeviceThread here
return 0;
}
void MockDevice::CreateThread(zx::channel channel) {
auto arg = fbl::make_unique<ThreadFuncArg>();
arg->channel = std::move(channel);
arg->dev = this;
fbl::AutoLock guard(&lock_);
thrd_t thrd;
int ret = thrd_create(&thrd, MockDevice::ThreadFunc, arg.get());
ZX_ASSERT(ret == thrd_success);
// The thread now owns this pointer
__UNUSED auto ptr = arg.release();
threads_.push_back(thrd);
}
void MockDevice::GetThreadKoids(zx_koid_t* process, zx_koid_t* thread) {
struct Koids {
zx_koid_t process;
zx_koid_t thread;
};
thread_local Koids thread_koids;
if (thread_koids.process == 0 && thread_koids.thread == 0) {
zx_info_handle_basic_t info;
zx_status_t status = zx_object_get_info(zx_thread_self(), ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
ZX_ASSERT(status == ZX_OK);
thread_koids.process = info.related_koid;
thread_koids.thread = info.koid;
}
*process = thread_koids.process;
*thread = thread_koids.thread;
}
fuchsia_device_mock_HookInvocation MockDevice::ConstructHookInvocation(uint64_t device_id) {
fuchsia_device_mock_HookInvocation invoc;
GetThreadKoids(&invoc.process_koid, &invoc.thread_koid);
invoc.device_id = device_id;
return invoc;
}
fuchsia_device_mock_HookInvocation MockDevice::ConstructHookInvocation() {
return ConstructHookInvocation(reinterpret_cast<uintptr_t>(zxdev()));
}
void MockDevice::DdkRelease() {
zx_status_t status = ReleaseHook(controller_, ConstructHookInvocation());
ZX_ASSERT(status == ZX_OK);
{
fbl::AutoLock guard(&lock_);
for (auto& t : threads_) {
thrd_join(t, nullptr);
}
}
delete this;
}
zx_status_t MockDevice::DdkGetProtocol(uint32_t proto_id, void* out) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = GetProtocolHook(controller_, ConstructHookInvocation(),
proto_id, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = OpenHook(controller_, ConstructHookInvocation(), flags, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::DdkOpenAt(zx_device_t** dev_out, const char* path, uint32_t flags) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = OpenAtHook(controller_, ConstructHookInvocation(), fbl::StringPiece(path),
flags, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::DdkClose(uint32_t flags) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = CloseHook(controller_, ConstructHookInvocation(), flags, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
void MockDevice::DdkUnbind() {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = UnbindHook(controller_, ConstructHookInvocation(), &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, false, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
}
zx_status_t MockDevice::DdkRead(void* buf, size_t count, zx_off_t off, size_t* actual) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = ReadHook(controller_, ConstructHookInvocation(), count, off, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
ctx.associated_buf = buf,
ctx.associated_buf_count = count,
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
*actual = ctx.associated_buf_actual;
return ctx.hook_status;
}
zx_status_t MockDevice::DdkWrite(const void* buf, size_t count, zx_off_t off, size_t* actual) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = WriteHook(controller_, ConstructHookInvocation(),
static_cast<const uint8_t*>(buf), count, off, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
*actual = count;
return ctx.hook_status;
}
zx_off_t MockDevice::DdkGetSize() {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = GetSizeHook(controller_, ConstructHookInvocation(), &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, false, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
ZX_ASSERT_MSG(false, "need to plumb returning values in\n");
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t MockDevice::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
size_t out_len, size_t* actual) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = IoctlHook(controller_, ConstructHookInvocation(), op,
static_cast<const uint8_t*>(in_buf), in_len,
out_len, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
ctx.associated_buf = out_buf;
ctx.associated_buf_count = out_len;
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
*actual = ctx.associated_buf_actual;
return ctx.hook_status;
}
zx_status_t MockDevice::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = MessageHook(controller_, ConstructHookInvocation(), &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::DdkSuspend(uint32_t flags) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = SuspendHook(controller_, ConstructHookInvocation(), flags, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::DdkResume(uint32_t flags) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = ResumeHook(controller_, ConstructHookInvocation(), flags, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::DdkRxrpc(zx_handle_t channel) {
fbl::Array<const fuchsia_device_mock_Action> actions;
zx_status_t status = RxrpcHook(controller_, ConstructHookInvocation(), &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext ctx(controller_, true, this, zxdev());
status = ProcessActions(std::move(actions), &ctx);
ZX_ASSERT(status == ZX_OK);
return ctx.hook_status;
}
zx_status_t MockDevice::Create(zx_device_t* parent, zx::channel controller,
fbl::unique_ptr<MockDevice>* out) {
auto dev = fbl::make_unique<MockDevice>(parent, std::move(controller));
*out = std::move(dev);
return ZX_OK;
}
zx_status_t ProcessActions(fbl::Array<const fuchsia_device_mock_Action> actions,
ProcessActionsContext* ctx) {
for (size_t i = 0; i < actions.size(); ++i) {
const auto& action = actions[i];
switch (action.tag) {
case fuchsia_device_mock_ActionTag_return_status: {
if (i != actions.size() - 1) {
printf("MockDevice::ProcessActions: return_status was not the final entry\n");
return ZX_ERR_INVALID_ARGS;
}
if (!ctx->has_hook_status) {
printf("MockDevice::ProcessActions: return_status present for no-status hook\n");
return ZX_ERR_INVALID_ARGS;
}
ctx->hook_status = action.return_status;
return ZX_OK;
}
case fuchsia_device_mock_ActionTag_write: {
if (ctx->associated_buf == nullptr) {
printf("MockDevice::ProcessActions: write action with no associated buf\n");
return ZX_ERR_INVALID_ARGS;
}
if (action.write.count > ctx->associated_buf_count) {
printf("MockDevice::ProcessActions: write action too large\n");
return ZX_ERR_INVALID_ARGS;
}
ctx->associated_buf_actual = action.write.count;
memcpy(ctx->associated_buf, action.write.data, action.write.count);
break;
}
case fuchsia_device_mock_ActionTag_create_thread: {
if (ctx->mock_device == nullptr) {
printf("MockDevice::CreateThread: asked to create thread without device\n");
return ZX_ERR_INVALID_ARGS;
}
zx::channel thread_channel(action.create_thread);
ctx->mock_device->CreateThread(std::move(thread_channel));
break;
}
case fuchsia_device_mock_ActionTag_remove_device: {
device_remove(ctx->device);
zx_status_t status = SendRemoveDeviceDone(*ctx->channel,
action.remove_device.action_id);
ZX_ASSERT(status == ZX_OK);
break;
}
case fuchsia_device_mock_ActionTag_add_device: {
// TODO(teisenbe): Implement more functionality here
ZX_ASSERT_MSG(!action.add_device.do_bind, "bind not yet supported\n");
fbl::unique_ptr<MockDevice> dev;
zx_status_t status = MockDevice::Create(ctx->device,
zx::channel(action.add_device.controller),
&dev);
if (status != ZX_OK) {
return status;
}
char name[ZX_DEVICE_NAME_MAX + 1];
if (action.add_device.name.size > sizeof(name) - 1) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(name, action.add_device.name.data, action.add_device.name.size);
name[action.add_device.name.size] = 0;
status = dev->DdkAdd(name, DEVICE_ADD_NON_BINDABLE);
if (status != ZX_OK) {
return status;
}
// Devmgr now owns this
__UNUSED auto ptr = dev.release();
status = SendAddDeviceDone(*ctx->channel, action.add_device.action_id);
ZX_ASSERT(status == ZX_OK);
break;
}
}
}
if (!ctx->has_hook_status) {
return ZX_OK;
}
printf("MockDevice::ProcessActions: return_status was not the final entry\n");
return ZX_ERR_INVALID_ARGS;
}
zx_status_t MockDeviceBind(void* ctx, zx_device_t* parent) {
// It's expected that this driver is binding against a device created by the
// fuchsia.device.test interface. Get the protocol from the device we're
// binding to so we can wire up the control channel.
test_protocol_t proto;
zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_TEST, &proto);
ZX_ASSERT(status == ZX_OK);
zx::channel c;
test_get_channel(&proto, c.reset_and_get_address());
// Ask the control channel what to do about this bind().
fbl::Array<const fuchsia_device_mock_Action> actions;
auto invoc = MockDevice::ConstructHookInvocation(reinterpret_cast<uintptr_t>(parent));
status = BindHook(c, invoc, &actions);
ZX_ASSERT(status == ZX_OK);
ProcessActionsContext pac_ctx(c, true, nullptr, parent);
status = ProcessActions(std::move(actions), &pac_ctx);
ZX_ASSERT(status == ZX_OK);
return pac_ctx.hook_status;
}
const zx_driver_ops_t kMockDeviceOps = []() {
zx_driver_ops_t ops;
ops.version = DRIVER_OPS_VERSION;
ops.bind = MockDeviceBind;
return ops;
}();
} // namespace mock_device
ZIRCON_DRIVER_BEGIN(mock_device, mock_device::kMockDeviceOps, "zircon", "0.1", 2)
BI_ABORT_IF_AUTOBIND,
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST),
ZIRCON_DRIVER_END(test_sysdev)