blob: 7ba11668c885741e4d844f395d5f897b2b02b9e3 [file] [log] [blame]
// 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 <fcntl.h>
#include <fuchsia/hardware/ethertap/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/virtualaudio/cpp/fidl.h>
#include <lib/ddk/platform-defs.h>
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/sys/cpp/component_context.h>
#include <sdk/lib/device-watcher/cpp/device-watcher.h>
#include "isolated_devmgr.h"
#include "src/lib/files/glob.h"
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
namespace isolated_devmgr {
namespace testing {
class DevmgrTest : public ::gtest::RealLoopFixture {
protected:
static constexpr const char* kSysdevDriver = "/boot/driver/sysdev.so";
static constexpr const char* kPlatformDriver = "/boot/driver/platform-bus.so";
const board_test::DeviceEntry kTestDeviceEntry = []() {
board_test::DeviceEntry entry = {};
strcpy(entry.name, "test");
entry.vid = PDEV_VID_TEST;
entry.pid = PDEV_PID_TEST;
return entry;
}();
const board_test::DeviceEntry kCrashDeviceEntry = []() {
board_test::DeviceEntry entry = {};
strcpy(entry.name, "crash-device");
entry.vid = PDEV_VID_TEST;
entry.pid = PDEV_PID_GENERIC;
entry.did = PDEV_DID_TEST_CRASH;
return entry;
}();
std::unique_ptr<IsolatedDevmgr> CreateDevmgrSysdev() {
devmgr_launcher::Args args;
IsolatedDevmgr::ExtraArgs extra_args;
args.sys_device_driver = "/boot/driver/test-parent-sys.so";
args.stdio = fbl::unique_fd(open("/dev/null", O_RDWR));
args.disable_block_watcher = true;
return IsolatedDevmgr::Create(std::move(args));
}
std::unique_ptr<IsolatedDevmgr> CreateDevmgrPlatTest(const char* crash_policy = "do-nothing") {
devmgr_launcher::Args args;
auto device_list_ptr = std::unique_ptr<fbl::Vector<board_test::DeviceEntry>>(
new fbl::Vector<board_test::DeviceEntry>());
args.sys_device_driver = kPlatformDriver;
args.stdio = fbl::unique_fd(open("/dev/null", O_RDWR));
args.disable_block_watcher = true;
args.boot_args = {{"driver-manager.driver-host-crash-policy", crash_policy}};
device_list_ptr->push_back(kTestDeviceEntry);
device_list_ptr->push_back(kCrashDeviceEntry);
return IsolatedDevmgr::Create(std::move(args), std::move(device_list_ptr), dispatcher());
}
fidl::InterfaceHandle<fuchsia::hardware::ethertap::TapDevice> CreateTapDevice(
const zx::channel& devfs) {
fidl::SynchronousInterfacePtr<fuchsia::hardware::ethertap::TapControl> tapctl;
fdio_service_connect_at(devfs.get(), "sys/test/tapctl",
tapctl.NewRequest().TakeChannel().release());
fuchsia::hardware::ethertap::Config config;
config.mtu = 1500;
config.options = 0;
config.features = 0;
const uint8_t mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
memcpy(config.mac.octets.data(), mac, sizeof(mac));
fidl::InterfaceHandle<fuchsia::hardware::ethertap::TapDevice> device;
zx_status_t status, o_status;
if ((status = tapctl->OpenDevice("tap_device", std::move(config), device.NewRequest(),
&o_status)) != ZX_OK ||
o_status != ZX_OK) {
fprintf(stderr, "status: %d, o_status: %d\n", status, o_status);
// discard channel
device = nullptr;
}
return device;
}
void EnableVirtualAudio(zx::channel devfs) {
fbl::unique_fd devfs_fd, fd;
ASSERT_EQ(ZX_OK, fdio_fd_create(devfs.release(), devfs_fd.reset_and_get_address()));
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFile(
devfs_fd, "sys/platform/00:00:2f/virtual_audio", &fd));
fdio_cpp::UnownedFdioCaller caller(devfs_fd.get());
fuchsia::virtualaudio::ControlSyncPtr virtual_audio_control;
fdio_service_connect_at(caller.borrow_channel(), "sys/platform/00:00:2f/virtual_audio",
virtual_audio_control.NewRequest().TakeChannel().release());
// Perform a simple RPC with a reply to sanity check we're talking to the driver.
ASSERT_EQ(ZX_OK, virtual_audio_control->RemoveAll());
}
};
TEST_F(DevmgrTest, CreateTapSysdev) {
auto devmgr = CreateDevmgrSysdev();
ASSERT_TRUE(devmgr);
ASSERT_EQ(devmgr->WaitForFile("sys/test/tapctl"), ZX_OK);
fidl::InterfaceHandle<fuchsia::io::Directory> dir;
devmgr->Connect(dir.NewRequest().TakeChannel());
auto tap = CreateTapDevice(dir.channel());
ASSERT_TRUE(tap);
// after having created tap, we should be able to see an ethernet device show
// up:
ASSERT_EQ(devmgr->WaitForFile("class/ethernet/000"), ZX_OK);
}
TEST_F(DevmgrTest, DeviceEntryEnumerationTest) {
auto devmgr = CreateDevmgrPlatTest();
ASSERT_TRUE(devmgr);
fbl::unique_fd fd;
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFile(devmgr->devfs_root(), "sys/platform", &fd));
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFile(
devmgr->devfs_root(), "sys/platform/platform-passthrough/test-board", &fd));
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFile(devmgr->devfs_root(),
"sys/platform/11:18:0/test-device", &fd));
}
TEST_F(DevmgrTest, ExceptionCallback) {
auto devmgr = CreateDevmgrPlatTest();
ASSERT_TRUE(devmgr);
bool exception = false;
devmgr->SetExceptionCallback([&exception](zx_exception_info_t) { exception = true; });
ASSERT_EQ(devmgr->WaitForFile("sys/platform/11:00:1f"), ZX_OK);
zx_handle_t dir;
ASSERT_EQ(fdio_get_service_handle(devmgr->root(), &dir), ZX_OK);
RunLoopUntil([&exception, &dir]() {
// keep trying to open crash-device until we see an exception
zx::channel a, b;
EXPECT_EQ(zx::channel::create(0, &a, &b), ZX_OK);
fdio_service_connect_at(dir, "sys/platform/11:00:1f/crash-device", b.release());
return exception;
});
}
// Driver manager will never crash, triggering a system reboot.
TEST_F(DevmgrTest, ExceptionCallback_SystemReboot) {
auto devmgr = CreateDevmgrPlatTest("reboot-system");
ASSERT_TRUE(devmgr);
zx_info_handle_basic_t process_info;
ASSERT_EQ(devmgr->driver_manager_process().get_info(ZX_INFO_HANDLE_BASIC, &process_info,
sizeof(process_info), nullptr, nullptr),
ZX_OK);
std::atomic<bool> driver_manager_crashed = false;
devmgr->SetExceptionCallback(
[&driver_manager_crashed, &process_info](zx_exception_info_t exception_info) {
driver_manager_crashed = process_info.koid == exception_info.pid;
});
ASSERT_EQ(devmgr->WaitForFile("sys/platform/11:00:1f"), ZX_OK);
zx_handle_t dir;
ASSERT_EQ(fdio_get_service_handle(devmgr->root(), &dir), ZX_OK);
// A single crash should cause driver manager to crash, but we don't know when crash-device will
// be available.
RunLoopUntil([&driver_manager_crashed, &dir]() {
zx::channel a, b;
EXPECT_EQ(zx::channel::create(0, &a, &b), ZX_OK);
fdio_service_connect_at(dir, "sys/platform/11:00:1f/crash-device", b.release());
return driver_manager_crashed.load();
});
}
// Driver manager will never crash, and the driver host should be reloaded 3 times.
TEST_F(DevmgrTest, ExceptionCallback_RestartDriverHost) {
auto devmgr = CreateDevmgrPlatTest("restart-driver-host");
ASSERT_TRUE(devmgr);
zx_info_handle_basic_t process_info;
ASSERT_EQ(devmgr->driver_manager_process().get_info(ZX_INFO_HANDLE_BASIC, &process_info,
sizeof(process_info), nullptr, nullptr),
ZX_OK);
std::atomic<bool> driver_manager_crashed = false;
std::atomic<uint32_t> exception_count = 0;
devmgr->SetExceptionCallback([&driver_manager_crashed, &process_info,
&exception_count](zx_exception_info_t exception_info) {
driver_manager_crashed = process_info.koid == exception_info.pid;
exception_count += 1;
});
ASSERT_EQ(devmgr->WaitForFile("sys/platform/11:00:1f"), ZX_OK);
zx_handle_t dir;
ASSERT_EQ(fdio_get_service_handle(devmgr->root(), &dir), ZX_OK);
RunLoopUntil([&driver_manager_crashed, &dir, &exception_count]() {
zx::channel a, b;
EXPECT_EQ(zx::channel::create(0, &a, &b), ZX_OK);
fdio_service_connect_at(dir, "sys/platform/11:00:1f/crash-device", b.release());
return driver_manager_crashed.load() || exception_count.load() == 4u;
});
ASSERT_FALSE(RunLoopWithTimeoutOrUntil(
[&driver_manager_crashed, &dir]() {
zx::channel a, b;
EXPECT_EQ(zx::channel::create(0, &a, &b), ZX_OK);
fdio_service_connect_at(dir, "sys/platform/11:00:1f/crash-device", b.release());
return driver_manager_crashed.load();
},
zx::sec(5)));
ASSERT_FALSE(driver_manager_crashed.load());
ASSERT_GE(exception_count.load(), 4u);
}
// Driver manager will never crash, and the driver host should never be reloaded.
TEST_F(DevmgrTest, ExceptionCallback_DoNothing) {
auto devmgr = CreateDevmgrPlatTest("do-nothing");
ASSERT_TRUE(devmgr);
zx_info_handle_basic_t process_info;
ASSERT_EQ(devmgr->driver_manager_process().get_info(ZX_INFO_HANDLE_BASIC, &process_info,
sizeof(process_info), nullptr, nullptr),
ZX_OK);
std::atomic<bool> driver_manager_crashed = false;
std::atomic<uint32_t> exception_count = 0;
devmgr->SetExceptionCallback([&driver_manager_crashed, &process_info,
&exception_count](zx_exception_info_t exception_info) {
driver_manager_crashed = process_info.koid == exception_info.pid;
exception_count += 1;
});
ASSERT_EQ(devmgr->WaitForFile("sys/platform/11:00:1f"), ZX_OK);
zx_handle_t dir;
ASSERT_EQ(fdio_get_service_handle(devmgr->root(), &dir), ZX_OK);
RunLoopUntil([&driver_manager_crashed, &dir, &exception_count]() {
zx::channel a, b;
EXPECT_EQ(zx::channel::create(0, &a, &b), ZX_OK);
fdio_service_connect_at(dir, "sys/platform/11:00:1f/crash-device", b.release());
return driver_manager_crashed.load() || exception_count.load() == 1u;
});
ASSERT_FALSE(RunLoopWithTimeoutOrUntil(
[&driver_manager_crashed, &dir]() {
zx::channel a, b;
EXPECT_EQ(zx::channel::create(0, &a, &b), ZX_OK);
fdio_service_connect_at(dir, "sys/platform/11:00:1f/crash-device", b.release());
return driver_manager_crashed.load();
},
zx::sec(5)));
ASSERT_FALSE(driver_manager_crashed.load());
ASSERT_EQ(exception_count.load(), 1u);
}
TEST_F(DevmgrTest, ExposedThroughComponent) {
auto ctx = sys::ComponentContext::CreateAndServeOutgoingDirectory();
fidl::InterfacePtr<fuchsia::sys::Launcher> launcher;
ctx->svc()->Connect(launcher.NewRequest());
zx::channel req;
auto services = sys::ServiceDirectory::CreateWithRequest(&req);
fuchsia::sys::LaunchInfo info;
info.directory_request = std::move(req);
info.url =
"fuchsia-pkg://fuchsia.com/isolated-devmgr-tests-package#meta/"
"ethertap-devmgr.cmx";
fidl::InterfacePtr<fuchsia::sys::ComponentController> ctlr;
launcher->CreateComponent(std::move(info), ctlr.NewRequest());
ctlr.set_error_handler([](zx_status_t err) { FAIL() << "Controller shouldn't exit"; });
zx::channel devfs_req, devfs;
zx::channel::create(0, &devfs_req, &devfs);
services->Connect("fuchsia.example.IsolatedDevmgr", std::move(devfs_req));
auto tap = CreateTapDevice(devfs);
ASSERT_TRUE(tap);
}
TEST_F(DevmgrTest, ExposeDriverFromComponentNamespace) {
auto ctx = sys::ComponentContext::CreateAndServeOutgoingDirectory();
fidl::InterfacePtr<fuchsia::sys::Launcher> launcher;
ctx->svc()->Connect(launcher.NewRequest());
zx::channel req;
auto services = sys::ServiceDirectory::CreateWithRequest(&req);
fuchsia::sys::LaunchInfo info;
info.directory_request = std::move(req);
info.url =
"fuchsia-pkg://fuchsia.com/isolated-devmgr-tests-package#meta/"
"virtual-audio-devmgr.cmx";
fidl::InterfacePtr<fuchsia::sys::ComponentController> ctlr;
launcher->CreateComponent(std::move(info), ctlr.NewRequest());
ctlr.set_error_handler([](zx_status_t err) { FAIL() << "Controller shouldn't exit"; });
zx::channel devfs_req, devfs;
zx::channel::create(0, &devfs_req, &devfs);
services->Connect("fuchsia.example.IsolatedDevmgr", std::move(devfs_req));
EnableVirtualAudio(std::move(devfs));
}
TEST_F(DevmgrTest, ExposeDevfsToHub) {
auto ctx = sys::ComponentContext::CreateAndServeOutgoingDirectory();
fidl::InterfacePtr<fuchsia::sys::Launcher> launcher;
ctx->svc()->Connect(launcher.NewRequest());
fuchsia::sys::LaunchInfo info;
info.url =
"fuchsia-pkg://fuchsia.com/isolated-devmgr-tests-package#meta/virtual-audio-devmgr.cmx";
auto echo_svc = sys::ServiceDirectory::CreateWithRequest(&info.directory_request);
fidl::InterfacePtr<fuchsia::sys::ComponentController> ctlr;
launcher->CreateComponent(std::move(info), ctlr.NewRequest());
ctlr.set_error_handler([](zx_status_t err) { FAIL() << "Controller shouldn't exit"; });
bool ready = false;
ctlr.events().OnDirectoryReady = [&ready] { ready = true; };
RunLoopUntil([&ready] { return ready; });
ASSERT_TRUE(ready);
// Verify that devfs is indeed visible in outgoing directory
constexpr char kGlob[] = "/hub/c/virtual-audio-devmgr.cmx/*/out/dev";
files::Glob glob(kGlob);
EXPECT_EQ(glob.size(), 1u) << kGlob << " expected to match exactly once.";
}
TEST_F(DevmgrTest, DiagnosticsFiles) {
auto devmgr = CreateDevmgrSysdev();
ASSERT_TRUE(devmgr);
fbl::unique_fd fd;
ASSERT_EQ(ZX_OK,
device_watcher::RecursiveWaitForFileReadOnly(devmgr->devfs_root(), "diagnostics", &fd));
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFileReadOnly(devmgr->devfs_root(),
"diagnostics/class", &fd));
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFileReadOnly(devmgr->devfs_root(),
"diagnostics/driver_manager", &fd));
ASSERT_EQ(ZX_OK,
device_watcher::RecursiveWaitForFileReadOnly(
devmgr->devfs_root(), "diagnostics/driver_manager/fuchsia.inspect.Tree", &fd));
ASSERT_EQ(ZX_OK, device_watcher::RecursiveWaitForFileReadOnly(
devmgr->devfs_root(), "diagnostics/driver_manager/driver_host", &fd));
// TODO(fxbug.dev/50569): Add test for root,sys,misc,test driver_host files once koids are
// available via dm.inspect
}
} // namespace testing
} // namespace isolated_devmgr