blob: 5f78ccb860b8b2fe0a2bb95aeffcefa1a6f5cfbc [file] [log] [blame]
// Copyright 2017 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 "driver.h"
#include <dirent.h>
#include <fcntl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/ddk/binding.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <new>
#include <string>
#include <driver-info/driver-info.h>
#include <fbl/string_printf.h>
#include "fdio.h"
#include "src/devices/lib/log/log.h"
namespace {
namespace fio = fuchsia_io;
struct AddContext {
fidl::WireSyncClient<fuchsia_boot::Arguments>* boot_args;
const char* libname;
DriverLoadCallback func;
// This is optional. If present, holds the driver shared library that was loaded ephemerally.
zx::vmo vmo;
};
bool is_driver_disabled(fidl::WireSyncClient<fuchsia_boot::Arguments>* boot_args,
const char* name) {
if (!boot_args) {
return false;
}
// driver.<driver_name>.disable
auto option = fbl::StringPrintf("driver.%s.disable", name);
auto disabled = (*boot_args)->GetBool(fidl::StringView::FromExternal(option), false);
return disabled.ok() && disabled.value().value;
}
bool is_driver_eager_fallback(fidl::WireSyncClient<fuchsia_boot::Arguments>* boot_args,
const char* name) {
if (!boot_args) {
return false;
}
std::vector<fbl::String> eager_fallback_drivers;
auto drivers = (*boot_args)->GetString("devmgr.bind-eager");
if (drivers.ok() && !drivers.value().value.is_null() && !drivers.value().value.empty()) {
std::string list(drivers.value().value.data(), drivers.value().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));
}
for (auto& driver : eager_fallback_drivers) {
if (driver.compare(name) == 0) {
return true;
}
}
return false;
}
void found_driver(zircon_driver_note_payload_t* note, const zx_bind_inst_t* bi,
const uint8_t* bytecode, void* cookie) {
auto context = static_cast<AddContext*>(cookie);
// ensure strings are terminated
note->name[sizeof(note->name) - 1] = 0;
note->vendor[sizeof(note->vendor) - 1] = 0;
note->version[sizeof(note->version) - 1] = 0;
if (is_driver_disabled(context->boot_args, note->name)) {
return;
}
auto drv = std::make_unique<Driver>();
if (drv == nullptr) {
return;
}
drv->bytecode_version = note->bytecodeversion;
// Check the bytecode version and populate binding or bytecode based on it.
if (drv->bytecode_version == 1) {
auto binding = std::make_unique<zx_bind_inst_t[]>(note->bindcount);
if (binding == nullptr) {
return;
}
const size_t bindlen = note->bindcount * sizeof(zx_bind_inst_t);
memcpy(binding.get(), bi, bindlen);
drv->binding = std::move(binding);
drv->binding_size = static_cast<uint32_t>(bindlen);
} else if (drv->bytecode_version == 2) {
auto binding = std::make_unique<uint8_t[]>(note->bytecount);
if (binding == nullptr) {
return;
}
const size_t bytecount = note->bytecount;
memcpy(binding.get(), bytecode, bytecount);
drv->binding = std::move(binding);
drv->binding_size = static_cast<uint32_t>(bytecount);
} else {
LOGF(ERROR, "Invalid bytecode version: %i", drv->bytecode_version);
return;
}
drv->flags = note->flags;
drv->libname = context->libname;
drv->name = note->name;
if (note->version[0] == '*') {
drv->fallback = true;
// TODO(fxbug.dev/44586): remove this once a better solution for driver prioritization is
// implemented.
if (is_driver_eager_fallback(context->boot_args, drv->name.c_str())) {
LOGF(INFO, "Marking fallback driver '%s' as eager.", drv->name.c_str());
drv->fallback = false;
}
}
if (context->vmo.is_valid()) {
drv->dso_vmo = std::move(context->vmo);
}
VLOGF(2, "Found driver: %s", (char*)cookie);
VLOGF(2, " name: %s", note->name);
VLOGF(2, " vendor: %s", note->vendor);
VLOGF(2, " version: %s", note->version);
VLOGF(2, " flags: %#x", note->flags);
VLOGF(2, " binding:");
for (size_t n = 0; n < note->bindcount; n++) {
VLOGF(2, " %03zd: %08x %08x", n, bi[n].op, bi[n].arg);
}
context->func(drv.release(), note->version);
}
} // namespace
void find_loadable_drivers(fidl::WireSyncClient<fuchsia_boot::Arguments>* boot_args,
const std::string& path, DriverLoadCallback func) {
DIR* dir = opendir(path.c_str());
if (dir == nullptr) {
return;
}
AddContext context = {boot_args, "", std::move(func), zx::vmo{}};
struct dirent* de;
while ((de = readdir(dir)) != nullptr) {
if (de->d_name[0] == '.') {
continue;
}
if (de->d_type != DT_REG) {
continue;
}
auto libname = fbl::StringPrintf("%s/%s", path.c_str(), de->d_name);
context.libname = libname.data();
int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
if (fd < 0) {
continue;
}
zx_status_t status = di_read_driver_info(fd, &context, found_driver);
close(fd);
if (status == ZX_ERR_NOT_FOUND) {
LOGF(INFO, "Missing info from driver '%s'", libname.data());
} else if (status != ZX_OK) {
LOGF(ERROR, "Failed to read info from driver '%s': %s", libname.data(),
zx_status_get_string(status));
}
}
closedir(dir);
}
zx_status_t load_driver_vmo(fidl::WireSyncClient<fuchsia_boot::Arguments>* boot_args,
std::string_view libname, zx::vmo vmo, DriverLoadCallback func) {
zx_handle_t vmo_handle = vmo.get();
AddContext context = {boot_args, libname.data(), std::move(func), std::move(vmo)};
auto di_vmo_read = [](void* vmo, void* data, size_t len, size_t off) {
return zx_vmo_read(*((zx_handle_t*)vmo), data, off, len);
};
zx_status_t status = di_read_driver_info_etc(&vmo_handle, di_vmo_read, &context, found_driver);
if (status == ZX_ERR_NOT_FOUND) {
LOGF(INFO, "Missing info from driver '%s'", libname.data());
} else if (status != ZX_OK) {
LOGF(ERROR, "Failed to read info from driver '%s': %s", libname.data(),
zx_status_get_string(status));
}
return status;
}
zx_status_t load_vmo(std::string_view libname, zx::vmo* out_vmo) {
int fd = -1;
zx_status_t r = fdio_open_fd(libname.data(),
static_cast<uint32_t>(fio::wire::OpenFlags::kRightReadable |
fio::wire::OpenFlags::kRightExecutable),
&fd);
if (r != ZX_OK) {
LOGF(ERROR, "Cannot open driver '%s': %d", libname.data(), r);
return ZX_ERR_IO;
}
zx::vmo vmo;
r = fdio_get_vmo_exec(fd, vmo.reset_and_get_address());
close(fd);
if (r != ZX_OK) {
LOGF(ERROR, "Cannot get driver VMO '%s'", libname.data());
return r;
}
const char* vmo_name = strrchr(libname.data(), '/');
if (vmo_name != nullptr) {
++vmo_name;
} else {
vmo_name = libname.data();
}
r = vmo.set_property(ZX_PROP_NAME, vmo_name, strlen(vmo_name));
if (r != ZX_OK) {
LOGF(ERROR, "Cannot set name on driver VMO to '%s'", libname.data());
return r;
}
*out_vmo = std::move(vmo);
return r;
}
void load_driver(fidl::WireSyncClient<fuchsia_boot::Arguments>* boot_args, const char* path,
DriverLoadCallback func) {
// TODO: check for duplicate driver add
int fd = open(path, O_RDONLY);
if (fd < 0) {
LOGF(ERROR, "Cannot open driver '%s'", path);
return;
}
AddContext context = {boot_args, path, std::move(func), zx::vmo{}};
zx_status_t status = di_read_driver_info(fd, &context, found_driver);
close(fd);
if (status == ZX_ERR_NOT_FOUND) {
LOGF(INFO, "Missing info from driver '%s'", path);
} else if (status != ZX_OK) {
LOGF(ERROR, "Failed to read info from driver '%s': %s", path, zx_status_get_string(status));
}
}