blob: 283c9827dd69d885abb5a562371916b7be3ddc8d [file] [log] [blame]
// Copyright 2016 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 <ddk/binding.h>
#include <ddk/driver.h>
#include <ddktl/device.h>
#include <ddktl/protocol/test.h>
#include <fbl/algorithm.h>
#include <fbl/string_piece.h>
#include <fbl/unique_ptr.h>
#include <fuchsia/device/test/c/fidl.h>
#include <lib/zx/channel.h>
#include <lib/zx/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/device/test.h>
namespace {
class TestDevice;
using TestDeviceType = ddk::Device<TestDevice, ddk::Ioctlable, ddk::Messageable>;
class TestDevice : public TestDeviceType,
public ddk::TestProtocol<TestDevice> {
public:
TestDevice(zx_device_t* parent) : TestDeviceType(parent) { }
// Methods required by the ddk mixins
zx_status_t DdkIoctl(uint32_t op, const void* in, size_t inlen, void* out,
size_t outlen, size_t* out_actual);
zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn);
void DdkRelease();
// Methods required by the TestProtocol mixin
void TestSetOutputSocket(zx::socket socket);
void TestGetOutputSocket(zx::socket* out_socket);
void TestSetTestFunc(const test_func_t* func);
zx_status_t TestRunTests(test_report_t* out_report);
void TestDestroy();
private:
zx::socket output_;
test_func_t test_func_;
};
class TestRootDevice;
using TestRootDeviceType = ddk::Device<TestRootDevice, ddk::Ioctlable, ddk::Messageable>;
class TestRootDevice : public TestRootDeviceType {
public:
TestRootDevice(zx_device_t* parent) : TestRootDeviceType(parent) { }
zx_status_t Bind() {
return DdkAdd("test");
}
// Methods required by the ddk mixins
zx_status_t DdkIoctl(uint32_t op, const void* in, size_t inlen, void* out,
size_t outlen, size_t* out_actual);
zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn);
void DdkRelease() { ZX_ASSERT_MSG(false, "TestRootDevice::DdkRelease() not supported\n"); }
static zx_status_t FidlCreateDevice(void* ctx, const char* name_data, size_t name_len,
fidl_txn_t* txn);
private:
// Create a new child device with this |name|
zx_status_t CreateDevice(const fbl::StringPiece& name,
char* path_out, size_t path_size, size_t* path_actual);
};
void TestDevice::TestSetOutputSocket(zx::socket socket) {
output_ = std::move(socket);
}
void TestDevice::TestGetOutputSocket(zx::socket* out_socket) {
output_.duplicate(ZX_RIGHT_SAME_RIGHTS, out_socket);
}
void TestDevice::TestSetTestFunc(const test_func_t* func) {
test_func_ = *func;
}
zx_status_t TestDevice::TestRunTests(test_report_t* report) {
if (test_func_.callback == NULL) {
return ZX_ERR_NOT_SUPPORTED;
}
return test_func_.callback(test_func_.ctx, report);
}
void TestDevice::TestDestroy() {
DdkRemove();
}
static zx_status_t fidl_SetOutputSocket(void* ctx, zx_handle_t raw_socket) {
zx::socket socket(raw_socket);
auto dev = static_cast<TestDevice*>(ctx);
dev->TestSetOutputSocket(std::move(socket));
return ZX_OK;
}
static zx_status_t fidl_RunTests(void* ctx, fidl_txn_t* txn) {
auto dev = static_cast<TestDevice*>(ctx);
test_report_t report = {};
fuchsia_device_test_TestReport fidl_report = {};
zx_status_t status = dev->TestRunTests(&report);
if (status == ZX_OK) {
fidl_report.test_count = report.n_tests;
fidl_report.success_count = report.n_success;
fidl_report.failure_count = report.n_failed;
}
return fuchsia_device_test_DeviceRunTests_reply(txn, status, &fidl_report);
}
static zx_status_t fidl_Destroy(void* ctx) {
auto dev = static_cast<TestDevice*>(ctx);
dev->TestDestroy();
return ZX_OK;
}
zx_status_t TestDevice::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
static const fuchsia_device_test_Device_ops_t kOps = {
.SetOutputSocket = fidl_SetOutputSocket,
.RunTests = fidl_RunTests,
.Destroy = fidl_Destroy,
};
return fuchsia_device_test_Device_dispatch(this, txn, msg, &kOps);
}
zx_status_t TestDevice::DdkIoctl(uint32_t op, const void* in, size_t inlen, void* out,
size_t outlen, size_t* out_actual) {
switch (op) {
case IOCTL_TEST_SET_OUTPUT_SOCKET:
if (inlen != sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
TestSetOutputSocket(zx::socket(*static_cast<const zx_handle_t*>(in)));
return ZX_OK;
case IOCTL_TEST_RUN_TESTS: {
if (outlen != sizeof(test_report_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
*out_actual = sizeof(test_report_t);
return TestRunTests(static_cast<test_report_t*>(out));
}
case IOCTL_TEST_DESTROY_DEVICE:
TestDestroy();
return 0;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
void TestDevice::DdkRelease() {
delete this;
}
zx_status_t TestRootDevice::CreateDevice(const fbl::StringPiece& name,
char* path_out, size_t path_size, size_t* path_actual) {
static_assert(fuchsia_device_test_MAX_DEVICE_NAME_LEN == ZX_DEVICE_NAME_MAX);
char devname[ZX_DEVICE_NAME_MAX + 1] = {};
if (name.size() > 0) {
memcpy(devname, name.data(), fbl::min(sizeof(devname) - 1, name.size()));
} else {
strncpy(devname, "testdev", sizeof(devname) - 1);
}
devname[sizeof(devname) - 1] = '\0';
// truncate trailing ".so"
if (!strcmp(devname + strlen(devname) - 3, ".so")) {
devname[strlen(devname) - 3] = 0;
}
if (path_size < strlen(devname) + sizeof(fuchsia_device_test_CONTROL_DEVICE) + 1) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
auto device = fbl::make_unique<TestDevice>(zxdev());
zx_status_t status = device->DdkAdd(devname);
if (status != ZX_OK) {
return status;
}
// devmgr now owns this
__UNUSED auto ptr = device.release();
*path_actual = snprintf(path_out, path_size ,"%s/%s", fuchsia_device_test_CONTROL_DEVICE,
devname);
return ZX_OK;
}
zx_status_t TestRootDevice::DdkIoctl(uint32_t op, const void* in, size_t inlen,
void* out, size_t outlen, size_t* out_actual) {
if (op != IOCTL_TEST_CREATE_DEVICE) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status = CreateDevice(fbl::StringPiece(static_cast<const char*>(in)),
static_cast<char*>(out), outlen, out_actual);
if (status != ZX_OK) {
return status;
}
if (*out_actual == outlen) {
// Force null-termination if we filled the buffer.
static_cast<char*>(out)[outlen - 1] = 0;
} else {
// Account for the trailing null byte which is reported in the ioctl return
*out_actual += 1;
}
return ZX_OK;
}
zx_status_t TestRootDevice::FidlCreateDevice(void* ctx, const char* name_data, size_t name_len,
fidl_txn_t* txn) {
auto root = static_cast<TestRootDevice*>(ctx);
char path[fuchsia_device_test_MAX_DEVICE_PATH_LEN];
size_t path_size = 0;
zx_status_t status = root->CreateDevice(fbl::StringPiece(name_data, name_len),
path, sizeof(path), &path_size);
return fuchsia_device_test_RootDeviceCreateDevice_reply(txn, status, path, path_size);
}
zx_status_t TestRootDevice::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
static const fuchsia_device_test_RootDevice_ops_t kOps = {
.CreateDevice = TestRootDevice::FidlCreateDevice,
};
return fuchsia_device_test_RootDevice_dispatch(this, txn, msg, &kOps);
}
zx_status_t TestDriverBind(void* ctx, zx_device_t* dev) {
auto root = fbl::make_unique<TestRootDevice>(dev);
zx_status_t status = root->Bind();
if (status != ZX_OK) {
return status;
}
// devmgr now owns root
__UNUSED auto ptr = root.release();
return ZX_OK;
}
const zx_driver_ops_t kTestDriverOps = []() {
zx_driver_ops_t driver;
driver.version = DRIVER_OPS_VERSION;
driver.bind = TestDriverBind;
return driver;
}();
} // namespace
ZIRCON_DRIVER_BEGIN(test, kTestDriverOps, "zircon", "0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST_PARENT),
ZIRCON_DRIVER_END(test)