blob: 07b1ce9da66d27199be574cebd763beacfbb73b5 [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 "device_port.h"
#include <fidl/fuchsia.hardware.network.driver/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include "device_interface.h"
#include "log.h"
namespace network::internal {
void DevicePort::Create(
DeviceInterface* parent, async_dispatcher_t* dispatcher, netdev::wire::PortId id,
fdf::WireSharedClient<fuchsia_hardware_network_driver::NetworkPort>&& port_client,
fdf_dispatcher_t* mac_dispatcher, TeardownCallback&& on_teardown, OnCreated&& on_created) {
if (parent == nullptr) {
LOGF_ERROR("null parent provided");
on_created(zx::error(ZX_ERR_INVALID_ARGS));
return;
}
fbl::AllocChecker ac;
std::unique_ptr<DevicePort> port(
new (&ac) DevicePort(parent, dispatcher, id, std::move(port_client), std::move(on_teardown)));
if (!ac.check()) {
LOGF_ERROR("Failed to allocate memory for port");
on_created(zx::error(ZX_ERR_NO_MEMORY));
return;
}
// Keep a raw pointer for making the call below, the unique ptr will have been moved.
DevicePort* port_ptr = port.get();
port_ptr->Init(mac_dispatcher, [on_created = std::move(on_created),
port = std::move(port)](zx_status_t status) mutable {
if (status != ZX_OK) {
// Reset the port client to ensure that the DevicePort object doesn't try to do anything with
// it on destruction.
port->port_ = fdf::WireSharedClient<netdriver::NetworkPort>();
on_created(zx::error(status));
return;
}
on_created(zx::ok(std::move(port)));
});
}
DevicePort::DevicePort(DeviceInterface* parent, async_dispatcher_t* dispatcher,
netdev::wire::PortId id,
fdf::WireSharedClient<fuchsia_hardware_network_driver::NetworkPort>&& port,
TeardownCallback&& on_teardown)
: parent_(parent),
dispatcher_(dispatcher),
id_(id),
port_(std::move(port)),
on_teardown_(std::move(on_teardown)) {}
DevicePort::~DevicePort() {
fdf::Arena arena('NETD');
if (port_.is_valid()) {
fidl::OneWayStatus status = port_.buffer(arena)->Removed();
if (!status.ok()) {
LOGF_ERROR("Failed to remove port: %s", status.FormatDescription().c_str());
}
}
}
void DevicePort::Init(fdf_dispatcher_t* mac_dispatcher,
fit::callback<void(zx_status_t)>&& on_complete) {
GetMac([this, mac_dispatcher, on_complete = std::move(on_complete)](
zx::result<::fdf::ClientEnd<netdriver::MacAddr>> result) mutable {
if (result.is_error()) {
on_complete(result.status_value());
return;
}
// Pre-create the callback here because it needs to be shared between two separate paths below
// and the on_complete callback can only be captured in one place.
fit::callback<void(zx_status_t)> get_port_info = [this, on_complete = std::move(on_complete)](
zx_status_t status) mutable {
if (status != ZX_OK) {
on_complete(status);
return;
}
GetInitialPortInfo([this, on_complete = std::move(on_complete)](zx_status_t status) mutable {
if (status != ZX_OK) {
on_complete(status);
return;
}
GetInitialStatus([on_complete = std::move(on_complete)](zx_status_t status) mutable {
on_complete(status);
});
});
};
if (result.value().is_valid()) {
CreateMacInterface(std::move(result.value()), mac_dispatcher, get_port_info.share());
return;
}
get_port_info(ZX_OK);
});
}
void DevicePort::GetMac(
fit::callback<void(zx::result<::fdf::ClientEnd<netdriver::MacAddr>>)>&& on_complete) {
fdf::Arena arena('PORT');
port_.buffer(arena)->GetMac().Then(
[on_complete = std::move(on_complete)](
fdf::WireUnownedResult<netdriver::NetworkPort::GetMac>& result) mutable {
if (!result.ok()) {
LOGF_ERROR("Failed to get Mac interface: %s", result.FormatDescription().c_str());
on_complete(zx::error(result.status()));
return;
}
on_complete(zx::ok(std::move(result->mac_ifc)));
});
}
void DevicePort::CreateMacInterface(::fdf::ClientEnd<netdriver::MacAddr>&& client_end,
fdf_dispatcher_t* mac_dispatcher,
fit::callback<void(zx_status_t)>&& on_complete) {
fdf::WireSharedClient mac_client(std::move(client_end), mac_dispatcher);
MacAddrDeviceInterface::Create(
std::move(mac_client),
[this, on_complete = std::move(on_complete)](
zx::result<std::unique_ptr<MacAddrDeviceInterface>> result) mutable {
if (result.is_error()) {
on_complete(result.status_value());
return;
}
fbl::AutoLock lock(&lock_);
mac_ = std::move(result.value());
on_complete(ZX_OK);
});
}
void DevicePort::GetInitialPortInfo(fit::callback<void(zx_status_t)>&& on_complete) {
fdf::Arena arena('PORT');
port_.buffer(arena)->GetInfo().Then(
[this, on_complete = std::move(on_complete)](
::fdf::WireUnownedResult<netdriver::NetworkPort::GetInfo>& result) mutable {
if (!result.ok()) {
LOGF_ERROR("Failed to get initial port info: %s", result.FormatDescription().c_str());
on_complete(result.status());
return;
}
if (!result->info.has_port_class()) {
LOGF_ERROR("missing port class");
on_complete(ZX_ERR_INVALID_ARGS);
return;
}
if (result->info.rx_types().count() > netdev::wire::kMaxFrameTypes) {
LOGF_ERROR("too many port rx types: %ld > %d", result->info.rx_types().count(),
netdev::wire::kMaxFrameTypes);
on_complete(ZX_ERR_INVALID_ARGS);
return;
}
if (result->info.tx_types().count() > netdev::wire::kMaxFrameTypes) {
LOGF_ERROR("too many port tx types: %ld > %d", result->info.tx_types().count(),
netdev::wire::kMaxFrameTypes);
on_complete(ZX_ERR_INVALID_ARGS);
return;
}
port_class_ = result->info.port_class();
ZX_ASSERT(supported_rx_.empty());
if (result->info.has_rx_types()) {
std::copy(result->info.rx_types().begin(), result->info.rx_types().end(),
std::back_inserter(supported_rx_));
}
ZX_ASSERT(supported_tx_.empty());
if (result->info.has_tx_types()) {
std::copy(result->info.tx_types().begin(), result->info.tx_types().end(),
std::back_inserter(supported_tx_));
}
on_complete(ZX_OK);
});
}
void DevicePort::GetInitialStatus(fit::callback<void(zx_status_t)>&& on_complete) {
fdf::Arena arena('PORT');
port_.buffer(arena)->GetStatus().Then(
[this, on_complete = std::move(on_complete)](
::fdf::WireUnownedResult<netdriver::NetworkPort::GetStatus>& result) mutable {
if (!result.ok()) {
LOGF_ERROR("Failed to get initial port status: %s", result.FormatDescription().c_str());
on_complete(result.status());
return;
}
status_ = fidl::ToNatural(result->status);
on_complete(ZX_OK);
});
}
void DevicePort::StatusChanged(const netdev::wire::PortStatus& new_status) {
fbl::AutoLock lock(&lock_);
status_ = fidl::ToNatural(new_status);
for (auto& w : watchers_) {
w.PushStatus(new_status);
}
}
void DevicePort::GetStatusWatcher(GetStatusWatcherRequestView request,
GetStatusWatcherCompleter::Sync& _completer) {
fbl::AutoLock lock(&lock_);
if (teardown_started_) {
// Don't install new watchers after teardown has started.
return;
}
fbl::AllocChecker ac;
auto n_watcher = fbl::make_unique_checked<StatusWatcher>(&ac, request->buffer);
if (!ac.check()) {
return;
}
zx_status_t status =
n_watcher->Bind(dispatcher_, std::move(request->watcher), [this](StatusWatcher* watcher) {
fbl::AutoLock lock(&lock_);
watchers_.erase(*watcher);
MaybeFinishTeardown();
});
if (status != ZX_OK) {
LOGF_ERROR("failed to bind watcher: %s", zx_status_get_string(status));
return;
}
fdf::Arena arena('NETD');
n_watcher->PushStatus(fidl::ToWire(arena, status_));
watchers_.push_back(std::move(n_watcher));
}
bool DevicePort::MaybeFinishTeardown() {
if (teardown_started_ && on_teardown_ && watchers_.is_empty() && !mac_ && bindings_.is_empty()) {
// Always finish teardown on dispatcher to evade deadlock opportunity on DeviceInterface ports
// lock.
async::PostTask(dispatcher_, [this, call = std::move(on_teardown_)]() mutable { call(*this); });
return true;
}
return false;
}
void DevicePort::Teardown() {
fbl::AutoLock lock(&lock_);
if (teardown_started_) {
return;
}
teardown_started_ = true;
// Attempt to conclude the teardown immediately if we have no live resources.
if (MaybeFinishTeardown()) {
return;
}
for (auto& watcher : watchers_) {
watcher.Unbind();
}
for (auto& binding : bindings_) {
binding.Unbind();
}
if (mac_) {
mac_->Teardown([this]() {
// Always dispatch mac teardown callback to our dispatcher.
async::PostTask(dispatcher_, [this]() {
fbl::AutoLock lock(&lock_);
// Dispose of mac entirely on teardown complete.
mac_ = nullptr;
MaybeFinishTeardown();
});
});
}
}
void DevicePort::GetMac(GetMacRequestView request, GetMacCompleter::Sync& _completer) {
fidl::ServerEnd req = std::move(request->mac);
fbl::AutoLock lock(&lock_);
if (teardown_started_) {
return;
}
if (!mac_) {
req.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
zx_status_t status = mac_->Bind(dispatcher_, std::move(req));
if (status != ZX_OK) {
LOGF_ERROR("failed to bind to MacAddr on port %d: %s", id_.base, zx_status_get_string(status));
}
}
void DevicePort::SessionAttached() {
fbl::AutoLock lock(&lock_);
NotifySessionCount(++attached_sessions_count_);
}
void DevicePort::SessionDetached() {
fbl::AutoLock lock(&lock_);
ZX_ASSERT_MSG(attached_sessions_count_ > 0, "detached the same port twice");
NotifySessionCount(--attached_sessions_count_);
}
void DevicePort::NotifySessionCount(size_t new_count) {
if (teardown_started_) {
// Skip all notifications if tearing down.
return;
}
// Port active changes whenever the new count on session attaching or detaching edges away from
// zero.
if (new_count <= 1) {
// Always post notifications for later on dispatcher so the port implementation can safely call
// back into the core device with no risk of deadlocks.
async::PostTask(dispatcher_, [this, active = new_count != 0]() {
fdf::Arena arena('NETD');
fidl::OneWayStatus result = port_.buffer(arena)->SetActive(active);
if (!result.ok()) {
LOGF_ERROR("SetActive failed with error: %s", result.FormatDescription().c_str());
}
});
}
}
bool DevicePort::IsValidRxFrameType(netdev::wire::FrameType frame_type) const {
cpp20::span rx_types(supported_rx_.begin(), supported_rx_.end());
return std::any_of(rx_types.begin(), rx_types.end(),
[frame_type](const netdev::wire::FrameType& t) { return t == frame_type; });
}
bool DevicePort::IsValidTxFrameType(netdev::wire::FrameType frame_type) const {
cpp20::span tx_types(supported_tx_.begin(), supported_tx_.end());
return std::any_of(
tx_types.begin(), tx_types.end(),
[frame_type](const netdev::wire::FrameTypeSupport& t) { return t.type == frame_type; });
}
void DevicePort::Bind(fidl::ServerEnd<netdev::Port> req) {
fbl::AllocChecker ac;
std::unique_ptr<Binding> binding(new (&ac) Binding);
if (!ac.check()) {
req.Close(ZX_ERR_NO_MEMORY);
return;
}
fbl::AutoLock lock(&lock_);
// Disallow binding a new request if teardown already started to prevent races
// with the dispatched unbind below.
if (teardown_started_) {
return;
}
// Capture a pointer to the binding so we can erase it in the unbound function.
Binding* binding_ptr = binding.get();
binding->Bind(fidl::BindServer(dispatcher_, std::move(req), this,
[binding_ptr](DevicePort* port, fidl::UnbindInfo /*unused*/,
fidl::ServerEnd<netdev::Port> /*unused*/) {
// Always complete unbind later to avoid deadlock in case bind
// fails synchronously.
async::PostTask(port->dispatcher_, [port, binding_ptr]() {
fbl::AutoLock lock(&port->lock_);
port->bindings_.erase(*binding_ptr);
port->MaybeFinishTeardown();
});
}));
bindings_.push_front(std::move(binding));
}
void DevicePort::GetInfo(GetInfoCompleter::Sync& completer) {
fidl::WireTableFrame<netdev::wire::PortInfo> frame;
netdev::wire::PortInfo port_info(
fidl::ObjectView<fidl::WireTableFrame<netdev::wire::PortInfo>>::FromExternal(&frame));
auto tx_support = fidl::VectorView<netdev::wire::FrameTypeSupport>::FromExternal(supported_tx_);
auto rx_support = fidl::VectorView<netdev::wire::FrameType>::FromExternal(supported_rx_);
fidl::WireTableFrame<netdev::wire::PortBaseInfo> base_info_frame;
netdev::wire::PortBaseInfo port_base_info(
fidl::ObjectView<fidl::WireTableFrame<netdev::wire::PortBaseInfo>>::FromExternal(
&base_info_frame));
port_base_info.set_port_class(port_class_)
.set_tx_types(fidl::ObjectView<decltype(tx_support)>::FromExternal(&tx_support))
.set_rx_types(fidl::ObjectView<decltype(rx_support)>::FromExternal(&rx_support));
port_info.set_id(id_).set_base_info(
fidl::ObjectView<netdev::wire::PortBaseInfo>::FromExternal(&port_base_info));
completer.Reply(port_info);
}
void DevicePort::GetStatus(GetStatusCompleter::Sync& completer) {
fdf::Arena arena('NETD');
port_.buffer(arena)->GetStatus().Then(
[completer = completer.ToAsync()](
fdf::WireUnownedResult<fuchsia_hardware_network_driver::NetworkPort::GetStatus>&
result) mutable {
if (!result.ok()) {
LOGF_ERROR("GetStatus() failed: %s", result.FormatDescription().c_str());
completer.Close(result.status());
return;
}
completer.Reply(result->status);
});
}
void DevicePort::GetDevice(GetDeviceRequestView request, GetDeviceCompleter::Sync& _completer) {
if (zx_status_t status = parent_->Bind(std::move(request->device)); status != ZX_OK) {
LOGF_ERROR("bind failed %s", zx_status_get_string(status));
}
}
void DevicePort::Clone(CloneRequestView request, CloneCompleter::Sync& _completer) {
Bind(std::move(request->port));
}
void DevicePort::GetCounters(GetCountersCompleter::Sync& completer) {
fidl::WireTableFrame<netdev::wire::PortGetCountersResponse> frame;
netdev::wire::PortGetCountersResponse rsp(
fidl::ObjectView<fidl::WireTableFrame<netdev::wire::PortGetCountersResponse>>::FromExternal(
&frame));
uint64_t tx_frames = counters_.tx_frames;
rsp.set_tx_frames(fidl::ObjectView<uint64_t>::FromExternal(&tx_frames));
uint64_t tx_bytes = counters_.tx_bytes;
rsp.set_tx_bytes(fidl::ObjectView<uint64_t>::FromExternal(&tx_bytes));
uint64_t rx_frames = counters_.rx_frames;
rsp.set_rx_frames(fidl::ObjectView<uint64_t>::FromExternal(&rx_frames));
uint64_t rx_bytes = counters_.rx_bytes;
rsp.set_rx_bytes(fidl::ObjectView<uint64_t>::FromExternal(&rx_bytes));
completer.Reply(rsp);
}
void DevicePort::GetDiagnostics(GetDiagnosticsRequestView request,
GetDiagnosticsCompleter::Sync& _completer) {
parent_->diagnostics().Bind(std::move(request->diagnostics));
}
} // namespace network::internal