| // 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 <fuchsia/device/test/c/fidl.h> | |
| #include <lib/zircon-internal/thread_annotations.h> | |
| #include <lib/zx/channel.h> | |
| #include <lib/zx/socket.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <algorithm> | |
| #include <ddk/binding.h> | |
| #include <ddk/driver.h> | |
| #include <ddk/platform-defs.h> | |
| #include <ddktl/device.h> | |
| #include <ddktl/protocol/test.h> | |
| #include <fbl/algorithm.h> | |
| #include <fbl/auto_lock.h> | |
| #include <fbl/mutex.h> | |
| #include <fbl/string_piece.h> | |
| namespace { | |
| class TestDevice; | |
| using TestDeviceType = ddk::Device<TestDevice, ddk::Messageable, ddk::Unbindable>; | |
| class TestDevice : public TestDeviceType, public ddk::TestProtocol<TestDevice, ddk::base_protocol> { | |
| public: | |
| TestDevice(zx_device_t* parent) : TestDeviceType(parent) {} | |
| // Methods required by the ddk mixins | |
| zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn); | |
| void DdkRelease(); | |
| void DdkUnbind(ddk::UnbindTxn txn); | |
| // Methods required by the TestProtocol mixin | |
| void TestSetOutputSocket(zx::socket socket); | |
| void TestGetOutputSocket(zx::socket* out_socket); | |
| void TestGetChannel(zx::channel* out_channel); | |
| void TestSetTestFunc(const test_func_t* func); | |
| zx_status_t TestRunTests(test_report_t* out_report); | |
| void TestDestroy(); | |
| void SetChannel(zx::channel c); | |
| private: | |
| zx::socket output_; | |
| zx::channel channel_; | |
| test_func_t test_func_; | |
| }; | |
| class TestRootDevice; | |
| using TestRootDeviceType = ddk::Device<TestRootDevice, ddk::Messageable, ddk::Unbindable>; | |
| 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 DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn); | |
| void DdkRelease() { delete this; } | |
| void DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); } | |
| static zx_status_t FidlCreateDevice(void* ctx, const char* name_data, size_t name_len, | |
| zx_handle_t client_remote_raw, fidl_txn_t* txn); | |
| private: | |
| // Create a new child device with this |name| | |
| zx_status_t CreateDevice(const fbl::StringPiece& name, zx::channel client_remote, 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::TestGetChannel(zx::channel* out_channel) { *out_channel = std::move(channel_); } | |
| 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() { | |
| output_.reset(); | |
| DdkAsyncRemove(); | |
| } | |
| 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; | |
| } | |
| void TestDevice::SetChannel(zx::channel c) { channel_ = std::move(c); } | |
| static zx_status_t fidl_SetChannel(void* ctx, zx_handle_t raw_channel) { | |
| zx::channel channel(raw_channel); | |
| auto dev = static_cast<TestDevice*>(ctx); | |
| dev->SetChannel(std::move(channel)); | |
| 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 = { | |
| .RunTests = fidl_RunTests, | |
| .SetOutputSocket = fidl_SetOutputSocket, | |
| .SetChannel = fidl_SetChannel, | |
| .Destroy = fidl_Destroy, | |
| }; | |
| return fuchsia_device_test_Device_dispatch(this, txn, msg, &kOps); | |
| } | |
| void TestDevice::DdkRelease() { delete this; } | |
| void TestDevice::DdkUnbind(ddk::UnbindTxn txn) { | |
| TestDestroy(); | |
| txn.Reply(); | |
| } | |
| zx_status_t TestRootDevice::CreateDevice(const fbl::StringPiece& name, zx::channel client_remote, | |
| 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(), std::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 = std::make_unique<TestDevice>(zxdev()); | |
| zx_status_t status = | |
| device->DdkAdd(ddk::DeviceAddArgs(devname).set_client_remote(std::move(client_remote))); | |
| 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::FidlCreateDevice(void* ctx, const char* name_data, size_t name_len, | |
| zx_handle_t client_remote_raw, fidl_txn_t* txn) { | |
| auto root = static_cast<TestRootDevice*>(ctx); | |
| zx::channel client_remote{client_remote_raw}; | |
| 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), | |
| std::move(client_remote), 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 = std::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", 4) | |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST_PARENT), | |
| // This bind functionality is used for the libdriver integration tests to | |
| // let us hook into composite devices after they are instantiated. | |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_TEST), | |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_LIBDRIVER_TEST), | |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_TEST_COMPOSITE), ZIRCON_DRIVER_END(test) |