blob: 06321473283f4ea917c857242105d839c0e3d454 [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 <fidl/fuchsia.driver.index/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fuchsia/boot/cpp/fidl.h>
#include <fuchsia/kernel/cpp/fidl.h>
#include <getopt.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/service/llcpp/service.h>
#include <lib/zx/event.h>
#include <lib/zx/port.h>
#include <lib/zx/resource.h>
#include <lib/zx/vmo.h>
#include <threads.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <zircon/syscalls/policy.h>
#include <zircon/types.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <memory>
#include <fbl/string_printf.h>
#include "component_lifecycle.h"
#include "coordinator.h"
#include "devfs.h"
#include "driver_host_loader_service.h"
#include "fdio.h"
#include "lib/async/cpp/task.h"
#include "src/devices/bin/driver_manager/devfs_exporter.h"
#include "src/devices/bin/driver_manager/device_watcher.h"
#include "src/devices/bin/driver_manager/v2/driver_development_service.h"
#include "src/devices/lib/log/log.h"
#include "src/lib/storage/vfs/cpp/managed_vfs.h"
#include "src/lib/storage/vfs/cpp/pseudo_dir.h"
#include "src/lib/storage/vfs/cpp/remote_dir.h"
#include "src/lib/storage/vfs/cpp/vfs.h"
#include "src/lib/storage/vfs/cpp/vmo_file.h"
#include "src/sys/lib/stdout-to-debuglog/cpp/stdout-to-debuglog.h"
#include "system_instance.h"
#include "v2/driver_runner.h"
namespace {
#define DEVMGR_LAUNCHER_DEVFS_ROOT_HND PA_HND(PA_USER1, 0)
#define DEVMGR_LAUNCHER_OUTGOING_SERVICES_HND PA_HND(PA_USER1, 1)
// These are helpers for getting sets of parameters over FIDL
struct DriverManagerParams {
bool enable_ephemeral;
bool log_to_debuglog;
bool require_system;
bool suspend_timeout_fallback;
bool verbose;
DriverHostCrashPolicy crash_policy;
std::string root_driver;
bool use_dfv2;
};
DriverManagerParams GetDriverManagerParams(fidl::WireSyncClient<fuchsia_boot::Arguments>& client) {
fuchsia_boot::wire::BoolPair bool_req[]{
{"devmgr.enable-ephemeral", false}, {"devmgr.log-to-debuglog", false},
{"devmgr.require-system", false}, {"devmgr.suspend-timeout-fallback", true},
{"devmgr.verbose", false}, {"driver_manager.use_driver_framework_v2", false},
};
auto bool_resp =
client->GetBools(fidl::VectorView<fuchsia_boot::wire::BoolPair>::FromExternal(bool_req));
if (!bool_resp.ok()) {
return {};
}
auto crash_policy = DriverHostCrashPolicy::kRestartDriverHost;
auto response = client->GetString("driver-manager.driver-host-crash-policy");
if (response.ok() && !response.value().value.is_null() && !response.value().value.empty()) {
std::string crash_policy_str(response.value().value.get());
if (crash_policy_str == "reboot-system") {
crash_policy = DriverHostCrashPolicy::kRebootSystem;
} else if (crash_policy_str == "restart-driver-host") {
crash_policy = DriverHostCrashPolicy::kRestartDriverHost;
} else if (crash_policy_str == "do-nothing") {
crash_policy = DriverHostCrashPolicy::kDoNothing;
} else {
LOGF(ERROR, "Unexpected option for driver-manager.driver-host-crash-policy: %s",
crash_policy_str.c_str());
}
}
std::string root_driver = "";
{
auto response = client->GetString("driver_manager.root-driver");
if (response.ok() && !response.value().value.is_null() && !response.value().value.empty()) {
root_driver = std::string(response.value().value.data(), response.value().value.size());
}
}
return {
.enable_ephemeral = bool_resp.value().values[0],
.log_to_debuglog = bool_resp.value().values[1],
.require_system = bool_resp.value().values[2],
.suspend_timeout_fallback = bool_resp.value().values[3],
.verbose = bool_resp.value().values[4],
.crash_policy = crash_policy,
.root_driver = std::move(root_driver),
.use_dfv2 = bool_resp.value().values[5],
};
}
static const std::string kRootJobPath = "/svc/" + std::string(fuchsia::kernel::RootJob::Name_);
static const std::string kRootResourcePath =
"/svc/" + std::string(fuchsia::boot::RootResource::Name_);
// Get the root job from the root job service.
zx_status_t get_root_job(zx::job* root_job) {
fuchsia::kernel::RootJobSyncPtr root_job_ptr;
zx_status_t status =
fdio_service_connect(kRootJobPath.c_str(), root_job_ptr.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
return status;
}
return root_job_ptr->Get(root_job);
}
// Get the root resource from the root resource service. Not receiving the
// startup handle is logged, but not fatal. In test environments, it would not
// be present.
zx_status_t get_root_resource(zx::resource* root_resource) {
fuchsia::boot::RootResourceSyncPtr root_resource_ptr;
zx_status_t status = fdio_service_connect(kRootResourcePath.c_str(),
root_resource_ptr.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
return status;
}
return root_resource_ptr->Get(root_resource);
}
// Values parsed out of argv. All paths described below are absolute paths.
struct DriverManagerArgs {
// Load drivers from these directories. If this is empty, the default will
// be used (unless load_drivers is set).
fbl::Vector<std::string> driver_search_paths;
// Load the drivers with these paths. The specified drivers do not need to
// be in directories in |driver_search_paths|.
// If any of these drivers are set, then driver_search_paths default will not
// be used.
fbl::Vector<const char*> load_drivers;
// Connect the stdout and stderr file descriptors for this program to a
// debuglog handle acquired with fuchsia.boot.WriteOnlyLog.
bool log_to_debuglog = false;
// Do not exit driver manager after suspending the system.
bool no_exit_after_suspend = false;
// Path prefix for binaries/drivers/libraries etc.
std::string path_prefix = "/boot/";
// Use this driver as the sys_device driver. If nullptr, the default will
// be used.
std::string sys_device_driver;
// If true then DriverManager uses DriverIndex for binding rather than
// looking in /boot/drivers/. If this is false DriverManager will not
// be able to load base packages.
bool use_driver_index = false;
};
DriverManagerArgs ParseDriverManagerArgs(int argc, char** argv) {
enum {
kLoadDriver,
kLogToDebuglog,
kNoExitAfterSuspend,
kSysDeviceDriver,
kUseDriverIndex,
};
option options[] = {
{"load-driver", required_argument, nullptr, kLoadDriver},
{"log-to-debuglog", no_argument, nullptr, kLogToDebuglog},
{"no-exit-after-suspend", no_argument, nullptr, kNoExitAfterSuspend},
{"sys-device-driver", required_argument, nullptr, kSysDeviceDriver},
{"use-driver-index", no_argument, nullptr, kUseDriverIndex},
{0, 0, 0, 0},
};
auto print_usage_and_exit = [options]() {
printf("driver_manager: supported arguments:\n");
for (const auto& option : options) {
printf(" --%s\n", option.name);
}
abort();
};
auto check_not_duplicated = [print_usage_and_exit](const std::string& arg) {
if (!arg.empty()) {
printf("driver_manager: duplicated argument\n");
print_usage_and_exit();
}
};
DriverManagerArgs args{};
for (int opt; (opt = getopt_long(argc, argv, "", options, nullptr)) != -1;) {
switch (opt) {
case kLoadDriver:
args.load_drivers.push_back(optarg);
break;
case kLogToDebuglog:
args.log_to_debuglog = true;
break;
case kNoExitAfterSuspend:
args.no_exit_after_suspend = true;
break;
case kSysDeviceDriver:
check_not_duplicated(args.sys_device_driver);
args.sys_device_driver = optarg;
break;
case kUseDriverIndex:
args.use_driver_index = true;
break;
default:
print_usage_and_exit();
}
}
return args;
}
} // namespace
int main(int argc, char** argv) {
zx_status_t status = StdoutToDebuglog::Init();
if (status != ZX_OK) {
LOGF(INFO, "Failed to redirect stdout to debuglog, assuming test environment and continuing");
}
auto args_result = service::Connect<fuchsia_boot::Arguments>();
if (args_result.is_error()) {
LOGF(ERROR, "Failed to get boot arguments service handle: %s", args_result.status_string());
return args_result.error_value();
}
auto boot_args = fidl::WireSyncClient<fuchsia_boot::Arguments>{std::move(*args_result)};
auto driver_manager_params = GetDriverManagerParams(boot_args);
auto driver_manager_args = ParseDriverManagerArgs(argc, argv);
if (driver_manager_params.verbose) {
fx_logger_t* logger = fx_log_get_logger();
if (logger) {
fx_logger_set_min_severity(logger, std::numeric_limits<fx_log_severity_t>::min());
}
}
if (driver_manager_params.log_to_debuglog || driver_manager_args.log_to_debuglog) {
status = log_to_debuglog();
if (status != ZX_OK) {
LOGF(ERROR, "Failed to reconfigure logger to use debuglog: %s", zx_status_get_string(status));
return status;
}
}
if (!driver_manager_params.root_driver.empty()) {
driver_manager_args.sys_device_driver = driver_manager_params.root_driver;
}
// Set up the default values for our arguments if they weren't given.
if (driver_manager_args.driver_search_paths.size() == 0 &&
driver_manager_args.load_drivers.size() == 0 && !driver_manager_args.use_driver_index) {
driver_manager_args.driver_search_paths.push_back(driver_manager_args.path_prefix + "driver");
}
if (driver_manager_args.sys_device_driver.empty()) {
driver_manager_args.sys_device_driver =
driver_manager_args.path_prefix + "driver/platform-bus.so";
}
SuspendCallback suspend_callback = [&driver_manager_args](zx_status_t status) {
if (status != ZX_OK) {
// TODO(https://fxbug.dev/56208): Change this log back to error once isolated devmgr is fixed.
LOGF(WARNING, "Error suspending devices while stopping the component:%s",
zx_status_get_string(status));
}
if (!driver_manager_args.no_exit_after_suspend) {
LOGF(INFO, "Exiting driver manager gracefully");
// TODO(fxb:52627) This event handler should teardown devices and driver hosts
// properly for system state transitions where driver manager needs to go down.
// Exiting like so, will not run all the destructors and clean things up properly.
// Instead the main devcoordinator loop should be quit.
exit(0);
}
};
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
svc::Outgoing outgoing(loop.dispatcher());
InspectManager inspect_manager(loop.dispatcher());
CoordinatorConfig config;
SystemInstance system_instance;
config.boot_args = &boot_args;
config.require_system = driver_manager_params.require_system;
config.log_to_debuglog =
driver_manager_params.log_to_debuglog || driver_manager_args.log_to_debuglog;
config.verbose = driver_manager_params.verbose;
config.fs_provider = &system_instance;
config.path_prefix = driver_manager_args.path_prefix;
config.enable_ephemeral = driver_manager_params.enable_ephemeral;
config.crash_policy = driver_manager_params.crash_policy;
// Waiting an infinite amount of time before falling back is effectively not
// falling back at all.
if (!driver_manager_params.suspend_timeout_fallback) {
config.suspend_timeout = zx::duration::infinite();
}
if (driver_manager_args.use_driver_index) {
auto driver_index_client = service::Connect<fuchsia_driver_index::DriverIndex>();
if (driver_index_client.is_error()) {
LOGF(ERROR, "Failed to connect to driver_index: %d", driver_index_client.error_value());
return driver_index_client.error_value();
}
config.driver_index = fidl::WireSharedClient<fuchsia_driver_index::DriverIndex>(
std::move(driver_index_client.value()), loop.dispatcher());
}
// TODO(fxbug.dev/33958): Remove all uses of the root resource.
status = get_root_resource(&config.root_resource);
if (status != ZX_OK) {
LOGF(INFO, "Failed to get root resource, assuming test environment and continuing");
}
// TODO(fxbug.dev/33957): Remove all uses of the root job.
zx::job root_job;
status = get_root_job(&root_job);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to get root job: %s", zx_status_get_string(status));
return status;
}
zx_handle_t oom_event;
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_OUT_OF_MEMORY, &oom_event);
if (status != ZX_OK) {
LOGF(INFO, "Failed to get OOM event, assuming test environment and continuing");
} else {
config.oom_event = zx::event(oom_event);
}
async::Loop firmware_loop(&kAsyncLoopConfigNeverAttachToThread);
firmware_loop.StartThread("firmware-loop");
Coordinator coordinator(std::move(config), &inspect_manager, loop.dispatcher(),
firmware_loop.dispatcher());
// Services offered to the rest of the system.
status = coordinator.InitOutgoingServices(outgoing.svc_dir());
if (status != ZX_OK) {
LOGF(ERROR, "Failed to initialize outgoing services: %s", zx_status_get_string(status));
return status;
}
// Check if whatever launched devcoordinator gave a channel to be connected to the
// outgoing services directory. This is for use in tests to let the test environment see
// outgoing services.
fidl::ServerEnd<fuchsia_io::Directory> outgoing_svc_dir_client(
zx::channel(zx_take_startup_handle(DEVMGR_LAUNCHER_OUTGOING_SERVICES_HND)));
if (outgoing_svc_dir_client.is_valid()) {
status = outgoing.Serve(std::move(outgoing_svc_dir_client));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind outgoing services: %s", zx_status_get_string(status));
return status;
}
}
devfs_init(coordinator.root_device(), loop.dispatcher());
std::optional<dfv2::DriverRunner> driver_runner;
std::optional<driver_manager::DriverDevelopmentService> driver_development_service;
std::optional<driver_manager::DevfsExporter> devfs_exporter;
// Find and load v1 or v2 Drivers.
if (!driver_manager_params.use_dfv2) {
auto publish = coordinator.PublishDriverDevelopmentService(outgoing.svc_dir());
if (publish.is_error()) {
LOGF(ERROR, "Failed to publish Driver Development service in DFv1: %s",
publish.status_string());
return publish.error_value();
}
if (config.enable_ephemeral) {
auto publish = coordinator.PublishDriverRegistrarService(outgoing.svc_dir());
if (publish.is_error()) {
LOGF(ERROR, "Failed to publish Driver Registrar service in DFv1: %s",
publish.status_string());
return publish.error_value();
}
}
// V1 Drivers.
status = system_instance.CreateDriverHostJob(root_job, &config.driver_host_job);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to create driver_host job: %s", zx_status_get_string(status));
return status;
}
coordinator.LoadV1Drivers(driver_manager_args.sys_device_driver.c_str(),
driver_manager_args.driver_search_paths,
driver_manager_args.load_drivers);
} else {
// V2 Drivers.
LOGF(INFO, "Starting DriverRunner with root driver URL: %s",
driver_manager_args.sys_device_driver.data());
auto realm_result = service::Connect<fuchsia_component::Realm>();
if (realm_result.is_error()) {
return realm_result.error_value();
}
auto driver_index_result = service::Connect<fuchsia_driver_index::DriverIndex>();
if (driver_index_result.is_error()) {
LOGF(ERROR, "Failed to connect to driver_index: %d", driver_index_result.error_value());
return driver_index_result.error_value();
}
driver_runner.emplace(std::move(realm_result.value()), std::move(driver_index_result.value()),
inspect_manager.inspector(), loop.dispatcher());
auto publish_component_runner = driver_runner->PublishComponentRunner(outgoing.svc_dir());
if (publish_component_runner.is_error()) {
return publish_component_runner.error_value();
}
auto start = driver_runner->StartRootDriver(driver_manager_args.sys_device_driver);
if (start.is_error()) {
return start.error_value();
}
driver_development_service.emplace(driver_runner.value(), loop.dispatcher());
auto publish_driver_development_service =
driver_development_service->Publish(outgoing.svc_dir());
if (publish_driver_development_service.is_error()) {
return publish_driver_development_service.error_value();
}
devfs_exporter.emplace(coordinator.root_device()->devnode(), loop.dispatcher());
auto devfs_publish = devfs_exporter->PublishExporter(outgoing.svc_dir());
if (devfs_publish.is_error()) {
return devfs_publish.error_value();
}
driver_runner->ScheduleBaseDriversBinding();
}
devfs_connect_diagnostics(coordinator.inspect_manager().diagnostics_client());
// Check if whatever launched devmgr gave a channel to be connected to /dev.
// This is for use in tests to let the test environment see devfs.
zx::channel devfs_client(zx_take_startup_handle(DEVMGR_LAUNCHER_DEVFS_ROOT_HND));
if (devfs_client.is_valid()) {
fdio_service_clone_to(devfs_root_borrow()->get(), devfs_client.release());
}
// Check if whatever launched devmgr gave a channel for component lifecycle events
fidl::ServerEnd<fuchsia_process_lifecycle::Lifecycle> component_lifecycle_request(
zx::channel(zx_take_startup_handle(PA_LIFECYCLE)));
if (component_lifecycle_request.is_valid()) {
status = devmgr::ComponentLifecycleServer::Create(loop.dispatcher(), &coordinator,
std::move(component_lifecycle_request),
std::move(suspend_callback));
if (status != ZX_OK) {
LOGF(ERROR, "driver_manager: Cannot create componentlifecycleserver: %s",
zx_status_get_string(status));
return status;
}
} else {
LOGF(INFO,
"No valid handle found for lifecycle events, assuming test environment and "
"continuing");
}
system_instance.InstallDevFsIntoNamespace();
system_instance.ServiceStarter(&coordinator);
fbl::unique_fd lib_fd;
{
std::string library_path = driver_manager_args.path_prefix + "lib";
status = fdio_open_fd(library_path.c_str(),
static_cast<uint32_t>(fio::wire::OpenFlags::kDirectory |
fio::wire::OpenFlags::kRightReadable |
fio::wire::OpenFlags::kRightExecutable),
lib_fd.reset_and_get_address());
if (status != ZX_OK) {
LOGF(ERROR, "Failed to open %s: %s", library_path.c_str(), zx_status_get_string(status));
return status;
}
}
// The loader needs its own thread because DriverManager makes synchronous calls to the
// DriverHosts, which make synchronous calls to load their shared libraries.
async::Loop loader_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto loader_service =
DriverHostLoaderService::Create(loader_loop.dispatcher(), std::move(lib_fd));
coordinator.set_loader_service_connector([ls = std::move(loader_service)](zx::channel* c) {
auto conn = ls->Connect();
if (conn.is_error()) {
LOGF(ERROR, "Failed to add driver_host loader connection: %s", conn.status_string());
} else {
*c = conn->TakeChannel();
}
return conn.status_value();
});
loader_loop.StartThread();
// TODO(https://fxbug.dev/99076) Remove this when this issue is fixed.
LOGF(INFO, "driver_manager loader loop started");
outgoing.svc_dir()->AddEntry(
"fuchsia.hardware.pci.DeviceWatcher",
fbl::MakeRefCounted<fs::Service>(
[&loader_loop](fidl::ServerEnd<fuchsia_device_manager::DeviceWatcher> request) {
auto watcher =
std::make_unique<DeviceWatcher>("/dev/class/pciroot", loader_loop.dispatcher());
fidl::BindServer(loader_loop.dispatcher(), std::move(request), std::move(watcher));
return ZX_OK;
}));
outgoing.svc_dir()->AddEntry(
"fuchsia.hardware.usb.DeviceWatcher",
fbl::MakeRefCounted<fs::Service>(
[&loader_loop](fidl::ServerEnd<fuchsia_device_manager::DeviceWatcher> request) {
auto watcher =
std::make_unique<DeviceWatcher>("/dev/class/usb-device", loader_loop.dispatcher());
fidl::BindServer(loader_loop.dispatcher(), std::move(request), std::move(watcher));
return ZX_OK;
}));
outgoing.root_dir()->AddEntry("dev",
fbl::MakeRefCounted<fs::RemoteDir>(system_instance.CloneFs("dev")));
outgoing.root_dir()->AddEntry("diagnostics", fbl::MakeRefCounted<fs::RemoteDir>(
system_instance.CloneFs("dev/diagnostics")));
outgoing.ServeFromStartupInfo();
// TODO(https://fxbug.dev/99076) Remove this when this issue is fixed.
auto log_loop_start = std::make_unique<async::TaskClosure>(
[] { LOGF(INFO, "driver_manager main loop is running"); });
log_loop_start->Post(loop.dispatcher());
coordinator.set_running(true);
status = loop.Run();
LOGF(ERROR, "Coordinator exited unexpectedly: %s", zx_status_get_string(status));
return status;
}