blob: 3d83d8f8a91c2568510c1fd98c31c3a5a11fa26d [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/connectivity/wlan/drivers/lib/components/cpp/include/wlan/drivers/components/network_device.h"
#include <fidl/fuchsia.hardware.network.driver/cpp/fidl.h>
#include <fidl/fuchsia.hardware.network/cpp/wire.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/fidl/cpp/wire_natural_conversions.h>
#include <lib/zx/vmar.h>
#include "src/connectivity/wlan/drivers/lib/components/cpp/log.h"
namespace netdriver = fuchsia_hardware_network_driver;
namespace {
void PopulateRxBuffer(netdriver::wire::RxBuffer& buffer, netdriver::wire::RxBufferPart& buffer_part,
const wlan::drivers::components::Frame& frame) {
buffer.data = ::fidl::VectorView<::netdriver::wire::RxBufferPart>::FromExternal(&buffer_part, 1);
buffer.meta.port = frame.PortId();
buffer.meta.frame_type = ::fuchsia_hardware_network::wire::FrameType::kEthernet;
buffer_part.id = frame.BufferId();
buffer_part.length = frame.Size();
buffer_part.offset = frame.Headroom();
}
} // anonymous namespace
namespace wlan::drivers::components {
NetworkDevice::Callbacks::~Callbacks() = default;
NetworkDevice::NetworkDevice(Callbacks* callbacks) : callbacks_(callbacks) {}
NetworkDevice::~NetworkDevice() {
ZX_ASSERT_MSG(!binding_.has_value(),
"NetworkDevice is bound. Call Remove and wait for NetDevRelease first.");
ZX_ASSERT_MSG(outgoing_directory_ == nullptr,
"NetworkDevice service registered. Call Remove and wait for NetDevRelease first.");
ZX_ASSERT_MSG(!node_controller_.is_valid(),
"NetworkDevice node valid. Call Remove and wait for NetDevRelease first.");
ZX_ASSERT_MSG(state_ == State::UnInitialized || state_ == State::Released,
"NetworkDevice not released yet. Call Remove and wait for NetDevRelease first.");
}
zx_status_t NetworkDevice::Initialize(fidl::WireClient<fuchsia_driver_framework::Node>& parent,
fdf_dispatcher_t* dispatcher,
fdf::OutgoingDirectory& outgoing, const char* device_name) {
std::lock_guard lock(mutex_);
if (state_ != State::UnInitialized) {
LOGF(ERROR, "NetworkDevice already initialized");
return ZX_ERR_ALREADY_BOUND;
}
state_ = State::Initialized;
netdev_dispatcher_ = dispatcher;
if (zx_status_t status = AddService(outgoing); status != ZX_OK) {
// AddService should have logged an error message already.
return status;
}
fidl::Arena arena;
auto offer = fdf::MakeOffer2<netdriver::Service>(arena, component::kDefaultInstance);
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(device_name)
.offers2(arena, std::vector{offer})
.Build();
auto [client, server] = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
auto result = parent.sync()->AddChild(args, std::move(server), {});
if (!result.ok()) {
LOGF(ERROR, "Failed to make add child call: %s", result.FormatDescription().c_str());
return result.status();
}
if (result->is_error()) {
LOGF(ERROR, "Failed to add child: %u", static_cast<uint32_t>(result->error_value()));
return ZX_ERR_INTERNAL;
}
node_controller_.Bind(std::move(client), fdf::Dispatcher::GetCurrent()->async_dispatcher(), this);
return ZX_OK;
}
bool NetworkDevice::Remove() {
std::lock_guard lock(mutex_);
if (state_ == State::UnInitialized || state_ == State::Released) {
// Never initialized or already removed. No need for a removal.
return false;
}
if (state_ != State::Initialized) {
// Removal is already in progress. Completion will be signaled through NetDevRelease.
return true;
}
RemoveLocked();
return true;
}
void NetworkDevice::RemoveLocked() {
// Attempt to remove the network device child node first. This is to allow the network device
// driver to complete shutdown properly which involves calling into our NetworkDeviceImpl server.
// Keep the server alive until after the node is gone.
if (node_controller_.is_valid()) {
// Remove the child node first as mentioned above. The next step in the removal process will be
// triggered by the on_fidl_error handler which is called when the node removal is complete.
TransitionToState(State::RemovingNode, [this] { RemoveNode(); });
} else if (outgoing_directory_) {
// Remove the service. The next step will be triggered by the RemoveService call itself as
// service removal is a synchronous process.
TransitionToState(State::RemovingService, [this] { RemoveService(); });
} else if (binding_.has_value()) {
// The service has been removed, it's now safe to unbind the server. The remaining removal will
// be triggered by the unbind handler.
TransitionToState(State::Unbinding, [this] { Unbind(); });
} else {
// Server has been unbound, call release. This will trigger the transition to the final state,
// Released, and call NetDevRelease on the callbacks.
TransitionToState(State::Releasing, [this] { Release(); });
}
}
void NetworkDevice::TransitionToState(State new_state, fit::function<void()>&& task) {
if (state_ >= new_state) {
// Don't perform the task if the desired or a subsequent state has already been reached.
return;
}
state_ = new_state;
async::PostTask(parent_dispatcher_, [task = std::move(task)] { task(); });
}
void NetworkDevice::WaitUntilServerConnected() { server_connected_.Wait(); }
fdf::WireSharedClient<netdriver::NetworkDeviceIfc>& NetworkDevice::NetDevIfcClient() {
return netdev_ifc_;
}
void NetworkDevice::CompleteRx(Frame&& frame) {
if (!ShouldCompleteFrame(frame)) {
return;
}
// Use a FIDL arena here as it can allocate storage on the stack. This avoid any memory
// allocations that would happen with an fdf::Arena. The default FIDL arena size should be plenty.
fidl::Arena arena;
// Still need an fdf::Arena for the actual call.
fdf::Arena fdf_arena('NETD');
fidl::VectorView<netdriver::wire::RxBuffer> buffers(arena, 1u);
fidl::VectorView<netdriver::wire::RxBufferPart> buffer_part(arena, 1u);
PopulateRxBuffer(buffers[0], buffer_part[0], frame);
frame.ReleaseFromStorage();
auto status = netdev_ifc_.buffer(fdf_arena)->CompleteRx(buffers);
if (!status.ok()) {
LOGF(ERROR, "Failed to complete RX: %s", status.FormatDescription().c_str());
return;
}
}
void NetworkDevice::CompleteRx(FrameContainer&& frames) {
// Use a FIDL arena here as it can allocate storage on the stack. This avoid any memory
// allocations that would happen with an fdf::Arena. Figure out the maximum possible arena size
// needed and use that.
constexpr size_t kRxBuffersPerBatch = netdriver::wire::kMaxRxBuffers;
constexpr size_t kArenaSize =
fidl::MaxSizeInChannel<netdriver::wire::NetworkDeviceIfcCompleteRxRequest,
fidl::MessageDirection::kSending>();
fidl::Arena<kArenaSize> arena;
// Still need an fdf::Arena for the actual call.
fdf::Arena fdf_arena('NETD');
const size_t buffers_to_fill = std::min(kRxBuffersPerBatch, frames.size());
fidl::VectorView<netdriver::wire::RxBuffer> buffers(arena, buffers_to_fill);
fidl::VectorView<netdriver::wire::RxBufferPart> buffer_parts(arena, buffers_to_fill);
for (auto frame = frames.begin(); frame != frames.end();) {
size_t count = 0;
for (; count < kRxBuffersPerBatch && frame != frames.end(); ++frame) {
if (!ShouldCompleteFrame(*frame)) [[unlikely]] {
continue;
}
PopulateRxBuffer(buffers[count], buffer_parts[count], *frame);
frame->ReleaseFromStorage();
++count;
}
if (count > 0) [[likely]] {
// The number of buffers might have changed as some frames should not be completed.
buffers.set_size(count);
auto status = netdev_ifc_.buffer(fdf_arena)->CompleteRx(buffers);
if (!status.ok()) {
LOGF(ERROR, "Failed to complete RX: %s", status.FormatDescription().c_str());
return;
}
}
}
}
void NetworkDevice::CompleteTx(cpp20::span<Frame> frames, zx_status_t status) {
// Use a FIDL arena here as it can allocate storage on the stack. This avoid any memory
// allocations that would happen with an fdf::Arena. Figure out the maximum possible arena size
// needed and use that.
constexpr size_t kTxBuffersPerBatch = netdriver::wire::kMaxTxBuffers;
constexpr size_t kArenaSize =
fidl::MaxSizeInChannel<netdriver::wire::NetworkDeviceIfcCompleteTxRequest,
fidl::MessageDirection::kSending>();
fidl::Arena<kArenaSize> arena;
// Still need an fdf::Arena for the actual call.
fdf::Arena fdf_arena('NETD');
fidl::VectorView<netdriver::wire::TxResult> results(arena, kTxBuffersPerBatch);
const size_t results_to_fill = std::min(results.size(), frames.size());
for (auto frame = frames.begin(); frame != frames.end();) {
size_t count = 0;
for (; count < results_to_fill && frame != frames.end(); ++frame) {
if (vmo_addrs_[frame->VmoId()] == nullptr) [[unlikely]] {
// Do not complete TX for frames that don't belong to the network device. These could be
// remnants from after a stop that should not be completed or frames used internally by
// the driver that made their way to this method.
continue;
}
results[count].id = frame->BufferId();
results[count].status = status;
++count;
}
if (count > 0) [[unlikely]] {
results.set_size(count);
auto status = netdev_ifc_.buffer(fdf_arena)->CompleteTx(results);
if (!status.ok()) {
LOGF(ERROR, "Failed to complete TX: %s", status.FormatDescription().c_str());
return;
}
}
}
}
// NetworkDeviceImpl implementation
void NetworkDevice::Init(netdriver::wire::NetworkDeviceImplInitRequest* request, fdf::Arena& arena,
InitCompleter::Sync& completer) {
{
std::lock_guard lock(mutex_);
netdev_ifc_.Bind(std::move(request->iface), netdev_dispatcher_);
}
callbacks_->NetDevInit(Callbacks::InitTxn(completer));
}
void NetworkDevice::Start(fdf::Arena& arena, StartCompleter::Sync& completer) {
bool expected = false;
if (!started_.compare_exchange_strong(expected, true)) {
LOGF(ERROR, "NetworkDevice already started, unexpected Start call");
return;
}
callbacks_->NetDevStart(Callbacks::StartTxn(completer));
}
void NetworkDevice::Stop(fdf::Arena& arena, StopCompleter::Sync& completer) {
bool expected = true;
if (!started_.compare_exchange_strong(expected, false)) {
LOGF(ERROR, "NetworkDevice not started, unexpected Stop call");
return;
}
callbacks_->NetDevStop(Callbacks::StopTxn(completer));
}
void NetworkDevice::GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) {
netdriver::DeviceImplInfo info;
callbacks_->NetDevGetInfo(&info);
completer.buffer(arena).Reply(fidl::ToWire(arena, std::move(info)));
}
void NetworkDevice::QueueTx(netdriver::wire::NetworkDeviceImplQueueTxRequest* request,
fdf::Arena& arena, QueueTxCompleter::Sync& completer) {
if (!started_.load(std::memory_order_relaxed)) {
// Do not queue TX on a device that is not started. This can happen when a device is being
// stopped. For threading reasons the Network Device cannot hold locks when its calling into
// our implementation so it's possible that it will attempt to queue TX after it has called
// stop. These buffers will need to be completed with an error status.
fidl::VectorView<netdriver::wire::TxResult> results(arena, request->buffers.size());
for (size_t i = 0; i < request->buffers.size(); ++i) {
const netdriver::wire::TxBuffer& buffer = request->buffers[i];
results[i].id = buffer.id;
results[i].status = ZX_ERR_UNAVAILABLE;
}
auto status = netdev_ifc_.buffer(arena)->CompleteTx(results);
if (!status.ok()) {
LOGF(ERROR, "Failed to reject TX: %s", status.FormatDescription().c_str());
return;
}
return;
}
tx_frames_.reserve(request->buffers.size());
for (size_t i = 0; i < request->buffers.size(); ++i) {
const netdriver::wire::TxBuffer& buffer = request->buffers[i];
const netdriver::wire::BufferRegion& region = buffer.data[0];
tx_frames_.emplace_back(nullptr, region.vmo, region.offset, buffer.id,
vmo_addrs_[region.vmo] + region.offset, region.length,
buffer.meta.port);
tx_frames_.back().ShrinkHead(buffer.head_length);
tx_frames_.back().ShrinkTail(buffer.tail_length);
}
callbacks_->NetDevQueueTx(tx_frames_);
tx_frames_.clear();
}
void NetworkDevice::QueueRxSpace(netdriver::wire::NetworkDeviceImplQueueRxSpaceRequest* request,
fdf::Arena& arena, QueueRxSpaceCompleter::Sync& completer) {
callbacks_->NetDevQueueRxSpace(cpp20::span(request->buffers.begin(), request->buffers.end()),
vmo_addrs_);
}
void NetworkDevice::PrepareVmo(netdriver::wire::NetworkDeviceImplPrepareVmoRequest* request,
fdf::Arena& arena, PrepareVmoCompleter::Sync& completer) {
const zx_status_t status = [&]() {
uint64_t size = 0;
const uint8_t id = request->id;
zx::vmo& vmo = request->vmo;
zx_status_t status = vmo.get_size(&size);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to get VMO size on prepare: %s", zx_status_get_string(status));
return status;
}
zx_vaddr_t addr = 0;
status = zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, size, &addr);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to map VMO on prepare: %s", zx_status_get_string(status));
return status;
}
vmo_addrs_[id] = reinterpret_cast<uint8_t*>(addr);
vmo_lengths_[id] = size;
return callbacks_->NetDevPrepareVmo(id, std::move(vmo), vmo_addrs_[id], size);
}();
completer.buffer(arena).Reply(status);
}
void NetworkDevice::ReleaseVmo(netdriver::wire::NetworkDeviceImplReleaseVmoRequest* request,
fdf::Arena& arena, ReleaseVmoCompleter::Sync& completer) {
const uint8_t id = request->id;
callbacks_->NetDevReleaseVmo(id);
zx_status_t status =
zx::vmar::root_self()->unmap(reinterpret_cast<zx_vaddr_t>(vmo_addrs_[id]), vmo_lengths_[id]);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to unmap VMO on release: %s", zx_status_get_string(status));
}
vmo_addrs_[id] = nullptr;
vmo_lengths_[id] = 0;
completer.buffer(arena).Reply();
}
void NetworkDevice::on_fidl_error(fidl::UnbindInfo error) {
if (error.status() != ZX_OK) {
LOGF(ERROR, "NetworkDevice fidl error: %s", error.FormatDescription().c_str());
return;
}
// Begin or continue removal. Either the child node was removed because we initiated the removal
// or because the node unexpectedly went away.
std::lock_guard lock(mutex_);
node_controller_ = {};
RemoveLocked();
}
zx_status_t NetworkDevice::AddService(fdf::OutgoingDirectory& outgoing) {
outgoing_directory_ = &outgoing;
parent_dispatcher_ = fdf::Dispatcher::GetCurrent()->async_dispatcher();
netdriver::Service::InstanceHandler handler(
{.network_device_impl = [this](fdf::ServerEnd<netdriver::NetworkDeviceImpl> server) mutable {
std::lock_guard lock(mutex_);
binding_ = fdf::BindServer(
netdev_dispatcher_, std::move(server), this,
[this](NetworkDevice*, fidl::UnbindInfo info,
fdf::ServerEnd<fuchsia_hardware_network_driver::NetworkDeviceImpl>) {
if (!info.is_user_initiated()) {
LOGF(WARNING, "NetworkDevice server unbound: %s", info.FormatDescription().c_str());
}
std::lock_guard lock(mutex_);
binding_.reset();
// Remove the entire network device at this point as it can no longer be used. This
// will trigger NetDevRelease to indicate to the owner of this object that the
// NetworkDevice is going away. It should be safe to do this even if this unbind is
// part of another Remove call as that is part of the removal process.
RemoveLocked();
});
server_connected_.Signal();
}});
auto status = outgoing.AddService<netdriver::Service>(std::move(handler));
if (status.is_error()) {
LOGF(ERROR, "Failed to add outgoing service: %s", status.status_string());
return status.status_value();
}
return ZX_OK;
}
bool NetworkDevice::ShouldCompleteFrame(const Frame& frame) {
if (vmo_addrs_[frame.VmoId()] == nullptr) [[unlikely]] {
// Do not return frames with VMOs we are not aware of. This can happen if the frame
// belongs to some internal VMO in the driver, by skipping it here we allow the driver
// some relaxation in what it passes to us. It can also happen if we are attempting to
// complete receives after a VMO has been released, at that point returning the frame
// serves no purpose.
return false;
}
return true;
}
void NetworkDevice::Unbind() {
std::lock_guard lock(mutex_);
if (binding_.has_value()) {
binding_->Unbind();
}
}
void NetworkDevice::RemoveService() {
std::lock_guard lock(mutex_);
if (outgoing_directory_) {
auto result = outgoing_directory_->RemoveService<netdriver::Service>(fdf::kDefaultInstance);
if (result.is_error()) {
LOGF(ERROR, "Failed to remove service from outgoing directory: %s", result.status_string());
}
outgoing_directory_ = nullptr;
}
// Because service removal is synchronous there is no asynchronous notification of completion.
// The removal process has to be continued here instead.
RemoveLocked();
}
void NetworkDevice::RemoveNode() {
std::lock_guard lock(mutex_);
if (node_controller_.is_valid()) {
auto result = node_controller_->Remove();
if (!result.ok()) {
LOGF(ERROR, "Controller node remove failed, FIDL error: %s",
result.FormatDescription().c_str());
}
// Don't destroy the node controller here, it needs to stay alive to call on_fidl_error to
// continue the removal procedure.
}
}
void NetworkDevice::Release() {
// Now that everything is shut down and removed, move back to the Released state. Don't hold any
// locks during the callback. The NetworkDevice object might be destroyed in the callback so any
// lock would then attempt to unlock a destroyed mutex when we exit this scope. The state
// transition happens before the callback for the same reason.
{
std::lock_guard lock(mutex_);
state_ = State::Released;
}
callbacks_->NetDevRelease();
}
} // namespace wlan::drivers::components