blob: c55d6593fdf78525760ad14d8f32fa8e278df2e7 [file] [log] [blame]
// Copyright 2020 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/bin/driver_host2/driver_host.h"
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/loop.h>
#include <lib/fdio/directory.h>
#include <lib/fit/function.h>
#include <zircon/dlfcn.h>
#include "src/devices/lib/driver2/start_args.h"
#include "src/devices/lib/log/log.h"
#include "src/lib/storage/vfs/cpp/service.h"
namespace fdf = fuchsia_driver_framework;
namespace fio = fuchsia_io;
namespace frunner = fuchsia_component_runner;
namespace {
class FileEventHandler : public fidl::WireAsyncEventHandler<fio::File> {
public:
explicit FileEventHandler(const std::string& binary_value) : binary_value_(binary_value) {}
void Unbound(fidl::UnbindInfo info) override {
if (info.status != ZX_OK) {
LOGF(ERROR, "Failed to start driver '/pkg/%s', could not open library: %s",
binary_value_.c_str(), zx_status_get_string(info.status));
}
}
private:
const std::string& binary_value_;
};
} // namespace
zx::status<std::unique_ptr<Driver>> Driver::Load(std::string url, std::string binary, zx::vmo vmo) {
void* library = dlopen_vmo(vmo.get(), RTLD_NOW);
if (library == nullptr) {
LOGF(ERROR, "Failed to start driver, could not load library: %s", dlerror());
return zx::error(ZX_ERR_INTERNAL);
}
auto record = static_cast<DriverRecordV1*>(dlsym(library, "__fuchsia_driver_record__"));
if (record == nullptr) {
LOGF(ERROR, "Failed to start driver, driver record not found");
return zx::error(ZX_ERR_NOT_FOUND);
}
if (record->version != 1) {
LOGF(ERROR, "Failed to start driver, unknown driver record version: %lu", record->version);
return zx::error(ZX_ERR_WRONG_TYPE);
}
return zx::ok(std::make_unique<Driver>(std::move(url), std::move(binary), library, record));
}
Driver::Driver(std::string url, std::string binary, void* library, DriverRecordV1* record)
: url_(std::move(url)), binary_(std::move(binary)), library_(library), record_(record) {}
Driver::~Driver() {
zx_status_t status = record_->stop(opaque_);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to stop driver: %s", zx_status_get_string(status));
}
dlclose(library_);
if (binding_.has_value()) {
binding_->Unbind();
}
}
void Driver::set_binding(fidl::ServerBindingRef<fuchsia_driver_framework::Driver> binding) {
binding_.emplace(std::move(binding));
}
zx::status<> Driver::Start(fidl::OutgoingMessage& start_args, async_dispatcher_t* dispatcher) {
auto converted = fidl::OutgoingToIncomingMessage(start_args);
if (converted.status() != ZX_OK) {
return zx::error(converted.status());
}
zx_status_t status = record_->start(converted.incoming_message(), dispatcher, &opaque_);
// After calling |record_->start|, we assume it has taken ownership of
// the handles from |start_args|, and can therefore relinquish ownership.
converted.ReleaseHandles();
return zx::make_status(status);
}
DriverHost::DriverHost(inspect::Inspector* inspector, async::Loop* loop) : loop_(loop) {
inspector->GetRoot().CreateLazyNode(
"drivers", [this] { return Inspect(); }, inspector);
}
fit::promise<inspect::Inspector> DriverHost::Inspect() {
inspect::Inspector inspector;
auto& root = inspector.GetRoot();
size_t i = 0;
for (auto& driver : drivers_) {
auto child = root.CreateChild("driver-" + std::to_string(++i));
child.CreateString("url", driver.url(), &inspector);
child.CreateString("binary", driver.binary(), &inspector);
inspector.emplace(std::move(child));
}
return fit::make_ok_promise(std::move(inspector));
}
zx::status<> DriverHost::PublishDriverHost(const fbl::RefPtr<fs::PseudoDir>& svc_dir) {
const auto service = [this](zx::channel request) {
fidl::BindServer(loop_->dispatcher(), std::move(request), this);
return ZX_OK;
};
zx_status_t status = svc_dir->AddEntry(fidl::DiscoverableProtocolName<fdf::DriverHost>,
fbl::MakeRefCounted<fs::Service>(service));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to add directory entry '%s': %s",
fidl::DiscoverableProtocolName<fdf::DriverHost>, zx_status_get_string(status));
}
return zx::make_status(status);
}
void DriverHost::Start(fdf::wire::DriverStartArgs start_args,
fidl::ServerEnd<fuchsia_driver_framework::Driver> request,
StartCompleter::Sync& completer) {
if (!start_args.has_url()) {
LOGF(ERROR, "Failed to start driver, missing 'url' argument");
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
std::string url(start_args.url().get());
auto pkg = start_args.has_ns() ? start_args::NsValue(start_args.ns(), "/pkg")
: zx::error(ZX_ERR_INVALID_ARGS);
if (pkg.is_error()) {
LOGF(ERROR, "Failed to start driver, missing '/pkg' directory: %s",
zx_status_get_string(pkg.error_value()));
completer.Close(pkg.error_value());
return;
}
zx::status<std::string> binary = start_args.has_program()
? start_args::ProgramValue(start_args.program(), "binary")
: zx::error(ZX_ERR_INVALID_ARGS);
if (binary.is_error()) {
LOGF(ERROR, "Failed to start driver, missing 'binary' argument: %s",
zx_status_get_string(binary.error_value()));
completer.Close(binary.error_value());
return;
}
// Open the driver's binary within the driver's package.
auto endpoints = fidl::CreateEndpoints<fio::File>();
if (endpoints.is_error()) {
completer.Close(endpoints.status_value());
return;
}
zx_status_t status =
fdio_open_at(pkg->handle(), binary->data(),
fio::wire::OPEN_RIGHT_READABLE | fio::wire::OPEN_RIGHT_EXECUTABLE,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
LOGF(ERROR, "Failed to start driver '/pkg/%s', could not open library: %s", binary->data(),
zx_status_get_string(status));
completer.Close(status);
return;
}
// We encode start_args outside of callback in order to access stack-allocated
// data before it is destroyed.
auto message = std::make_unique<fdf::wire::DriverStartArgs::OwnedEncodedMessage>(&start_args);
if (!message->ok()) {
LOGF(ERROR, "Failed to start driver '/pkg/%s', could not encode start args: %s", binary->data(),
message->error());
completer.Close(message->status());
return;
}
// Once we receive the VMO from the call to GetBuffer, we can load the driver
// into this driver host. We move the storage and encoded for start_args into
// this callback to extend its lifetime.
fidl::Client<fio::File> file(std::move(endpoints->client), loop_->dispatcher(),
std::make_shared<FileEventHandler>(binary.value()));
auto callback = [this, request = std::move(request), completer = completer.ToAsync(),
url = std::move(url), binary = std::move(binary.value()),
message = std::move(message),
_ = file.Clone()](fidl::WireResponse<fio::File::GetBuffer>* response) mutable {
if (response->s != ZX_OK) {
LOGF(ERROR, "Failed to start driver '/pkg/%s', could not get library VMO: %s", binary.data(),
zx_status_get_string(response->s));
completer.Close(response->s);
return;
}
zx_status_t status =
response->buffer->vmo.set_property(ZX_PROP_NAME, binary.data(), binary.size());
if (status != ZX_OK) {
LOGF(ERROR, "Failed to start driver '/pkg/%s', could not name library VMO: %s", binary.data(),
zx_status_get_string(status));
completer.Close(status);
return;
}
auto driver = Driver::Load(std::move(url), binary, std::move(response->buffer->vmo));
if (driver.is_error()) {
completer.Close(driver.error_value());
return;
}
auto driver_ptr = driver.value().get();
auto bind = fidl::BindServer<Driver>(loop_->dispatcher(), std::move(request), driver_ptr,
[this](Driver* driver, auto, auto) {
drivers_.erase(*driver);
// If this is the last driver, shutdown the driver host.
if (drivers_.is_empty()) {
loop_->Quit();
}
});
driver->set_binding(std::move(bind));
drivers_.push_back(std::move(driver.value()));
auto start = driver_ptr->Start(message->GetOutgoingMessage(), loop_->dispatcher());
if (start.is_error()) {
LOGF(ERROR, "Failed to start driver '/pkg/%s': %s", binary.data(), start.status_string());
completer.Close(start.error_value());
return;
}
LOGF(INFO, "Started '%s'", binary.data());
};
file->GetBuffer(fio::wire::VMO_FLAG_READ | fio::wire::VMO_FLAG_EXEC | fio::wire::VMO_FLAG_PRIVATE,
std::move(callback));
}