blob: 1a9a06f80a47c5049d11aaa9cb79a476abf9970d [file] [log] [blame]
// Copyright 2024 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/controller_allowlist_passthrough.h"
#include <zircon/assert.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "lib/fidl/cpp/wire/internal/transport.h"
#include "src/devices/lib/log/log.h"
#include "zircon/errors.h"
namespace {
const char *kAllowAllUses = "Allow_all_uses";
// Listed below are all the class names of _servers_ of the fuchsia.device/Controller protocol
// that are allowed for each function. There can be and often are multiple clients that use
// a function of the protocol for each entry. As a comment after each entry, please list
// at least one client or test that uses the function of the listed class name. This makes it
// easier to track what clients may be affected by removing an interface. See
// https://fxbug.dev/331409420 for more information about migrating off of the
// fuchsia.device/Controller protocol.
const std::unordered_map<std::string, std::unordered_set<std::string_view>> kControllerAllowlists({
{"ConnectToController",
{
"block", // zxcrypt-test
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/sample-driver.cm", // device_controller_fidl
"driver_runner_test", // driver-runner-test
}},
{"ConnectToDeviceFidl",
{
"block", // Allow qemu to boot
"driver_runner_test", // driver_runner_death_test
"nand", // Some internal bot
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/fvm.cm", // paver-test
"No_class_name_but_driver_url_is_fuchsia-boot:///fvm#meta/fvm.cm", // installer_test.sh
"No_class_name_but_driver_url_is_fuchsia-boot:///gpt#meta/gpt.cm", // storage-verity-benchmarks
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/nand-broker.cm", // nand-broker-test
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/sample-driver.cm",
}},
{"Bind",
{
"block", // allow vim3 to boot
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/test.cm", // bind-fail-test
"driver_runner_test", // driver-runner-test
"test", // power-manager-integration-test
}},
{"Rebind", {kAllowAllUses}},
{"UnbindChildren",
{
"block", // fshost_integration_tests_fxfs_no_fxblob.cm
"driver_runner_test", // driver-runner-test
}},
{"ScheduleUnbind",
{
"bt-emulator", // bt-host-integration-tests
"driver_runner_test", // driver-runner-test
"nand", // ram-nand-test
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/ddk-lifecycle-test.cm", // ddk-lifecycle-test
"No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/fvm.cm", // paver-test
"No_class_name_but_driver_url_is_fuchsia-boot:///fvm#meta/fvm.cm",
"No_class_name_but_driver_url_is_unbound", // blobfs-ramdisk-test, and so many others
}},
{"GetTopologicalPath", {kAllowAllUses}},
});
} // namespace
void ControllerAllowlistPassthrough::CheckAllowlist(const std::string &function_name) {
auto allowlist = kControllerAllowlists.find(function_name);
ZX_ASSERT_MSG(allowlist != kControllerAllowlists.end(), "Function %s was not on the allowlist",
function_name.c_str());
// If the kAllowAllUses string is present, just skip the check for that function.
if (allowlist->second.find(kAllowAllUses) != allowlist->second.end()) {
return;
}
ZX_ASSERT_MSG(allowlist->second.find(class_name_) != allowlist->second.end(),
"\nUndeclared DEVFS_USAGE detected: %s is using %s.\n"
"This error is thrown when a fuchsia.device/Controller client\n"
"attempts to access a device and function not registered at\n"
"src/devices/bin/driver_manager/controller_allowlist_passthrough.cc.\n"
"If you need to add to the allowlist, add a bug as a child under "
"https://fxbug.dev/331409420. To learn more, see: "
"sdk/fidl/fuchsia.device.fs/README.md\n",
class_name_.c_str(), function_name.c_str());
}
void ControllerAllowlistPassthrough::ConnectToDeviceFidl(
ConnectToDeviceFidlRequestView request, ConnectToDeviceFidlCompleter::Sync &completer) {
CheckAllowlist("ConnectToDeviceFidl");
if (compat_client_.is_valid()) {
fidl::OneWayStatus status = compat_client_->ConnectToDeviceFidl(std::move(request->server));
if (!status.ok()) {
LOGF(ERROR, "Failed to forward ConnectToDeviceFidl call for %s.", class_name_.c_str());
}
} else {
std::shared_ptr locked_node = node_.lock();
if (!locked_node) {
LOGF(ERROR, "Node was freed before it was used for %s.", class_name_.c_str());
return;
}
locked_node->ConnectToDeviceFidl(request, completer);
}
}
void ControllerAllowlistPassthrough::ConnectToController(
ConnectToControllerRequestView request, ConnectToControllerCompleter::Sync &completer) {
CheckAllowlist("ConnectToController");
dev_controller_bindings_.AddBinding(dispatcher_, std::move(request->server), this,
fidl::kIgnoreBindingClosure);
}
void ControllerAllowlistPassthrough::Bind(BindRequestView request, BindCompleter::Sync &completer) {
CheckAllowlist("Bind");
if (compat_client_.is_valid()) {
compat_client_->Bind(request->driver)
.ThenExactlyOnce([completer = completer.ToAsync()](auto &result) mutable {
if (!result.ok()) {
completer.ReplyError(result.status());
return;
}
completer.Reply(result.value());
});
} else {
std::shared_ptr locked_node = node_.lock();
if (!locked_node) {
LOGF(ERROR, "Node was freed before it was used for %s.", class_name_.c_str());
return;
}
locked_node->Bind(request, completer);
}
}
void ControllerAllowlistPassthrough::Rebind(RebindRequestView request,
RebindCompleter::Sync &completer) {
CheckAllowlist("Rebind");
if (compat_client_.is_valid()) {
compat_client_->Rebind(request->driver)
.ThenExactlyOnce(
[completer = completer.ToAsync()](
fidl::WireUnownedResult<fuchsia_device::Controller::Rebind> &result) mutable {
if (!result.ok()) {
completer.ReplyError(result.status());
return;
}
completer.Reply(result.value());
});
} else {
std::shared_ptr locked_node = node_.lock();
if (!locked_node) {
LOGF(ERROR, "Node was freed before it was used for %s.", class_name_.c_str());
return;
}
locked_node->Rebind(request, completer);
}
}
void ControllerAllowlistPassthrough::UnbindChildren(UnbindChildrenCompleter::Sync &completer) {
CheckAllowlist("UnbindChildren");
if (compat_client_.is_valid()) {
compat_client_->UnbindChildren().ThenExactlyOnce(
[completer = completer.ToAsync()](auto &result) mutable {
if (!result.ok()) {
completer.ReplyError(result.status());
return;
}
completer.Reply(result.value());
});
} else {
std::shared_ptr locked_node = node_.lock();
if (!locked_node) {
LOGF(ERROR, "Node was freed before it was used for %s.", class_name_.c_str());
return;
}
locked_node->UnbindChildren(completer);
}
}
void ControllerAllowlistPassthrough::ScheduleUnbind(ScheduleUnbindCompleter::Sync &completer) {
CheckAllowlist("ScheduleUnbind");
if (compat_client_.is_valid()) {
compat_client_->ScheduleUnbind().ThenExactlyOnce(
[completer = completer.ToAsync()](auto &result) mutable {
if (!result.ok()) {
completer.ReplyError(result.status());
return;
}
completer.Reply(result.value());
});
} else {
std::shared_ptr locked_node = node_.lock();
if (!locked_node) {
LOGF(ERROR, "Node was freed before it was used for %s.", class_name_.c_str());
return;
}
locked_node->ScheduleUnbind(completer);
}
}
void ControllerAllowlistPassthrough::GetTopologicalPath(
GetTopologicalPathCompleter::Sync &completer) {
CheckAllowlist("GetTopologicalPath");
if (compat_client_.is_valid()) {
compat_client_->GetTopologicalPath().ThenExactlyOnce(
[completer = completer.ToAsync()](auto &result) mutable {
if (!result.ok()) {
completer.ReplyError(result.status());
return;
}
completer.Reply(result.value());
});
} else {
std::shared_ptr locked_node = node_.lock();
if (!locked_node) {
LOGF(ERROR, "Node was freed before it was used for %s.", class_name_.c_str());
return;
}
locked_node->GetTopologicalPath(completer);
}
}
zx_status_t ControllerAllowlistPassthrough::Connect(
fidl::ServerEnd<fuchsia_device::Controller> server_end) {
dev_controller_bindings_.AddBinding(dispatcher_, std::move(server_end), this,
fidl::kIgnoreBindingClosure);
return ZX_OK;
}
std::unique_ptr<ControllerAllowlistPassthrough> ControllerAllowlistPassthrough::Create(
std::optional<fidl::ClientEnd<fuchsia_device_fs::Connector>> controller_connector,
std::weak_ptr<fidl::WireServer<fuchsia_device::Controller>> node,
async_dispatcher_t *dispatcher, const std::string &class_name) {
std::unique_ptr<ControllerAllowlistPassthrough> ret(
new ControllerAllowlistPassthrough(std::move(node), dispatcher, class_name));
if (!controller_connector.has_value()) {
return ret;
}
auto [client_end, server_end] = fidl::Endpoints<fuchsia_device::Controller>::Create();
zx_status_t status =
fidl::WireCall(controller_connector.value())->Connect(server_end.TakeChannel()).status();
if (status != ZX_OK) {
return ret;
}
ret->compat_client_.Bind(std::move(client_end), dispatcher);
return ret;
}