| // 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/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/devmgr-launcher/processargs.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/io.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/device/vfs.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 <memory> |
| |
| #include <fbl/string_printf.h> |
| |
| #include "component_lifecycle.h" |
| #include "coordinator.h" |
| #include "devfs.h" |
| #include "driver_host_loader_service.h" |
| #include "driver_runner.h" |
| #include "fdio.h" |
| #include "src/devices/bin/driver_manager/fake_driver_index.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" |
| |
| namespace { |
| |
| // These are helpers for getting sets of parameters over FIDL |
| struct DriverManagerParams { |
| bool driver_host_asan; |
| bool driver_host_strict_linking; |
| bool enable_ephemeral; |
| bool log_to_debuglog; |
| bool require_system; |
| bool suspend_timeout_fallback; |
| bool verbose; |
| std::vector<fbl::String> eager_fallback_drivers; |
| }; |
| |
| DriverManagerParams GetDriverManagerParams(fidl::WireSyncClient<fuchsia_boot::Arguments>& client) { |
| fuchsia_boot::wire::BoolPair bool_req[]{ |
| // TODO(bwb): remove this or figure out how to make it work |
| {"devmgr.devhost.asan", false}, |
| {"devmgr.devhost.strict-linking", false}, |
| {"devmgr.enable-ephemeral", false}, |
| {"devmgr.log-to-debuglog", false}, |
| {"devmgr.require-system", false}, |
| // Turn it on by default. See fxbug.dev/34577 |
| {"devmgr.suspend-timeout-fallback", true}, |
| {"devmgr.verbose", false}, |
| }; |
| auto bool_resp = |
| client.GetBools(fidl::VectorView<fuchsia_boot::wire::BoolPair>::FromExternal(bool_req)); |
| if (!bool_resp.ok()) { |
| return {}; |
| } |
| |
| auto drivers = client.GetString("devmgr.bind-eager"); |
| std::vector<fbl::String> eager_fallback_drivers; |
| if (drivers.ok() && !drivers->value.is_null() && !drivers->value.empty()) { |
| std::string list(drivers->value.data(), drivers->value.size()); |
| size_t pos; |
| while ((pos = list.find(',')) != std::string::npos) { |
| eager_fallback_drivers.emplace_back(list.substr(0, pos)); |
| list.erase(0, pos + 1); |
| } |
| eager_fallback_drivers.emplace_back(std::move(list)); |
| } |
| return { |
| .driver_host_asan = bool_resp->values[0], |
| .driver_host_strict_linking = bool_resp->values[1], |
| .enable_ephemeral = bool_resp->values[2], |
| .log_to_debuglog = bool_resp->values[3], |
| .require_system = bool_resp->values[4], |
| .suspend_timeout_fallback = bool_resp->values[5], |
| .verbose = bool_resp->values[6], |
| .eager_fallback_drivers = std::move(eager_fallback_drivers), |
| }; |
| } |
| |
| 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. |
| 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|. |
| 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; |
| // Use the default loader rather than the one provided by fshost. |
| bool use_default_loader = false; |
| // If this exists, use the driver runner and launch this driver URL as the root driver. |
| std::string driver_runner_root_driver_url; |
| }; |
| |
| DriverManagerArgs ParseDriverManagerArgs(int argc, char** argv) { |
| enum { |
| kDriverSearchPath, |
| kLoadDriver, |
| kLogToDebuglog, |
| kNoExitAfterSuspend, |
| kPathPrefix, |
| kSysDeviceDriver, |
| kUseDefaultLoader, |
| kDriverRunnerRootDriverUrl, |
| }; |
| option options[] = { |
| {"driver-search-path", required_argument, nullptr, kDriverSearchPath}, |
| {"load-driver", required_argument, nullptr, kLoadDriver}, |
| {"log-to-debuglog", no_argument, nullptr, kLogToDebuglog}, |
| {"no-exit-after-suspend", no_argument, nullptr, kNoExitAfterSuspend}, |
| {"path-prefix", required_argument, nullptr, kPathPrefix}, |
| {"sys-device-driver", required_argument, nullptr, kSysDeviceDriver}, |
| {"use-default-loader", no_argument, nullptr, kUseDefaultLoader}, |
| {"driver-runner-root-driver-url", required_argument, nullptr, kDriverRunnerRootDriverUrl}, |
| {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 kDriverSearchPath: |
| args.driver_search_paths.push_back(optarg); |
| break; |
| 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 kPathPrefix: |
| args.path_prefix = optarg; |
| break; |
| case kSysDeviceDriver: |
| check_not_duplicated(args.sys_device_driver); |
| args.sys_device_driver = optarg; |
| break; |
| case kUseDefaultLoader: |
| args.use_default_loader = true; |
| break; |
| case kDriverRunnerRootDriverUrl: |
| args.driver_runner_root_driver_url = optarg; |
| 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"); |
| } |
| |
| zx::channel local, remote; |
| status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| auto path = fbl::StringPrintf("/svc/%s", fidl::DiscoverableProtocolName<fuchsia_boot::Arguments>); |
| status = fdio_service_connect(path.data(), remote.release()); |
| if (status != ZX_OK) { |
| LOGF(ERROR, "Failed to get boot arguments service handle: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| auto boot_args = fidl::WireSyncClient<fuchsia_boot::Arguments>{std::move(local)}; |
| auto driver_manager_params = GetDriverManagerParams(boot_args); |
| auto driver_manager_args = ParseDriverManagerArgs(argc, argv); |
| |
| if (driver_manager_params.verbose) { |
| FX_LOG_SET_SEVERITY(ALL); |
| } |
| 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; |
| } |
| } |
| // 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.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) { |
| LOGF(ERROR, "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(&kAsyncLoopConfigNoAttachToCurrentThread); |
| svc::Outgoing outgoing(loop.dispatcher()); |
| InspectManager inspect_manager(loop.dispatcher()); |
| |
| std::optional<DriverRunner> driver_runner; |
| std::optional<FakeDriverIndex> driver_index; |
| if (driver_manager_args.driver_runner_root_driver_url.size() != 0) { |
| LOGF(INFO, "Starting DriverRunner with root driver URL: %s", |
| driver_manager_args.driver_runner_root_driver_url.data()); |
| |
| const auto realm_path = |
| fbl::StringPrintf("/svc/%s", fidl::DiscoverableProtocolName<fuchsia_sys2::Realm>); |
| auto endpoints = fidl::CreateEndpoints<fuchsia_sys2::Realm>(); |
| if (endpoints.is_error()) { |
| return endpoints.status_value(); |
| } |
| status = fdio_service_connect(realm_path.data(), endpoints->server.TakeChannel().release()); |
| if (status != ZX_OK) { |
| LOGF(ERROR, "Failed to connect to '%s': %s", realm_path.data(), zx_status_get_string(status)); |
| return status; |
| } |
| // TODO(fxbug.dev/33183): Replace this with a driver_index component. |
| driver_index.emplace(loop.dispatcher(), |
| [](auto args) -> zx::status<FakeDriverIndex::MatchResult> { |
| std::string_view name(args.name().data(), args.name().size()); |
| if (name == "board") { |
| return zx::ok(FakeDriverIndex::MatchResult{ |
| .url = "fuchsia-boot:///#meta/x64.cm", |
| .matched_args = std::move(args), |
| }); |
| } else { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| }); |
| auto driver_index_client = driver_index->Connect(); |
| if (driver_index_client.is_error()) { |
| LOGF(INFO, "Failed to conect to DriverIndex: %s", |
| zx_status_get_string(driver_index_client.status_value())); |
| return driver_index_client.status_value(); |
| } |
| |
| driver_runner.emplace(std::move(endpoints->client), std::move(driver_index_client.value()), |
| &inspect_manager.inspector(), loop.dispatcher()); |
| auto publish = driver_runner->PublishComponentRunner(outgoing.svc_dir()); |
| if (publish.is_error()) { |
| return publish.error_value(); |
| } |
| auto start = driver_runner->StartRootDriver(driver_manager_args.driver_runner_root_driver_url); |
| if (start.is_error()) { |
| return start.error_value(); |
| } |
| } |
| |
| CoordinatorConfig config; |
| SystemInstance system_instance; |
| config.boot_args = &boot_args; |
| config.require_system = driver_manager_params.require_system; |
| config.asan_drivers = driver_manager_params.driver_host_asan; |
| config.suspend_fallback = driver_manager_params.suspend_timeout_fallback; |
| 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.eager_fallback_drivers = std::move(driver_manager_params.eager_fallback_drivers); |
| config.enable_ephemeral = driver_manager_params.enable_ephemeral; |
| |
| // 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; |
| } |
| 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; |
| } |
| |
| 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); |
| } |
| |
| Coordinator coordinator(std::move(config), &inspect_manager, 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. |
| zx::channel outgoing_svc_dir_client( |
| 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; |
| } |
| } |
| |
| status = coordinator.InitCoreDevices(driver_manager_args.sys_device_driver.c_str()); |
| if (status != ZX_OK) { |
| LOGF(ERROR, "Failed to initialize core devices: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| devfs_init(coordinator.root_device(), loop.dispatcher()); |
| devfs_publish(coordinator.root_device(), coordinator.misc_device()); |
| devfs_publish(coordinator.root_device(), coordinator.sys_device()); |
| devfs_publish(coordinator.root_device(), coordinator.test_device()); |
| devfs_connect_diagnostics(fidl::UnownedClientEnd<fuchsia_io::Directory>( |
| coordinator.inspect_manager().diagnostics_channel())); |
| |
| // 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 |
| zx::channel component_lifecycle_request(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); |
| |
| if (driver_manager_params.driver_host_strict_linking) { |
| fbl::unique_fd lib_fd; |
| status = fdio_open_fd("/pkg/lib", |
| ZX_FS_FLAG_DIRECTORY | ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_EXECUTABLE, |
| lib_fd.reset_and_get_address()); |
| if (status != ZX_OK) { |
| LOGF(ERROR, "Failed to open /pkg/lib: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| auto loader_service = DriverHostLoaderService::Create(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(); |
| }); |
| } else if (!driver_manager_args.use_default_loader) { |
| coordinator.set_loader_service_connector([&system_instance](zx::channel* c) { |
| zx_status_t status = system_instance.clone_fshost_ldsvc(c); |
| if (status != ZX_OK) { |
| LOGF(ERROR, "Failed to clone fshost loader for driver_host: %s", |
| zx_status_get_string(status)); |
| } |
| return status; |
| }); |
| } |
| |
| for (const std::string& path : driver_manager_args.driver_search_paths) { |
| find_loadable_drivers(path, fit::bind_member(&coordinator, &Coordinator::DriverAddedInit)); |
| } |
| for (const char* driver : driver_manager_args.load_drivers) { |
| load_driver(driver, fit::bind_member(&coordinator, &Coordinator::DriverAddedInit)); |
| } |
| |
| coordinator.PrepareProxy(coordinator.sys_device(), nullptr); |
| coordinator.PrepareProxy(coordinator.test_device(), nullptr); |
| |
| // Initial bind attempt for drivers enumerated at startup. |
| coordinator.BindDrivers(); |
| if (coordinator.require_system()) { |
| LOGF(INFO, "Full system required, fallback drivers will be loaded after '/system' is loaded"); |
| } else { |
| coordinator.BindFallbackDrivers(); |
| } |
| |
| 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(); |
| |
| coordinator.set_running(true); |
| status = loop.Run(); |
| LOGF(ERROR, "Coordinator exited unexpectedly: %s", zx_status_get_string(status)); |
| return status; |
| } |