| // Copyright 2021 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 "src/devices/misc/drivers/compat/driver.h" |
| |
| #include <fidl/fuchsia.device.manager/cpp/wire.h> |
| #include <fidl/fuchsia.driver.framework/cpp/wire.h> |
| #include <fidl/fuchsia.scheduler/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/ddk/binding_priv.h> |
| #include <lib/driver/compat/cpp/connect.h> |
| #include <lib/driver/component/cpp/internal/start_args.h> |
| #include <lib/driver/component/cpp/internal/symbols.h> |
| #include <lib/driver/logging/cpp/structured_logger.h> |
| #include <lib/driver/promise/cpp/promise.h> |
| #include <lib/fidl/cpp/wire/connect_service.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fpromise/bridge.h> |
| #include <lib/fpromise/promise.h> |
| #include <lib/fpromise/single_threaded_executor.h> |
| #include <lib/sync/cpp/completion.h> |
| #include <zircon/dlfcn.h> |
| |
| #include "src/devices/lib/log/log.h" |
| #include "src/devices/misc/drivers/compat/compat_driver_server.h" |
| #include "src/devices/misc/drivers/compat/loader.h" |
| #include "src/lib/driver_symbols/symbols.h" |
| |
| namespace fboot = fuchsia_boot; |
| namespace fdf { |
| using namespace fuchsia_driver_framework; |
| } |
| namespace fio = fuchsia_io; |
| namespace fkernel = fuchsia_kernel; |
| namespace fldsvc = fuchsia_ldsvc; |
| namespace fdm = fuchsia_device_manager; |
| |
| using fpromise::bridge; |
| using fpromise::error; |
| using fpromise::join_promises; |
| using fpromise::ok; |
| using fpromise::promise; |
| using fpromise::result; |
| |
| // This lock protects any globals, as globals could be accessed by other |
| // drivers and other threads within the process. |
| // Currently this protects the root resource and the loader service. |
| std::mutex kDriverGlobalsLock; |
| |
| namespace { |
| |
| constexpr auto kOpenFlags = fio::wire::OpenFlags::kRightReadable | |
| fio::wire::OpenFlags::kRightExecutable | |
| fio::wire::OpenFlags::kNotDirectory; |
| constexpr auto kVmoFlags = |
| fio::wire::VmoFlags::kRead | fio::wire::VmoFlags::kExecute | fio::wire::VmoFlags::kPrivateClone; |
| constexpr auto kLibDriverPath = "/pkg/driver/compat.so"; |
| |
| std::string_view GetFilename(std::string_view path) { |
| size_t index = path.rfind('/'); |
| return index == std::string_view::npos ? path : path.substr(index + 1); |
| } |
| |
| zx::result<zx::vmo> LoadVmo(fdf::Namespace& ns, const char* path, |
| fuchsia_io::wire::OpenFlags flags) { |
| zx::result file = ns.Open<fuchsia_io::File>(path, flags); |
| if (file.is_error()) { |
| return file.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(file.value())->GetBackingMemory(kVmoFlags); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| if (result.value().is_error()) { |
| return result.value().take_error(); |
| } |
| return zx::ok(std::move(result.value().value()->vmo)); |
| } |
| |
| zx::result<zx::resource> GetMmioResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::MmioResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetPowerResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::PowerResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetIommuResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::IommuResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetFramebufferResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::FramebufferResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetIoportResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::IoportResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetIrqResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::IrqResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetSmcResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::SmcResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetInfoResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::InfoResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| zx::result<zx::resource> GetMsiResource(fdf::Namespace& ns) { |
| zx::result resource = ns.Connect<fkernel::MsiResource>(); |
| if (resource.is_error()) { |
| return resource.take_error(); |
| } |
| fidl::WireResult result = fidl::WireCall(resource.value())->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| } // namespace |
| |
| namespace compat { |
| |
| // This lock protects the global logger list. |
| std::mutex kGlobalLoggerListLock; |
| |
| // This contains all the loggers in this driver host. |
| #ifdef DRIVER_COMPAT_ADD_NODE_NAMES_TO_LOG_TAGS |
| GlobalLoggerList global_logger_list __TA_GUARDED(kGlobalLoggerListLock)(true); |
| #else |
| GlobalLoggerList global_logger_list __TA_GUARDED(kGlobalLoggerListLock)(false); |
| #endif |
| |
| zx_status_t AddMetadata(Device* device, |
| fidl::VectorView<fuchsia_driver_compat::wire::Metadata> data) { |
| for (auto& metadata : data) { |
| size_t size; |
| zx_status_t status = metadata.data.get_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| std::vector<uint8_t> data(size); |
| status = metadata.data.read(data.data(), 0, data.size()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = device->AddMetadata(metadata.type, data.data(), data.size()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| promise<void, zx_status_t> GetAndAddMetadata( |
| fidl::WireClient<fuchsia_driver_compat::Device>& client, Device* device) { |
| ZX_ASSERT_MSG( |
| client, "Attempted to access metadata from an invalid fuchsia.driver.compat.Device client."); |
| bridge<void, zx_status_t> bridge; |
| client->GetMetadata().Then( |
| [device, completer = std::move(bridge.completer)]( |
| fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetMetadata>& result) mutable { |
| if (!result.ok()) { |
| return; |
| } |
| auto* response = result.Unwrap(); |
| if (response->is_error()) { |
| completer.complete_error(response->error_value()); |
| return; |
| } |
| zx_status_t status = AddMetadata(device, response->value()->metadata); |
| if (status != ZX_OK) { |
| completer.complete_error(status); |
| return; |
| } |
| completer.complete_ok(); |
| }); |
| return bridge.consumer.promise_or(error(ZX_ERR_INTERNAL)); |
| } |
| |
| void GlobalLoggerList::LoggerInstances::Log(FuchsiaLogSeverity severity, const char* tag, |
| const char* file, int line, const char* msg, |
| va_list args) { |
| std::lock_guard guard(kGlobalLoggerListLock); |
| auto it = loggers_.begin(); |
| |
| if (it == loggers_.end()) { |
| LOGF(WARNING, "No logger available in this LoggerInstances. Using host logger."); |
| driver_logger::GetLogger().VLogWrite(severity, tag, msg, args, file, line); |
| return; |
| } |
| |
| if (!log_node_names_) { |
| (*it)->logvf(severity, tag, file, line, msg, args); |
| return; |
| } |
| |
| if (tag) { |
| node_names_.push_back(tag); |
| } |
| |
| (*it)->logvf(severity, node_names_, file, line, msg, args); |
| |
| if (tag) { |
| node_names_.pop_back(); |
| } |
| } |
| |
| zx_driver_t* GlobalLoggerList::LoggerInstances::ZxDriver() { |
| return static_cast<zx_driver_t*>(this); |
| } |
| |
| void GlobalLoggerList::LoggerInstances::AddLogger(std::shared_ptr<fdf::Logger>& logger, |
| const std::optional<std::string>& node_name) { |
| loggers_.insert(logger); |
| if (log_node_names_ && node_name.has_value()) { |
| node_names_.push_back(node_name.value()); |
| } |
| } |
| |
| void GlobalLoggerList::LoggerInstances::RemoveLogger(std::shared_ptr<fdf::Logger>& logger, |
| const std::optional<std::string>& node_name) { |
| loggers_.erase(logger); |
| if (log_node_names_ && node_name.has_value()) { |
| node_names_.erase(std::remove(node_names_.begin(), node_names_.end(), node_name.value()), |
| node_names_.end()); |
| } |
| } |
| |
| zx_driver_t* GlobalLoggerList::AddLogger(const std::string& driver_path, |
| std::shared_ptr<fdf::Logger>& logger, |
| const std::optional<std::string>& node_name) { |
| auto& instances = instances_.try_emplace(driver_path, log_node_names_).first->second; |
| instances.AddLogger(logger, node_name); |
| return instances.ZxDriver(); |
| } |
| |
| void GlobalLoggerList::RemoveLogger(const std::string& driver_path, |
| std::shared_ptr<fdf::Logger>& logger, |
| const std::optional<std::string>& node_name) { |
| auto it = instances_.find(driver_path); |
| if (it != instances_.end()) { |
| it->second.RemoveLogger(logger, node_name); |
| // Don't erase the instance even if it becomes empty. There are some drivers that incorrectly |
| // log after they have been destroyed. We want to make sure that the logger instance that we |
| // put for them is kept around. The empty loggers will just cause it to log with the |
| // driver host's logger. |
| } |
| } |
| |
| std::optional<size_t> GlobalLoggerList::loggers_count_for_testing(const std::string& driver_path) { |
| auto it = instances_.find(driver_path); |
| if (it != instances_.end()) { |
| return (*it).second.count(); |
| } |
| |
| return std::nullopt; |
| } |
| |
| Driver::Driver(fdf::DriverStartArgs start_args, zx::vmo config_vmo, |
| fdf::UnownedSynchronizedDispatcher driver_dispatcher, device_t device, |
| const zx_protocol_device_t* ops, std::string_view driver_path) |
| : Base("compat", std::move(start_args), std::move(driver_dispatcher)), |
| executor_(dispatcher()), |
| driver_path_(driver_path), |
| device_(device, ops, this, std::nullopt, nullptr, dispatcher()), |
| config_vmo_(std::move(config_vmo)) { |
| // Give the parent device the correct node. |
| device_.Bind({std::move(node()), dispatcher()}); |
| // Call this so the parent device is in the post-init state. |
| device_.InitReply(ZX_OK); |
| ZX_ASSERT(url().has_value()); |
| } |
| |
| Driver::~Driver() { |
| if (ShouldCallRelease()) { |
| record_->ops->release(context_); |
| } |
| dlclose(library_); |
| { |
| std::lock_guard guard(kGlobalLoggerListLock); |
| global_logger_list.RemoveLogger(driver_path(), inner_logger_, node_name()); |
| } |
| } |
| |
| void Driver::Start(fdf::StartCompleter completer) { |
| zx::result loader_vmo = LoadVmo(*incoming(), kLibDriverPath, kOpenFlags); |
| if (loader_vmo.is_error()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to open loader vmo: %s", loader_vmo.status_string()); |
| completer(loader_vmo.take_error()); |
| return; |
| } |
| |
| zx::result driver_vmo = LoadVmo(*incoming(), driver_path_.c_str(), kOpenFlags); |
| if (driver_vmo.is_error()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to open driver vmo: %s", driver_vmo.status_string()); |
| completer(driver_vmo.take_error()); |
| return; |
| } |
| |
| // Give the driver's VMO a name. |
| std::string_view vmo_name = GetFilename(driver_path_); |
| if (zx_status_t status = driver_vmo->set_property(ZX_PROP_NAME, vmo_name.data(), vmo_name.size()); |
| status != ZX_OK) { |
| LOGF(ERROR, "Failed to name driver's DFv1 vmo '%s': %s", std::string(vmo_name).c_str(), |
| zx_status_get_string(status)); |
| // We don't need to exit on this error, there will just be less debugging information. |
| } |
| |
| if (zx::result result = LoadDriver(std::move(loader_vmo.value()), std::move(driver_vmo.value())); |
| result.is_error()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver: %s", result.status_string()); |
| completer(result.take_error()); |
| return; |
| } |
| |
| // Store start completer to be replied to later. It will either be done when the below promises |
| // hit an error or after the init hook is replied to and the node has been created and a devfs |
| // node has been exported. |
| start_completer_.emplace(std::move(completer)); |
| |
| auto start_driver = |
| Driver::ConnectToParentDevices() |
| .and_then(fit::bind_member<&Driver::GetDeviceInfo>(this)) |
| .then([this](result<void, zx_status_t>& result) -> fpromise::result<void, zx_status_t> { |
| if (result.is_error()) { |
| FDF_LOGL(WARNING, *logger_, "Getting DeviceInfo failed with: %s", |
| zx_status_get_string(result.error())); |
| } |
| if (zx::result result = StartDriver(); result.is_error()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to start driver '%s': %s", url().value().data(), |
| result.status_string()); |
| device_.Unbind(); |
| CompleteStart(result.take_error()); |
| return error(result.error_value()); |
| } |
| return ok(); |
| }) |
| .wrap_with(scope_); |
| executor_.schedule_task(std::move(start_driver)); |
| } |
| |
| bool Driver::IsComposite() { return !parent_clients_.empty(); } |
| |
| zx_handle_t Driver::GetMmioResource() { |
| if (!mmio_resource_.is_valid()) { |
| zx::result resource = ::GetMmioResource(*incoming()); |
| if (resource.is_ok()) { |
| mmio_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get mmio_resource '%s'", resource.status_string()); |
| } |
| } |
| return mmio_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetMsiResource() { |
| if (!msi_resource_.is_valid()) { |
| zx::result resource = ::GetMsiResource(*incoming()); |
| if (resource.is_ok()) { |
| msi_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get msi_resource '%s'", resource.status_string()); |
| } |
| } |
| return msi_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetPowerResource() { |
| if (!power_resource_.is_valid()) { |
| zx::result resource = ::GetPowerResource(*incoming()); |
| if (resource.is_ok()) { |
| power_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get power_resource '%s'", resource.status_string()); |
| } |
| } |
| return power_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetIommuResource() { |
| if (!iommu_resource_.is_valid()) { |
| zx::result resource = ::GetIommuResource(*incoming()); |
| if (resource.is_ok()) { |
| iommu_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get iommu_resource '%s'", resource.status_string()); |
| } |
| } |
| return iommu_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetFramebufferResource() { |
| if (!framebuffer_resource_.is_valid()) { |
| zx::result resource = ::GetFramebufferResource(*incoming()); |
| if (resource.is_ok()) { |
| framebuffer_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get framebuffer_resource '%s'", |
| resource.status_string()); |
| } |
| } |
| return framebuffer_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetIoportResource() { |
| if (!ioport_resource_.is_valid()) { |
| zx::result resource = ::GetIoportResource(*incoming()); |
| if (resource.is_ok()) { |
| ioport_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get ioport_resource '%s'", resource.status_string()); |
| } |
| } |
| return ioport_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetIrqResource() { |
| if (!irq_resource_.is_valid()) { |
| zx::result resource = ::GetIrqResource(*incoming()); |
| if (resource.is_ok()) { |
| irq_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get irq_resource '%s'", resource.status_string()); |
| } |
| } |
| return irq_resource_.get(); |
| } |
| |
| zx_handle_t Driver::GetSmcResource() { |
| if (!smc_resource_.is_valid()) { |
| zx::result resource = ::GetSmcResource(*incoming()); |
| if (resource.is_ok()) { |
| smc_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get smc_resource '%s'", resource.status_string()); |
| } |
| } |
| return smc_resource_.get(); |
| } |
| |
| zx::vmo& Driver::GetConfigVmo() { return config_vmo_; } |
| |
| zx_status_t Driver::GetProperties(device_props_args_t* out_args, |
| const std::string& parent_node_name) { |
| if (!out_args) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto set_str_prop_value = |
| [this]( |
| const ::fuchsia_driver_framework::NodePropertyValue& value) -> zx_device_str_prop_val_t { |
| zx_device_str_prop_val_t out_value; |
| switch (value.Which()) { |
| case fuchsia_driver_framework::NodePropertyValue::Tag::kIntValue: |
| out_value.data_type = ZX_DEVICE_PROPERTY_VALUE_INT; |
| out_value.data.int_val = value.int_value().value(); |
| break; |
| case fuchsia_driver_framework::NodePropertyValue::Tag::kStringValue: |
| out_value.data_type = ZX_DEVICE_PROPERTY_VALUE_STRING; |
| out_value.data.str_val = value.string_value()->data(); |
| break; |
| case fuchsia_driver_framework::NodePropertyValue::Tag::kBoolValue: |
| out_value.data_type = ZX_DEVICE_PROPERTY_VALUE_BOOL; |
| out_value.data.bool_val = value.bool_value().value(); |
| break; |
| case fuchsia_driver_framework::NodePropertyValue::Tag::kEnumValue: |
| out_value.data_type = ZX_DEVICE_PROPERTY_VALUE_ENUM; |
| out_value.data.enum_val = value.enum_value()->data(); |
| break; |
| default: |
| FDF_LOGL(ERROR, *logger_, "Unsupported property type, value: %lu", |
| static_cast<fidl_xunion_tag_t>(value.Which())); |
| break; |
| } |
| return out_value; |
| }; |
| |
| auto props = node_properties(parent_node_name); |
| uint32_t prop_count = 0; |
| uint32_t str_prop_count = 0; |
| for (auto& prop : props) { |
| switch (prop.key().Which()) { |
| case fuchsia_driver_framework::NodePropertyKey::Tag::kIntValue: |
| if (prop_count >= out_args->prop_count) { |
| out_args->actual_prop_count = prop_count; |
| out_args->actual_str_prop_count = str_prop_count; |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| prop_count++; |
| out_args->props[prop_count - 1].id = static_cast<uint16_t>(prop.key().int_value().value()); |
| if (!prop.value().int_value().has_value()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| out_args->props[prop_count - 1].value = prop.value().int_value().value(); |
| break; |
| case fuchsia_driver_framework::NodePropertyKey::Tag::kStringValue: |
| if (str_prop_count >= out_args->str_prop_count) { |
| out_args->actual_prop_count = prop_count; |
| out_args->actual_str_prop_count = str_prop_count; |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| str_prop_count++; |
| out_args->str_props[str_prop_count - 1].key = prop.key().string_value()->data(); |
| out_args->str_props[str_prop_count - 1].property_value = set_str_prop_value(prop.value()); |
| break; |
| } |
| } |
| out_args->actual_prop_count = prop_count; |
| out_args->actual_str_prop_count = str_prop_count; |
| return ZX_OK; |
| } |
| |
| zx_handle_t Driver::GetInfoResource() { |
| if (!info_resource_.is_valid()) { |
| zx::result resource = ::GetInfoResource(*incoming()); |
| if (resource.is_ok()) { |
| info_resource_ = std::move(resource.value()); |
| } else { |
| FDF_LOGL(WARNING, *logger_, "Failed to get info_resource '%s'", resource.status_string()); |
| } |
| } |
| return info_resource_.get(); |
| } |
| |
| bool Driver::IsRunningOnDispatcher() const { |
| fdf::Unowned<fdf::Dispatcher> current_dispatcher = fdf::Dispatcher::GetCurrent(); |
| if (current_dispatcher == fdf::Unowned<fdf::Dispatcher>{}) { |
| return false; |
| } |
| return current_dispatcher->async_dispatcher() == dispatcher(); |
| } |
| |
| zx_status_t Driver::RunOnDispatcher(fit::callback<zx_status_t()> task) { |
| if (IsRunningOnDispatcher()) { |
| return task(); |
| } |
| |
| libsync::Completion completion; |
| zx_status_t task_status; |
| auto discarded = fit::defer([&] { |
| task_status = ZX_ERR_CANCELED; |
| completion.Signal(); |
| }); |
| zx_status_t status = |
| async::PostTask(dispatcher(), [&task_status, &completion, task = std::move(task), |
| discarded = std::move(discarded)]() mutable { |
| discarded.cancel(); |
| task_status = task(); |
| completion.Signal(); |
| }); |
| if (status != ZX_OK) { |
| return status; |
| } |
| completion.Wait(); |
| return task_status; |
| } |
| |
| void Driver::PrepareStop(fdf::PrepareStopCompleter completer) { |
| zx::result client = this->incoming()->Connect<fuchsia_device_manager::SystemStateTransition>(); |
| if (client.is_error()) { |
| FDF_LOGL(ERROR, *logger_, |
| "failed to connect to fuchsia.device.manager/SystemStateTransition: %s", |
| client.status_string()); |
| completer(client.take_error()); |
| return; |
| } |
| fidl::WireResult result = fidl::WireCall(client.value())->GetTerminationSystemState(); |
| if (!result.ok()) { |
| FDF_LOGL(ERROR, *logger_, "failed to get termination state: %s", client.status_string()); |
| completer(zx::error(result.error().status())); |
| return; |
| } |
| |
| system_state_ = result->state; |
| stop_triggered_ = true; |
| |
| executor_.schedule_task(device_.HandleStopSignal().then( |
| [completer = std::move(completer)](fpromise::result<void>& init) mutable { |
| completer(zx::ok()); |
| })); |
| } |
| |
| zx::result<> Driver::LoadDriver(zx::vmo loader_vmo, zx::vmo driver_vmo) { |
| const std::string& url_str = url().value(); |
| |
| // Replace loader service to load the DFv1 driver, load the driver, |
| // then place the original loader service back. |
| { |
| // This requires a lock because the loader is a global variable. |
| std::scoped_lock lock(kDriverGlobalsLock); |
| zx::result new_loader_endpoints = fidl::CreateEndpoints<fldsvc::Loader>(); |
| if (new_loader_endpoints.is_error()) { |
| return new_loader_endpoints.take_error(); |
| } |
| fidl::ClientEnd<fldsvc::Loader> original_loader( |
| zx::channel(dl_set_loader_service(new_loader_endpoints->client.channel().release()))); |
| auto reset_loader = fit::defer([&original_loader]() { |
| zx::channel l = zx::channel(dl_set_loader_service(original_loader.TakeChannel().release())); |
| }); |
| |
| // Start loader. |
| async::Loop loader_loop(&kAsyncLoopConfigNeverAttachToThread); |
| zx_status_t status = loader_loop.StartThread("loader-loop"); |
| if (status != ZX_OK) { |
| FDF_LOGL(ERROR, *logger_, |
| "Failed to load driver '%s', could not start thread for loader loop: %s", |
| url_str.data(), zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| Loader loader(loader_loop.dispatcher(), original_loader.borrow(), std::move(loader_vmo)); |
| fidl::BindServer(loader_loop.dispatcher(), std::move(new_loader_endpoints->server), &loader); |
| |
| auto result = driver_symbols::FindRestrictedSymbols(driver_vmo, url_str); |
| if (result.is_error()) { |
| LOGF(WARNING, "Driver '%s' failed to validate as ELF: %s", url_str.c_str(), |
| result.status_value()); |
| } else if (result->size() > 0) { |
| LOGF(ERROR, "Driver '%s' referenced %lu restricted libc symbols: ", url_str.c_str(), |
| result->size()); |
| for (auto& str : *result) { |
| LOGF(ERROR, str.c_str()); |
| } |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // Open driver. |
| library_ = dlopen_vmo(driver_vmo.get(), RTLD_NOW); |
| if (library_ == nullptr) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', could not load library: %s", |
| url_str.data(), dlerror()); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| } |
| |
| // Load and verify symbols. |
| auto note = static_cast<const zircon_driver_note_t*>(dlsym(library_, "__zircon_driver_note__")); |
| if (note == nullptr) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', driver note not found", url_str.data()); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| driver_name_ = note->payload.name; |
| FDF_LOGL(INFO, *logger_, "Loaded driver '%s'", note->payload.name); |
| record_ = static_cast<zx_driver_rec_t*>(dlsym(library_, "__zircon_driver_rec__")); |
| if (record_ == nullptr) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', driver record not found", |
| url_str.data()); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| if (record_->ops == nullptr) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', missing driver ops", url_str.data()); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| if (record_->ops->version != DRIVER_OPS_VERSION) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', incorrect driver version", |
| url_str.data()); |
| return zx::error(ZX_ERR_WRONG_TYPE); |
| } |
| if (record_->ops->bind == nullptr && record_->ops->create == nullptr) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', missing '%s'", url_str.data(), |
| (record_->ops->bind == nullptr ? "bind" : "create")); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| if (record_->ops->bind != nullptr && record_->ops->create != nullptr) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', both 'bind' and 'create' are defined", |
| url_str.data()); |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| // Create our logger. |
| zx::result logger_result = fdf::Logger::Create(*incoming(), dispatcher(), note->payload.name); |
| if (logger_result.is_error()) { |
| return logger_result.take_error(); |
| } |
| |
| // Move the logger over into a shared_ptr instead of unique_ptr so we can pass it to the global |
| // logging manager and compat::Device. |
| inner_logger_ = std::shared_ptr<fdf::Logger>(logger_result.value().release()); |
| device_.set_logger(inner_logger_); |
| { |
| std::lock_guard guard(kGlobalLoggerListLock); |
| record_->driver = global_logger_list.AddLogger(driver_path(), inner_logger_, node_name()); |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::result<> Driver::TryRunUnitTests() { |
| auto getvar_bool = [this](const char* key, bool default_value) { |
| zx::result value = GetVariable(key); |
| if (value.is_error()) { |
| return default_value; |
| } |
| if (*value == "0" || *value == "false" || *value == "off") { |
| return false; |
| } |
| return true; |
| }; |
| |
| bool default_opt = getvar_bool("driver.tests.enable", false); |
| auto variable_name = std::string("driver.") + driver_name_ + ".tests.enable"; |
| bool should_run_unittests = getvar_bool(variable_name.c_str(), default_opt); |
| if (should_run_unittests && record_->ops->run_unit_tests != nullptr) { |
| zx::channel test_input, test_output; |
| zx_status_t status = zx::channel::create(0, &test_input, &test_output); |
| ZX_ASSERT_MSG(status == ZX_OK, "zx::channel::create failed with %s", |
| zx_status_get_string(status)); |
| |
| bool tests_passed = |
| record_->ops->run_unit_tests(context_, device_.ZxDevice(), test_input.release()); |
| if (!tests_passed) { |
| FDF_LOGL(ERROR, *logger_, "[ FAILED ] %s", driver_path().c_str()); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| FDF_LOGL(INFO, *logger_, "[ PASSED ] %s", driver_path().c_str()); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> Driver::StartDriver() { |
| const std::string& url_str = url().value(); |
| if (record_->ops->init != nullptr) { |
| // If provided, run init. |
| zx_status_t status = record_->ops->init(&context_); |
| if (status != ZX_OK) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', 'init' failed: %s", url_str.data(), |
| zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| } |
| |
| zx::result result = TryRunUnitTests(); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| |
| if (record_->ops->bind != nullptr) { |
| // If provided, run bind and return. |
| zx_status_t status = record_->ops->bind(context_, device_.ZxDevice()); |
| if (status != ZX_OK) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', 'bind' failed: %s", url_str.data(), |
| zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| } else { |
| // Else, run create and return. |
| auto client_end = incoming()->Connect<fboot::Items>(); |
| if (client_end.is_error()) { |
| return zx::error(client_end.status_value()); |
| } |
| zx_status_t status = record_->ops->create(context_, device_.ZxDevice(), "proxy", |
| client_end->channel().release()); |
| if (status != ZX_OK) { |
| FDF_LOGL(ERROR, *logger_, "Failed to load driver '%s', 'create' failed: %s", url_str.data(), |
| zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| } |
| if (!device_.HasChildren()) { |
| FDF_LOGL(ERROR, *logger_, "Driver '%s' did not add a child device", url_str.data()); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| return zx::ok(); |
| } |
| |
| fpromise::promise<void, zx_status_t> Driver::ConnectToParentDevices() { |
| bridge<void, zx_status_t> bridge; |
| auto task = compat::ConnectToParentDevices( |
| dispatcher(), incoming().get(), |
| [this, completer = std::move(bridge.completer)]( |
| zx::result<std::vector<compat::ParentDevice>> devices) mutable { |
| if (devices.is_error()) { |
| completer.complete_error(devices.error_value()); |
| return; |
| } |
| std::vector<std::string> parents_names; |
| for (auto& device : devices.value()) { |
| if (device.name == "default") { |
| parent_client_ = fidl::WireClient<fuchsia_driver_compat::Device>( |
| std::move(device.client), dispatcher()); |
| continue; |
| } |
| |
| // TODO(https://fxbug.dev/42051759): When services stop adding extra instances |
| // separated by ',' then remove this check. |
| if (device.name.find(',') != std::string::npos) { |
| continue; |
| } |
| |
| parents_names.push_back(device.name); |
| parent_clients_[device.name] = fidl::WireClient<fuchsia_driver_compat::Device>( |
| std::move(device.client), dispatcher()); |
| } |
| device_.set_fragments(std::move(parents_names)); |
| completer.complete_ok(); |
| }); |
| async_tasks_.AddTask(std::move(task)); |
| return bridge.consumer.promise_or(error(ZX_ERR_INTERNAL)).wrap_with(scope_); |
| } |
| |
| promise<void, zx_status_t> Driver::GetDeviceInfo() { |
| if (!parent_client_) { |
| return fpromise::make_result_promise<void, zx_status_t>(error(ZX_ERR_PEER_CLOSED)); |
| } |
| |
| std::vector<promise<void, zx_status_t>> promises; |
| |
| // Get our topological path from our default parent. |
| bridge<void, zx_status_t> topo_bridge; |
| parent_client_->GetTopologicalPath().Then( |
| [this, completer = std::move(topo_bridge.completer)]( |
| fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetTopologicalPath>& |
| result) mutable { |
| if (!result.ok()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to get topo path %s", |
| zx_status_get_string(result.status())); |
| return; |
| } |
| auto* response = result.Unwrap(); |
| auto topological_path = std::string(response->path.data(), response->path.size()); |
| // If we are a composite then we have to add the name of our composite device |
| // to our primary parent. The composite device's name is the node_name handed |
| // to us. |
| if (IsComposite()) { |
| topological_path.append("/"); |
| topological_path.append(node_name().value()); |
| } |
| device_.set_topological_path(std::move(topological_path)); |
| completer.complete_ok(); |
| }); |
| |
| promises.push_back(topo_bridge.consumer.promise_or(error(ZX_ERR_INTERNAL))); |
| |
| // Get our metadata from our fragments if we are a composite, |
| // or our primary parent. |
| if (IsComposite()) { |
| for (auto& client : parent_clients_) { |
| promises.push_back(GetAndAddMetadata(client.second, &device_)); |
| } |
| } else { |
| promises.push_back(GetAndAddMetadata(parent_client_, &device_)); |
| } |
| |
| // Collect all our promises and return the first error we see. |
| return join_promise_vector(std::move(promises)) |
| .then([](fpromise::result<std::vector<fpromise::result<void, zx_status_t>>>& results) { |
| if (results.is_error()) { |
| return fpromise::make_result_promise(error(ZX_ERR_INTERNAL)); |
| } |
| for (auto& result : results.value()) { |
| if (result.is_error()) { |
| return fpromise::make_result_promise(error(result.error())); |
| } |
| } |
| return fpromise::make_result_promise<void, zx_status_t>(ok()); |
| }); |
| } |
| |
| void* Driver::Context() const { return context_; } |
| |
| zx::result<zx::vmo> Driver::LoadFirmware(Device* device, const char* filename, size_t* size) { |
| std::string full_filename = "/pkg/lib/firmware/"; |
| full_filename.append(filename); |
| fpromise::result connect_result = fpromise::run_single_threaded( |
| fdf::Open(*incoming(), dispatcher(), full_filename.c_str(), kOpenFlags)); |
| if (connect_result.is_error()) { |
| return zx::error(connect_result.take_error()); |
| } |
| |
| fidl::WireResult get_backing_memory_result = |
| connect_result.take_value().sync()->GetBackingMemory(fio::wire::VmoFlags::kRead); |
| if (!get_backing_memory_result.ok()) { |
| if (get_backing_memory_result.is_peer_closed()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return zx::error(get_backing_memory_result.status()); |
| } |
| const auto* res = get_backing_memory_result.Unwrap(); |
| if (res->is_error()) { |
| return zx::error(res->error_value()); |
| } |
| zx::vmo& vmo = res->value()->vmo; |
| if (zx_status_t status = vmo.get_prop_content_size(size); status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(std::move(vmo)); |
| } |
| |
| zx_status_t Driver::AddDevice(Device* parent, device_add_args_t* args, zx_device_t** out) { |
| return RunOnDispatcher([&] { |
| zx_device_t* child; |
| zx_status_t status = parent->Add(args, &child); |
| if (status != ZX_OK) { |
| FDF_LOGL(ERROR, *logger_, "Failed to add device %s: %s", args->name, |
| zx_status_get_string(status)); |
| return status; |
| } |
| if (out) { |
| *out = child; |
| } |
| return ZX_OK; |
| }); |
| } |
| |
| zx::result<> Driver::SetProfileByRole(zx::unowned_thread thread, std::string_view role) { |
| auto role_manager = incoming()->Connect<fuchsia_scheduler::RoleManager>(); |
| if (role_manager.is_error()) { |
| return role_manager.take_error(); |
| } |
| |
| zx::thread duplicate_thread; |
| zx_status_t status = |
| thread->duplicate(ZX_RIGHT_TRANSFER | ZX_RIGHT_MANAGE_THREAD, &duplicate_thread); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| fidl::Arena arena; |
| auto request = |
| fuchsia_scheduler::wire::RoleManagerSetRoleRequest::Builder(arena) |
| .target(fuchsia_scheduler::wire::RoleTarget::WithThread(std::move(duplicate_thread))) |
| .role(fuchsia_scheduler::wire::RoleName{fidl::StringView::FromExternal(role)}) |
| .Build(); |
| auto result = fidl::WireCall(*role_manager)->SetRole(request); |
| if (result.status() != ZX_OK) { |
| return zx::error(result.status()); |
| } |
| if (!result.value().is_ok()) { |
| return result.value().take_error(); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<std::string> Driver::GetVariable(const char* name) { |
| auto boot_args = incoming()->Connect<fuchsia_boot::Arguments>(); |
| if (boot_args.is_error()) { |
| return boot_args.take_error(); |
| } |
| |
| auto result = fidl::WireCall(*boot_args)->GetString(fidl::StringView::FromExternal(name)); |
| if (!result.ok() || result->value.is_null() || result->value.empty()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| return zx::ok(std::string(result->value.data(), result->value.size())); |
| } |
| |
| zx_status_t Driver::GetProtocol(uint32_t proto_id, void* out) { |
| if (!parent_client_) { |
| FDF_LOGL(WARNING, *logger_, |
| "Invalid fuchsia.driver.compat.Device client. Failed to retrieve Banjo protocol."); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return RunOnDispatcher([proto_id, out, &client = parent_client_, &logger = logger_]() { |
| static uint64_t process_koid = []() { |
| zx_info_handle_basic_t basic; |
| ZX_ASSERT(zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, |
| nullptr) == ZX_OK); |
| return basic.koid; |
| }(); |
| |
| fidl::WireResult result = client.sync()->GetBanjoProtocol(proto_id, process_koid); |
| if (!result.ok()) { |
| FDF_LOGL(ERROR, *logger, "Failed to send request to get banjo protocol: %s", |
| result.status_string()); |
| return result.status(); |
| } |
| if (result->is_error()) { |
| FDF_LOGL(ERROR, *logger, "Failed to get banjo protocol: %s", |
| zx_status_get_string(result->error_value())); |
| return result->error_value(); |
| } |
| |
| struct GenericProtocol { |
| const void* ops; |
| void* ctx; |
| }; |
| |
| auto proto = static_cast<GenericProtocol*>(out); |
| proto->ops = reinterpret_cast<const void*>(result->value()->ops); |
| proto->ctx = reinterpret_cast<void*>(result->value()->context); |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t Driver::GetFragmentProtocol(const char* fragment, uint32_t proto_id, void* out) { |
| auto iter = parent_clients_.find(fragment); |
| if (iter == parent_clients_.end()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to find compat client of fragment"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| fidl::WireClient<fuchsia_driver_compat::Device>& client = iter->second; |
| |
| static uint64_t process_koid = []() { |
| zx_info_handle_basic_t basic; |
| ZX_ASSERT(zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, |
| nullptr) == ZX_OK); |
| return basic.koid; |
| }(); |
| |
| fidl::WireResult result = client.sync()->GetBanjoProtocol(proto_id, process_koid); |
| if (!result.ok()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to send request to get banjo protocol: %s", |
| result.status_string()); |
| return result.status(); |
| } |
| if (result->is_error()) { |
| FDF_LOGL(ERROR, *logger_, "Failed to get banjo protocol: %s", |
| zx_status_get_string(result->error_value())); |
| return result->error_value(); |
| } |
| |
| struct GenericProtocol { |
| const void* ops; |
| void* ctx; |
| }; |
| |
| auto proto = static_cast<GenericProtocol*>(out); |
| proto->ops = reinterpret_cast<const void*>(result->value()->ops); |
| proto->ctx = reinterpret_cast<void*>(result->value()->context); |
| return ZX_OK; |
| } |
| |
| void Driver::CompleteStart(zx::result<> result) { |
| if (start_completer_.has_value()) { |
| start_completer_.value()(result); |
| start_completer_.reset(); |
| } else { |
| // This can happen if the driver's bind hook ends up returning an error after successfully |
| // creating a device through DdkAdd. This is because the device add will schedule an InitReply, |
| // inside of which we always call CompleteStart for this initial device. Regardless of if the |
| // InitReply is calling this successfully or with an error, since the driver's bind hook |
| // returned an error already to the start completer, we can just log it. |
| // |
| // TODO(https://fxbug.dev/323581670): Improve the compat driver state flow so this isn't needed. |
| FDF_LOGL(INFO, *logger_, |
| "Called Driver::CompleteStart with %s, but start completer has already been used.", |
| result.status_string()); |
| } |
| } |
| |
| } // namespace compat |
| |
| EXPORT_FUCHSIA_DRIVER_REGISTRATION_V1(compat::CompatDriverServer::initialize, |
| compat::CompatDriverServer::destroy); |