| // 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 <lib/async-loop/cpp/loop.h> |
| #include <lib/ddk/binding_priv.h> |
| #include <lib/fpromise/bridge.h> |
| #include <lib/service/llcpp/service.h> |
| #include <zircon/dlfcn.h> |
| |
| #include "src/devices/lib/driver2/promise.h" |
| #include "src/devices/lib/driver2/record_cpp.h" |
| #include "src/devices/lib/driver2/start_args.h" |
| #include "src/devices/misc/drivers/compat/loader.h" |
| |
| namespace fboot = fuchsia_boot; |
| namespace fdf = fuchsia_driver_framework; |
| namespace fio = fuchsia_io; |
| namespace fldsvc = fuchsia_ldsvc; |
| |
| using fpromise::bridge; |
| using fpromise::error; |
| using fpromise::join_promises; |
| using fpromise::ok; |
| using fpromise::promise; |
| using fpromise::result; |
| |
| zx::resource kRootResource; |
| |
| namespace { |
| |
| constexpr auto kOpenFlags = fio::wire::kOpenRightReadable | fio::wire::kOpenRightExecutable | |
| fio::wire::kOpenFlagNotDirectory; |
| constexpr auto kVmoFlags = fio::wire::kVmoFlagRead | fio::wire::kVmoFlagExec; |
| constexpr auto kLibDriverPath = "/pkg/driver/compat.so"; |
| |
| template <typename T> |
| T GetSymbol(const fidl::VectorView<fdf::wire::NodeSymbol>& symbols, std::string_view name, |
| T default_value = nullptr) { |
| auto value = driver::SymbolValue<T>(symbols, name); |
| return value.is_ok() ? *value : default_value; |
| } |
| |
| } // namespace |
| |
| namespace compat { |
| |
| Driver::Driver(async_dispatcher_t* dispatcher, fidl::WireSharedClient<fdf::Node> node, |
| driver::Namespace ns, driver::Logger logger, std::string_view url, |
| std::string_view name, void* context, const zx_protocol_device_t* ops, |
| std::optional<Device*> parent) |
| : dispatcher_(dispatcher), |
| executor_(dispatcher), |
| outgoing_(dispatcher), |
| ns_(std::move(ns)), |
| logger_(std::move(logger)), |
| device_(name, context, ops, parent, inner_logger_, dispatcher) { |
| device_.Bind(std::move(node)); |
| } |
| |
| Driver::~Driver() { |
| if (record_ != nullptr && record_->ops->release != nullptr) { |
| record_->ops->release(context_); |
| } |
| dlclose(library_); |
| } |
| |
| zx_driver_t* Driver::ZxDriver() { return static_cast<zx_driver_t*>(this); } |
| |
| zx::status<std::unique_ptr<Driver>> Driver::Start(fdf::wire::DriverStartArgs& start_args, |
| async_dispatcher_t* dispatcher, |
| fidl::WireSharedClient<fdf::Node> node, |
| driver::Namespace ns, driver::Logger logger) { |
| fidl::VectorView<fdf::wire::NodeSymbol> symbols; |
| if (start_args.has_symbols()) { |
| symbols = start_args.symbols(); |
| } |
| auto name = GetSymbol<const char*>(symbols, kName, "compat-device"); |
| auto context = GetSymbol<void*>(symbols, kContext); |
| auto ops = GetSymbol<const zx_protocol_device_t*>(symbols, kOps); |
| std::optional<Device*> parent_opt; |
| if (auto parent = driver::SymbolValue<Device*>(symbols, kParent); parent.is_ok()) { |
| parent_opt = *parent; |
| } |
| |
| // Open the compat driver's binary within the package. |
| auto compat = driver::ProgramValue(start_args.program(), "compat"); |
| if (compat.is_error()) { |
| FDF_LOGL(ERROR, logger, "Field \"compat\" missing from component manifest"); |
| return compat.take_error(); |
| } |
| |
| auto driver = |
| std::make_unique<Driver>(dispatcher, std::move(node), std::move(ns), std::move(logger), |
| start_args.url().get(), name, context, ops, parent_opt); |
| auto result = driver->Run(std::move(start_args.outgoing_dir()), "/pkg/" + *compat); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| return zx::ok(std::move(driver)); |
| } |
| |
| zx::status<> Driver::Run(fidl::ServerEnd<fio::Directory> outgoing_dir, |
| std::string_view driver_path) { |
| auto serve = outgoing_.Serve(std::move(outgoing_dir)); |
| if (serve.is_error()) { |
| return serve.take_error(); |
| } |
| |
| auto root_resource = driver::Connect<fboot::RootResource>(ns_, dispatcher_) |
| .and_then(fit::bind_member(this, &Driver::GetRootResource)); |
| auto loader_vmo = driver::Connect<fio::File>(ns_, dispatcher_, kLibDriverPath, kOpenFlags) |
| .and_then(fit::bind_member(this, &Driver::GetBuffer)); |
| auto driver_vmo = driver::Connect<fio::File>(ns_, dispatcher_, driver_path, kOpenFlags) |
| .and_then(fit::bind_member(this, &Driver::GetBuffer)); |
| auto start_driver = |
| join_promises(std::move(root_resource), std::move(loader_vmo), std::move(driver_vmo)) |
| .then(fit::bind_member(this, &Driver::Join)) |
| .and_then(fit::bind_member(this, &Driver::LoadDriver)) |
| .and_then(fit::bind_member(this, &Driver::StartDriver)) |
| .or_else(fit::bind_member(this, &Driver::StopDriver)) |
| .wrap_with(scope_); |
| executor_.schedule_task(std::move(start_driver)); |
| return zx::ok(); |
| } |
| |
| promise<zx::resource, zx_status_t> Driver::GetRootResource( |
| const fidl::WireSharedClient<fboot::RootResource>& root_resource) { |
| bridge<zx::resource, zx_status_t> bridge; |
| auto callback = [this, completer = std::move(bridge.completer)]( |
| fidl::WireUnownedResult<fboot::RootResource::Get>& result) mutable { |
| if (!result.ok()) { |
| FDF_LOG(WARNING, "Failed to get root resource: %s", result.FormatDescription().data()); |
| FDF_LOG(WARNING, "Assuming test environment and continuing"); |
| completer.complete_error(result.status()); |
| return; |
| } |
| completer.complete_ok(std::move(result->resource)); |
| }; |
| root_resource->Get(std::move(callback)); |
| return bridge.consumer.promise_or(error(ZX_ERR_UNAVAILABLE)); |
| } |
| |
| promise<zx::vmo, zx_status_t> Driver::GetBuffer(const fidl::WireSharedClient<fio::File>& file) { |
| bridge<zx::vmo, zx_status_t> bridge; |
| auto callback = [this, completer = std::move(bridge.completer)]( |
| fidl::WireUnownedResult<fio::File::GetBuffer>& result) mutable { |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "Failed to get buffer: %s", result.FormatDescription().data()); |
| completer.complete_error(result.status()); |
| return; |
| } |
| if (result->s != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to get buffer: %s", zx_status_get_string(result->s)); |
| completer.complete_error(result->s); |
| return; |
| } |
| completer.complete_ok(std::move(result->buffer->vmo)); |
| }; |
| file->GetBuffer(kVmoFlags, std::move(callback)); |
| return bridge.consumer.promise_or(error(ZX_ERR_UNAVAILABLE)); |
| } |
| |
| result<std::tuple<zx::vmo, zx::vmo>, zx_status_t> Driver::Join( |
| result<std::tuple<result<zx::resource, zx_status_t>, result<zx::vmo, zx_status_t>, |
| result<zx::vmo, zx_status_t>>>& results) { |
| if (results.is_error()) { |
| return error(ZX_ERR_INTERNAL); |
| } |
| auto& [root_resource, loader_vmo, driver_vmo] = results.value(); |
| if (root_resource.is_ok()) { |
| kRootResource = root_resource.take_value(); |
| } |
| if (loader_vmo.is_error()) { |
| return loader_vmo.take_error_result(); |
| } |
| if (driver_vmo.is_error()) { |
| return driver_vmo.take_error_result(); |
| } |
| return fpromise::ok(std::make_tuple(loader_vmo.take_value(), driver_vmo.take_value())); |
| } |
| |
| result<void, zx_status_t> Driver::LoadDriver(std::tuple<zx::vmo, zx::vmo>& vmos) { |
| auto& [loader_vmo, driver_vmo] = vmos; |
| |
| // Replace loader service. |
| auto endpoints = fidl::CreateEndpoints<fldsvc::Loader>(); |
| if (endpoints.is_error()) { |
| return error(endpoints.status_value()); |
| } |
| zx::channel loader_channel(dl_set_loader_service(endpoints->client.channel().release())); |
| fidl::ClientEnd<fldsvc::Loader> loader_client(std::move(loader_channel)); |
| auto clone = service::Clone(loader_client, service::AssumeProtocolComposesNode); |
| if (clone.is_error()) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', could not clone loader client: %s", url_.data(), |
| clone.status_string()); |
| return error(clone.status_value()); |
| } |
| |
| // Start loader. |
| async::Loop loader_loop(&kAsyncLoopConfigNeverAttachToThread); |
| zx_status_t status = loader_loop.StartThread("loader-loop"); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', could not start thread for loader loop: %s", |
| url_.data(), zx_status_get_string(status)); |
| return error(status); |
| } |
| Loader loader(loader_loop.dispatcher()); |
| auto bind = |
| loader.Bind(fidl::ClientEnd<fldsvc::Loader>(std::move(loader_client)), std::move(loader_vmo)); |
| if (bind.is_error()) { |
| return error(bind.status_value()); |
| } |
| fidl::BindServer(loader_loop.dispatcher(), std::move(endpoints->server), &loader); |
| |
| // Open driver. |
| library_ = dlopen_vmo(driver_vmo.get(), RTLD_NOW); |
| if (library_ == nullptr) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', could not load library: %s", url_.data(), |
| dlerror()); |
| return error(ZX_ERR_INTERNAL); |
| } |
| |
| // Return original loader service. |
| loader_channel.reset(dl_set_loader_service(clone->channel().release())); |
| |
| // Load and verify symbols. |
| auto note = static_cast<const zircon_driver_note_t*>(dlsym(library_, "__zircon_driver_note__")); |
| if (note == nullptr) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', driver note not found", url_.data()); |
| return error(ZX_ERR_BAD_STATE); |
| } |
| FDF_LOG(INFO, "Loaded driver '%s'", note->payload.name); |
| record_ = static_cast<zx_driver_rec_t*>(dlsym(library_, "__zircon_driver_rec__")); |
| if (record_ == nullptr) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', driver record not found", url_.data()); |
| return error(ZX_ERR_BAD_STATE); |
| } |
| if (record_->ops == nullptr) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', missing driver ops", url_.data()); |
| return error(ZX_ERR_BAD_STATE); |
| } |
| if (record_->ops->version != DRIVER_OPS_VERSION) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', incorrect driver version", url_.data()); |
| return error(ZX_ERR_WRONG_TYPE); |
| } |
| if (record_->ops->bind == nullptr && record_->ops->create == nullptr) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', missing '%s'", url_.data(), |
| (record_->ops->bind == nullptr ? "bind" : "create")); |
| return error(ZX_ERR_BAD_STATE); |
| } else if (record_->ops->bind != nullptr && record_->ops->create != nullptr) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', both 'bind' and 'create' are defined", url_.data()); |
| return error(ZX_ERR_INVALID_ARGS); |
| } |
| record_->driver = ZxDriver(); |
| |
| // Create logger. |
| auto inner_logger = driver::Logger::Create(ns_, dispatcher_, note->payload.name); |
| if (inner_logger.is_error()) { |
| return error(inner_logger.status_value()); |
| } |
| inner_logger_ = std::move(*inner_logger); |
| return ok(); |
| } |
| |
| result<void, zx_status_t> Driver::StartDriver() { |
| if (record_->ops->init != nullptr) { |
| // If provided, run init. |
| zx_status_t status = record_->ops->init(&context_); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to load driver '%s', 'init' failed: %s", url_.data(), |
| zx_status_get_string(status)); |
| return error(status); |
| } |
| } |
| 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_LOG(ERROR, "Failed to load driver '%s', 'bind' failed: %s", url_.data(), |
| zx_status_get_string(status)); |
| return error(status); |
| } |
| } else { |
| // Else, run create and return. |
| auto client_end = ns_.Connect<fboot::Items>(); |
| if (client_end.is_error()) { |
| return 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_LOG(ERROR, "Failed to load driver '%s', 'create' failed: %s", url_.data(), |
| zx_status_get_string(status)); |
| return error(status); |
| } |
| } |
| if (!device_.HasChildren()) { |
| FDF_LOG(ERROR, "Driver '%s' did not add a child device", url_.data()); |
| return error(ZX_ERR_BAD_STATE); |
| } |
| return ok(); |
| } |
| |
| result<> Driver::StopDriver(const zx_status_t& status) { |
| FDF_LOG(ERROR, "Failed to start driver '%s': %s", url_.data(), zx_status_get_string(status)); |
| device_.Unbind(); |
| return ok(); |
| } |
| |
| void* Driver::Context() const { return context_; } |
| |
| void Driver::Log(FuchsiaLogSeverity severity, const char* tag, const char* file, int line, |
| const char* msg, va_list args) { |
| inner_logger_.logvf(severity, tag, file, line, msg, args); |
| } |
| |
| } // namespace compat |
| |
| FUCHSIA_DRIVER_RECORD_CPP_V1(compat::Driver); |