blob: dfdaeba95fb38c78b956c88e08b59905c962cc8e [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 "src/devices/bin/driver_manager/bind_driver_manager.h"
#include <errno.h>
#include <lib/fit/function.h>
#include <zircon/status.h>
#include "src/devices/bin/driver_manager/binding_internal.h"
#include "src/devices/bin/driver_manager/devfs.h"
#include "src/devices/lib/log/log.h"
BindDriverManager::BindDriverManager(Coordinator* coordinator, AttemptBindFunc attempt_bind)
: coordinator_(coordinator), attempt_bind_(std::move(attempt_bind)) {}
BindDriverManager::~BindDriverManager() {}
zx_status_t BindDriverManager::BindDriverToDevice(const MatchedDriver& driver,
const fbl::RefPtr<Device>& dev) {
if (std::holds_alternative<MatchedCompositeDriverInfo>(driver)) {
return BindDriverToFragment(std::get<MatchedCompositeDriverInfo>(driver), dev);
}
if (std::holds_alternative<MatchedDeviceGroupInfo>(driver)) {
return BindDriverToDeviceGroup(std::get<MatchedDeviceGroupInfo>(driver), dev);
}
if (!std::holds_alternative<MatchedDriverInfo>(driver)) {
return ZX_ERR_INTERNAL;
}
auto driver_info = std::get<MatchedDriverInfo>(driver);
zx_status_t status = attempt_bind_(driver_info.driver, dev);
// If we get this here it means we've successfully bound one driver
// and the device isn't multi-bind.
if (status == ZX_ERR_ALREADY_BOUND) {
return ZX_OK;
}
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind driver '%.*s' to device '%.*s': %s",
static_cast<uint32_t>(driver_info.driver->libname.size()),
driver_info.driver->libname.data(), static_cast<uint32_t>(dev->name().size()),
dev->name().data(), zx_status_get_string(status));
}
return status;
}
zx_status_t BindDriverManager::BindDevice(const fbl::RefPtr<Device>& dev,
std::string_view drvlibname, bool new_device) {
// shouldn't be possible to get a bind request for a proxy device
if (dev->flags & DEV_CTX_PROXY) {
return ZX_ERR_NOT_SUPPORTED;
}
// A libname of "" means a general rebind request instead of a specific request.
bool autobind = drvlibname.size() == 0;
if (autobind && (dev->flags & DEV_CTX_SKIP_AUTOBIND)) {
return ZX_OK;
}
// Attempt composite device matching first. This is unnecessary if a
// specific driver has been requested.
if (autobind) {
for (auto& composite : coordinator_->device_manager()->composite_devices()) {
auto status = composite.TryMatchBindFragments(dev);
if (status != ZX_OK) {
return status;
}
}
}
// TODO: disallow if we're in the middle of enumeration, etc
zx::status<std::vector<MatchedDriver>> result = GetMatchingDrivers(dev, drvlibname);
if (!result.is_ok()) {
return result.error_value();
}
auto drivers = std::move(result.value());
for (auto& driver : drivers) {
zx_status_t status = BindDriverToDevice(driver, dev);
if (status != ZX_OK) {
return status;
}
}
// Notify observers that this device is available again
// Needed for non-auto-binding drivers like GPT against block, etc
if (!new_device && autobind) {
devfs_advertise_modified(dev);
}
return ZX_OK;
}
zx_status_t BindDriverManager::MatchDevice(const fbl::RefPtr<Device>& dev, const Driver* driver,
bool autobind) const {
if (dev->IsAlreadyBound()) {
return ZX_ERR_ALREADY_BOUND;
}
if (autobind && dev->should_skip_autobind()) {
return ZX_ERR_NEXT;
}
if (!dev->is_bindable() && !(dev->is_composite_bindable())) {
return ZX_ERR_NEXT;
}
if (!can_driver_bind(driver, dev->protocol_id(), dev->props(), dev->str_props(), autobind)) {
return ZX_ERR_NEXT;
}
return ZX_OK;
}
zx_status_t BindDriverManager::MatchAndBind(const fbl::RefPtr<Device>& dev, const Driver* drv,
bool autobind) {
zx_status_t status = MatchDevice(dev, drv, autobind);
if (status != ZX_OK) {
return status;
}
return BindDriverToDevice(MatchedDriverInfo{.driver = drv}, dev);
}
zx::status<std::vector<MatchedDriver>> BindDriverManager::GetMatchingDrivers(
const fbl::RefPtr<Device>& dev, std::string_view drvlibname) {
// It shouldn't be possible to get a bind request for a proxy device.
if (dev->flags & DEV_CTX_PROXY) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (dev->IsAlreadyBound()) {
return zx::error(ZX_ERR_ALREADY_BOUND);
}
std::vector<MatchedDriver> matched_drivers;
// A libname of "" means a general rebind request
// instead of a specific request
bool autobind = drvlibname.size() == 0;
// Check for drivers outside of the Driver-index.
for (const Driver& driver : coordinator_->drivers()) {
if (!autobind && drvlibname.compare(driver.libname)) {
continue;
}
zx_status_t status = MatchDevice(dev, &driver, autobind);
if (status == ZX_ERR_ALREADY_BOUND) {
return zx::error(ZX_ERR_ALREADY_BOUND);
}
if (status == ZX_ERR_NEXT) {
continue;
}
if (status == ZX_OK) {
auto matched = MatchedDriverInfo{.driver = &driver};
matched_drivers.push_back(std::move(matched));
}
// If the device doesn't support multibind (this is a devmgr-internal setting),
// then return on first match or failure.
// Otherwise, keep checking all the drivers.
if (!(dev->flags & DEV_CTX_MULTI_BIND)) {
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(matched_drivers));
}
}
// Check for drivers in the Driver-index.
{
DriverLoader::MatchDeviceConfig config;
config.libname = drvlibname;
auto drivers = coordinator_->driver_loader().MatchDeviceDriverIndex(dev, config);
for (auto driver : drivers) {
matched_drivers.push_back(driver);
}
}
return zx::ok(std::move(matched_drivers));
}
zx::status<std::vector<MatchedDriver>> BindDriverManager::MatchDeviceWithDriverIndex(
const fbl::RefPtr<Device>& dev, const DriverLoader::MatchDeviceConfig& config) const {
if (dev->IsAlreadyBound()) {
return zx::error(ZX_ERR_ALREADY_BOUND);
}
if (dev->should_skip_autobind()) {
return zx::error(ZX_ERR_NEXT);
}
if (!dev->is_bindable() && !(dev->is_composite_bindable())) {
return zx::error(ZX_ERR_NEXT);
}
return zx::ok(coordinator_->driver_loader().MatchDeviceDriverIndex(dev, config));
}
zx_status_t BindDriverManager::MatchAndBindWithDriverIndex(
const fbl::RefPtr<Device>& dev, const DriverLoader::MatchDeviceConfig& config) {
auto result = MatchDeviceWithDriverIndex(dev, config);
if (!result.is_ok()) {
return result.error_value();
}
auto matched_drivers = std::move(result.value());
for (auto driver : matched_drivers) {
zx_status_t status = BindDriverToDevice(driver, dev);
// If we get this here it means we've successfully bound one driver
// and the device isn't multi-bind.
if (status == ZX_ERR_ALREADY_BOUND) {
return ZX_OK;
}
}
return ZX_OK;
}
void BindDriverManager::BindAllDevicesDriverIndex(const DriverLoader::MatchDeviceConfig& config) {
// This call is not strictly necessary -- we do not bind anything to the root device.
// However, it guarantees that we connect to the driver index and wait for it to start.
// Some tests become flaky if we don't do this here.
zx_status_t status = MatchAndBindWithDriverIndex(coordinator_->root_device(), config);
if (status != ZX_OK && status != ZX_ERR_NEXT) {
LOGF(ERROR, "DriverIndex failed to match root_device: %d", status);
return;
}
for (auto& dev : coordinator_->device_manager()->devices()) {
auto dev_ref = fbl::RefPtr(&dev);
zx_status_t status = MatchAndBindWithDriverIndex(dev_ref, config);
if (status == ZX_ERR_NEXT || status == ZX_ERR_ALREADY_BOUND) {
continue;
}
if (status != ZX_OK) {
return;
}
}
}
zx_status_t BindDriverManager::AddDeviceGroup(
std::string_view topological_path, std::string_view group_name,
fuchsia_device_manager::wire::DeviceGroupDescriptor group_desc) {
auto path = std::string(topological_path);
if (device_groups_.count(path) != 0) {
LOGF(ERROR, "Duplicate device groups already exists: %s", path.c_str());
return ZX_ERR_INVALID_ARGS;
}
auto result = CompositeDevice::CreateForDeviceGroup(group_name, std::move(group_desc));
if (!result.is_ok()) {
LOGF(ERROR, "Failed to create CompositeDevice for device group: %s",
zx_status_get_string(result.error_value()));
return result.error_value();
}
device_groups_[path] = std::move(result.value());
for (auto& dev : coordinator_->device_manager()->devices()) {
MatchAndBindDeviceGroup(fbl::RefPtr(&dev), topological_path);
}
return ZX_OK;
}
zx_status_t BindDriverManager::MatchAndBindDeviceGroup(const fbl::RefPtr<Device>& dev,
std::string_view topological_path) {
DriverLoader::MatchDeviceConfig config;
auto result = MatchDeviceWithDriverIndex(dev, config);
if (!result.is_ok()) {
return result.error_value();
}
auto matched_drivers = std::move(result.value());
for (auto driver : matched_drivers) {
if (!std::holds_alternative<MatchedDeviceGroupInfo>(driver)) {
continue;
}
auto device_group = std::get<MatchedDeviceGroupInfo>(driver);
if (device_group.topological_path.compare(std::string(topological_path)) != 0) {
continue;
}
zx_status_t status = BindDriverToDeviceGroup(device_group, dev);
// If we get this here it means we've successfully bound one driver
// and the device isn't multi-bind.
if (status == ZX_ERR_ALREADY_BOUND) {
return ZX_OK;
}
}
return ZX_OK;
}
zx_status_t BindDriverManager::BindDriverToFragment(const MatchedCompositeDriverInfo& driver,
const fbl::RefPtr<Device>& dev) {
// Check if the driver already exists in |driver_index_composite_devices_|. If
// it doesn't, create and add a new CompositeDevice.
std::string name(driver.driver_info.driver->libname.c_str());
if (driver_index_composite_devices_.count(name) == 0) {
std::unique_ptr<CompositeDevice> composite_dev;
zx_status_t status = CompositeDevice::CreateFromDriverIndex(driver, &composite_dev);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to create CompositeDevice from DriverIndex: %s",
zx_status_get_string(status));
return status;
}
driver_index_composite_devices_[name] = std::move(composite_dev);
}
// Bind the matched fragment to the device.
auto& composite = driver_index_composite_devices_[name];
zx_status_t status = composite->BindFragment(driver.composite.node, dev);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to BindFragment for '%.*s': %s", static_cast<uint32_t>(dev->name().size()),
dev->name().data(), zx_status_get_string(status));
}
return status;
}
zx_status_t BindDriverManager::BindDriverToDeviceGroup(const MatchedDeviceGroupInfo& device_group,
const fbl::RefPtr<Device>& dev) {
auto path = std::string(device_group.topological_path);
if (device_groups_.count(path) == 0) {
return ZX_ERR_NOT_FOUND;
}
// Bind the matched fragment to the device.
auto& composite = device_groups_[path];
auto status = composite->BindFragment(device_group.node_index, dev);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to BindFragment for '%.*s': %s", static_cast<uint32_t>(dev->name().size()),
dev->name().data(), zx_status_get_string(status));
}
return status;
}