blob: 36e06222518fce624434a0be9c17cb2612d6350f [file] [log] [blame]
// 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 "driver_loader.h"
#include <fcntl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/llcpp/connect_service.h>
#include <lib/service/llcpp/service.h>
#include <sched.h>
#include <unistd.h>
#include <zircon/threads.h>
#include <thread>
#include <variant>
#include <fbl/unique_fd.h>
#include "coordinator.h"
#include "src/devices/bin/driver_manager/manifest_parser.h"
#include "src/devices/lib/log/log.h"
namespace {
// Go through each field in the DriverInfo and copy it with a given allocator.
fuchsia_driver_development::wire::DriverInfo CopyDriverInfo(
fidl::AnyArena& allocator, fuchsia_driver_development::wire::DriverInfo& driver) {
auto allocated = fuchsia_driver_development::wire::DriverInfo::Builder(allocator);
if (driver.has_libname()) {
allocated.libname(allocator, driver.libname().get());
}
if (driver.has_name()) {
allocated.name(allocator, driver.name().get());
}
if (driver.has_url()) {
allocated.url(allocator, driver.url().get());
}
if (driver.has_bind_rules()) {
if (driver.bind_rules().is_bytecode_v1()) {
auto vector = fidl::VectorView<fuchsia_device_manager::wire::BindInstruction>(
allocator, driver.bind_rules().bytecode_v1().count());
for (size_t i = 0; i < vector.count(); i++) {
vector[i] = driver.bind_rules().bytecode_v1()[i];
}
allocated.bind_rules(
fuchsia_driver_development::wire::BindRulesBytecode::WithBytecodeV1(allocator, vector));
}
if (driver.bind_rules().is_bytecode_v2()) {
auto vector = fidl::VectorView<uint8_t>(allocator, driver.bind_rules().bytecode_v2().count());
for (size_t i = 0; i < driver.bind_rules().bytecode_v2().count(); i++) {
vector[i] = driver.bind_rules().bytecode_v2()[i];
}
allocated.bind_rules(fuchsia_driver_development::wire::BindRulesBytecode::WithBytecodeV2(
allocator, std::move(vector)));
}
}
if (driver.has_package_type()) {
allocated.package_type(driver.package_type());
}
return allocated.Build();
}
zx::status<fdi::wire::MatchedDriverInfo> GetFidlMatchedDriverInfo(fdi::wire::MatchedDriver driver) {
if (driver.is_device_group_node()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
if (driver.is_composite_driver()) {
if (!driver.composite_driver().has_driver_info()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(driver.composite_driver().driver_info());
}
return zx::ok(driver.driver());
}
MatchedCompositeDevice CreateMatchedCompositeDevice(
fdi::wire::MatchedCompositeInfo composite_info) {
MatchedCompositeDevice composite = {};
if (composite_info.has_num_nodes()) {
composite.num_nodes = composite_info.num_nodes();
}
if (composite_info.has_node_index()) {
composite.node = composite_info.node_index();
}
if (composite_info.has_composite_name()) {
composite.name =
std::string(composite_info.composite_name().data(), composite_info.composite_name().size());
}
if (composite_info.has_node_names()) {
std::vector<std::string> names;
for (auto& name : composite_info.node_names()) {
names.push_back(std::string(name.data(), name.size()));
}
composite.node_names = std::move(names);
}
return composite;
}
zx::status<MatchedDeviceGroupInfo> CreateMatchedDeviceGroupInfo(
fdi::wire::MatchedDeviceGroupNodeInfo device_group_node_info) {
// Currently we only support only one device group in the list.
// TODO(fxb/103208): Update the DFv1 device group implementation so it
// can handle multiple device groups.
if (!device_group_node_info.has_device_groups() ||
device_group_node_info.device_groups().count() != 1) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
auto device_group = device_group_node_info.device_groups().at(0);
if (!device_group.has_topological_path()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (!device_group.has_node_index()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(MatchedDeviceGroupInfo{
.topological_path = std::string(device_group.topological_path().data(),
device_group.topological_path().size()),
.node_index = device_group.node_index(),
});
}
bool ShouldUseUniversalResolver(fdi::wire::DriverPackageType package_type) {
return package_type == fdi::wire::DriverPackageType::kUniverse ||
package_type == fdi::wire::DriverPackageType::kCached;
}
} // namespace
DriverLoader::~DriverLoader() {
if (system_loading_thread_) {
system_loading_thread_->join();
}
}
void DriverLoader::StartSystemLoadingThread(Coordinator* coordinator) {
if (system_loading_thread_) {
LOGF(ERROR, "DriverLoader: StartLoadingThread cannot be called twice!\n");
return;
}
system_loading_thread_ = std::thread([coordinator]() {
fbl::unique_fd fd(open("/system", O_RDONLY));
if (fd.get() < 0) {
LOGF(WARNING, "Unable to open '/system', system drivers are disabled");
return;
}
fbl::DoublyLinkedList<std::unique_ptr<Driver>> drivers;
auto driver_added = [&drivers](Driver* drv, const char* version) {
std::unique_ptr<Driver> driver(drv);
LOGF(INFO, "Adding driver '%s' '%s'", driver->name.data(), driver->libname.data());
if (load_vmo(driver->libname.data(), &driver->dso_vmo)) {
LOGF(ERROR, "Driver '%s' '%s' could not cache DSO", driver->name.data(),
driver->libname.data());
}
// De-prioritize drivers that are "fallback".
if (driver->fallback) {
drivers.push_back(std::move(driver));
} else {
drivers.push_front(std::move(driver));
}
};
find_loadable_drivers(coordinator->boot_args(), "/system/driver", driver_added);
async::PostTask(coordinator->dispatcher(),
[coordinator = coordinator, drivers = std::move(drivers)]() mutable {
coordinator->AddAndBindDrivers(std::move(drivers));
coordinator->BindFallbackDrivers();
});
});
constexpr char name[] = "driver-loader-thread";
zx_object_set_property(native_thread_get_zx_handle(system_loading_thread_->native_handle()),
ZX_PROP_NAME, name, sizeof(name));
}
const Driver* DriverLoader::LibnameToDriver(std::string_view libname) const {
for (const auto& drv : driver_index_drivers_) {
if (libname.compare(drv.libname) == 0) {
return &drv;
}
}
return nullptr;
}
void DriverLoader::WaitForBaseDrivers(fit::callback<void()> callback) {
// TODO(dgilhooley): Change this back to an ERROR once DriverIndex is used in all tests.
if (!driver_index_.is_valid()) {
LOGF(INFO, "%s: DriverIndex is not initialized", __func__);
return;
}
driver_index_->WaitForBaseDrivers().Then(
[this, callback = std::move(callback)](
fidl::WireUnownedResult<fdi::DriverIndex::WaitForBaseDrivers>& result) mutable {
if (!result.ok()) {
// Since IsolatedDevmgr doesn't use the ComponentFramework, DriverIndex can be
// closed before DriverManager during tests, which would mean we would see
// a ZX_ERR_PEER_CLOSED.
if (result.status() == ZX_ERR_PEER_CLOSED) {
LOGF(WARNING, "Connection to DriverIndex closed during WaitForBaseDrivers.");
} else {
LOGF(ERROR, "Failed to connect to DriverIndex: %s",
result.error().FormatDescription().c_str());
}
return;
}
include_fallback_drivers_ = true;
callback();
});
}
const Driver* DriverLoader::LoadDriverUrl(const std::string& driver_url,
bool use_universe_resolver) {
// Check if we've already loaded this driver. If we have then return it.
auto driver = LibnameToDriver(driver_url);
if (driver != nullptr) {
return driver;
}
// Pick the correct package resolver to use.
auto resolver = base_resolver_;
if (use_universe_resolver && universe_resolver_) {
resolver = universe_resolver_;
}
// We've never seen the driver before so add it, then return it.
auto fetched_driver = resolver->FetchDriver(driver_url);
if (fetched_driver.is_error()) {
LOGF(ERROR, "Error fetching driver: %s: %d", driver_url.data(), fetched_driver.error_value());
return nullptr;
}
// It's possible the driver is nullptr if it was disabled.
if (!fetched_driver.value()) {
return nullptr;
}
// Success. Return driver.
driver_index_drivers_.push_back(std::move(fetched_driver.value()));
return &driver_index_drivers_.back();
}
bool DriverLoader::MatchesLibnameDriverIndex(const std::string& driver_url,
std::string_view libname) {
if (libname.compare(driver_url) == 0) {
return true;
}
auto result = GetPathFromUrl(driver_url);
if (result.is_error()) {
return false;
}
if (libname.find('/') == std::string_view::npos) {
std::string abs_libname = std::string("/boot/driver/") + std::string(libname);
return result.value() == abs_libname;
}
return result.value() == libname;
}
zx_status_t DriverLoader::AddDeviceGroup(std::string_view topological_path,
fidl::VectorView<fdf::wire::DeviceGroupNode> nodes) {
fidl::Arena allocator;
auto result = driver_index_.sync()->AddDeviceGroup(fidl::StringView(allocator, topological_path),
std::move(nodes));
if (!result.ok()) {
LOGF(ERROR, "DriverIndex::AddDeviceGroup failed: %d", result.status());
}
return result.status();
}
const std::vector<MatchedDriver> DriverLoader::MatchDeviceDriverIndex(
const fbl::RefPtr<Device>& dev, const MatchDeviceConfig& config) {
if (!driver_index_.is_valid()) {
return std::vector<MatchedDriver>();
}
bool autobind = config.libname.empty();
fidl::Arena allocator;
auto& props = dev->props();
auto& str_props = dev->str_props();
size_t size = props.size() + str_props.size() + 2;
if (!autobind) {
size += 1;
}
fidl::VectorView<fdf::wire::NodeProperty> fidl_props(allocator, size);
size_t index = 0;
fidl_props[index++] =
fdf::wire::NodeProperty(allocator)
.set_key(allocator, fdf::wire::NodePropertyKey::WithIntValue(BIND_PROTOCOL))
.set_value(allocator, fdf::wire::NodePropertyValue::WithIntValue(dev->protocol_id()));
fidl_props[index++] =
fdf::wire::NodeProperty(allocator)
.set_key(allocator, fdf::wire::NodePropertyKey::WithIntValue(BIND_AUTOBIND))
.set_value(allocator, fdf::wire::NodePropertyValue::WithIntValue(autobind));
// If we are looking for a specific driver, we add a property to the device with the
// name of the driver we are looking for. Drivers can then bind to this.
if (!autobind) {
fidl_props[index++] =
fdf::wire::NodeProperty(allocator)
.set_key(allocator, fdf::wire::NodePropertyKey::WithStringValue(
allocator, allocator, "fuchsia.compat.LIBNAME"))
.set_value(allocator, fdf::wire::NodePropertyValue::WithStringValue(
allocator, allocator, config.libname));
}
for (size_t i = 0; i < props.size(); i++) {
fidl_props[index++] =
fdf::wire::NodeProperty(allocator)
.set_key(allocator, fdf::wire::NodePropertyKey::WithIntValue(props[i].id))
.set_value(allocator, fdf::wire::NodePropertyValue::WithIntValue(props[i].value));
}
for (size_t i = 0; i < str_props.size(); i++) {
auto prop = fdf::wire::NodeProperty(allocator).set_key(
allocator,
fdf::wire::NodePropertyKey::WithStringValue(allocator, allocator, str_props[i].key));
switch (str_props[i].value.index()) {
case StrPropValueType::Integer: {
prop.set_value(allocator, fdf::wire::NodePropertyValue::WithIntValue(
std::get<StrPropValueType::Integer>(str_props[i].value)));
break;
}
case StrPropValueType::String: {
prop.set_value(allocator, fdf::wire::NodePropertyValue::WithStringValue(
allocator, allocator,
std::get<StrPropValueType::String>(str_props[i].value)));
break;
}
case StrPropValueType::Bool: {
prop.set_value(allocator, fdf::wire::NodePropertyValue::WithBoolValue(
std::get<StrPropValueType::Bool>(str_props[i].value)));
break;
}
case StrPropValueType::Enum: {
prop.set_value(allocator, fdf::wire::NodePropertyValue::WithEnumValue(
allocator, allocator,
std::get<StrPropValueType::Enum>(str_props[i].value)));
break;
}
}
fidl_props[index++] = prop;
}
return MatchPropertiesDriverIndex(fidl_props, config);
}
const std::vector<MatchedDriver> DriverLoader::MatchPropertiesDriverIndex(
fidl::VectorView<fdf::wire::NodeProperty> props, const MatchDeviceConfig& config) {
std::vector<MatchedDriver> matched_drivers;
std::vector<MatchedDriver> matched_fallback_drivers;
if (!driver_index_.is_valid()) {
return matched_drivers;
}
fidl::Arena allocator;
fdf::wire::NodeAddArgs args(allocator);
args.set_properties(allocator, std::move(props));
auto result = driver_index_.sync()->MatchDriversV1(std::move(args));
if (!result.ok()) {
if (result.status() != ZX_OK) {
LOGF(ERROR, "DriverIndex::MatchDriversV1 failed: %d", result.status());
return matched_drivers;
}
}
// If there's no driver to match then DriverIndex will return ZX_ERR_NOT_FOUND.
if (result->is_error()) {
if (result->error_value() != ZX_ERR_NOT_FOUND) {
LOGF(ERROR, "DriverIndex: MatchDriversV1 returned error: %d", result->error_value());
}
return matched_drivers;
}
const auto& drivers = result->value()->drivers;
for (auto driver : drivers) {
if (driver.is_device_group_node()) {
auto device_group = CreateMatchedDeviceGroupInfo(driver.device_group_node());
if (device_group.is_error()) {
LOGF(ERROR,
"DriverIndex: MatchDriverV1 response is missing fields in MatchedDeviceGroupInfo");
continue;
}
matched_drivers.push_back(device_group.value());
continue;
}
auto fidl_driver_info = GetFidlMatchedDriverInfo(driver);
if (fidl_driver_info.is_error()) {
LOGF(ERROR, "DriverIndex: MatchedDriversV1 response is missing MatchedDriverInfo");
continue;
}
if (!fidl_driver_info->has_driver_url()) {
LOGF(ERROR, "DriverIndex: MatchDriversV1 response is missing a driver_url");
continue;
}
if (!fidl_driver_info->has_is_fallback()) {
LOGF(ERROR, "DriverIndex: MatchDriversV1 response is missing is_fallback");
continue;
}
std::string driver_url(fidl_driver_info->driver_url().get());
bool use_universe_resolver = false;
if (fidl_driver_info->has_package_type()) {
use_universe_resolver = ShouldUseUniversalResolver(fidl_driver_info->package_type());
}
auto loaded_driver = LoadDriverUrl(driver_url, use_universe_resolver);
if (!loaded_driver) {
continue;
}
if (!fidl_driver_info->is_fallback() && config.only_return_base_and_fallback_drivers &&
IsFuchsiaBootScheme(driver_url)) {
continue;
}
MatchedDriverInfo matched_driver_info = {};
matched_driver_info.driver = loaded_driver;
if (fidl_driver_info->has_colocate()) {
matched_driver_info.colocate = fidl_driver_info->colocate();
}
MatchedDriver matched_driver;
if (driver.is_composite_driver()) {
matched_driver = MatchedCompositeDriverInfo{
.composite = CreateMatchedCompositeDevice(driver.composite_driver()),
.driver_info = matched_driver_info,
};
} else {
matched_driver = matched_driver_info;
}
if (config.libname.empty() || MatchesLibnameDriverIndex(driver_url, config.libname)) {
if (fidl_driver_info->is_fallback()) {
if (include_fallback_drivers_ || !config.libname.empty()) {
matched_fallback_drivers.push_back(matched_driver);
}
} else {
matched_drivers.push_back(matched_driver);
}
}
}
// Fallback drivers need to be at the end of the matched drivers.
matched_drivers.insert(matched_drivers.end(), matched_fallback_drivers.begin(),
matched_fallback_drivers.end());
return matched_drivers;
}
zx::status<std::vector<fuchsia_driver_development::wire::DriverInfo>> DriverLoader::GetDriverInfo(
fidl::AnyArena& allocator, fidl::VectorView<fidl::StringView> filter) {
std::vector<fuchsia_driver_development::wire::DriverInfo> info;
auto driver_index_client = service::Connect<fuchsia_driver_development::DriverIndex>();
if (driver_index_client.is_error()) {
LOGF(WARNING, "Failed to connect to fuchsia_driver_development::DriverIndex\n");
return driver_index_client.take_error();
}
auto endpoints = fidl::CreateEndpoints<fuchsia_driver_development::DriverInfoIterator>();
if (endpoints.is_error()) {
LOGF(ERROR, "fidl::CreateEndpoints failed: %s\n", endpoints.status_string());
return endpoints.take_error();
}
auto driver_index = fidl::BindSyncClient(std::move(*driver_index_client));
auto info_result = driver_index->GetDriverInfo(filter, std::move(endpoints->server));
// There are still some environments where we can't connect to DriverIndex.
if (info_result.status() != ZX_OK) {
LOGF(INFO, "DriverIndex:GetDriverInfo failed: %d\n", info_result.status());
return zx::error(info_result.status());
}
auto iterator = fidl::BindSyncClient(std::move(endpoints->client));
for (;;) {
auto next_result = iterator->GetNext();
if (!next_result.ok()) {
// This is likely a pipelined error from the GetDriverInfo call above. We unfortunately
// cannot read the epitaph without using an async call.
LOGF(ERROR, "DriverInfoIterator.GetNext failed: %s\n",
next_result.FormatDescription().c_str());
break;
}
if (next_result.value().drivers.count() == 0) {
// When we receive 0 responses, we are done iterating.
break;
}
// Go through each driver info and create a copy.
for (auto& driver : next_result.value().drivers) {
info.push_back(CopyDriverInfo(allocator, driver));
}
}
return zx::ok(std::move(info));
}
std::vector<const Driver*> DriverLoader::GetAllDriverIndexDrivers() {
std::vector<const Driver*> drivers;
auto driver_index_client = service::Connect<fuchsia_driver_development::DriverIndex>();
if (driver_index_client.is_error()) {
LOGF(WARNING, "Failed to connect to fuchsia_driver_development::DriverIndex\n");
return drivers;
}
auto endpoints = fidl::CreateEndpoints<fuchsia_driver_development::DriverInfoIterator>();
if (endpoints.is_error()) {
LOGF(ERROR, "fidl::CreateEndpoints failed: %s\n", endpoints.status_string());
return drivers;
}
auto driver_index = fidl::BindSyncClient(std::move(*driver_index_client));
auto info_result = driver_index->GetDriverInfo(fidl::VectorView<fidl::StringView>(),
std::move(endpoints->server));
// There are still some environments where we can't connect to DriverIndex.
if (info_result.status() != ZX_OK) {
LOGF(INFO, "DriverIndex:GetDriverInfo failed: %d\n", info_result.status());
return drivers;
}
auto iterator = fidl::BindSyncClient(std::move(endpoints->client));
for (;;) {
auto next_result = iterator->GetNext();
if (!next_result.ok()) {
// This is likely a pipelined error from the GetDriverInfo call above. We unfortunately
// cannot read the epitaph without using an async call.
LOGF(ERROR, "DriverInfoIterator.GetNext failed: %s\n",
next_result.FormatDescription().c_str());
break;
}
if (next_result.value().drivers.count() == 0) {
// When we receive 0 responses, we are done iterating.
break;
}
for (auto driver : next_result.value().drivers) {
if (!driver.has_libname()) {
continue;
}
std::string url(driver.libname().data(), driver.libname().size());
bool use_universe_resolver = false;
if (driver.has_package_type()) {
use_universe_resolver = ShouldUseUniversalResolver(driver.package_type());
}
const Driver* drv = LoadDriverUrl(url, use_universe_resolver);
if (drv) {
drivers.push_back(drv);
}
}
}
return drivers;
}