blob: 9cf4cb214942c837c8173111ba0939d2ea27e239 [file]
// 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 "netdevice.h"
#include <fidl/fuchsia.driver.framework/cpp/fidl.h>
#include <fidl/fuchsia.hardware.network/cpp/wire.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/defer.h>
#include <lib/virtio/ring.h>
#include <lib/zircon-internal/align.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <virtio/net.h>
#include <virtio/virtio.h>
#include "src/connectivity/ethernet/drivers/virtio/virtio_net_driver.h"
// Enables/disables debugging info
#define LOCAL_TRACE 0
namespace virtio {
namespace {
bool IsLinkActive(const virtio_net_config& config, bool is_status_supported) {
// 5.1.4.2 Driver Requirements: Device configuration layout
//
// If the driver does not negotiate the VIRTIO_NET_F_STATUS feature, it SHOULD assume the link
// is active, otherwise it SHOULD read the link status from the bottom bit of status.
//
// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2000004
return is_status_supported ? config.status & VIRTIO_NET_S_LINK_UP : true;
}
uint16_t MaxVirtqueuePairs(const virtio_net_config& config, bool is_mq_supported) {
// 5.1.5 Device Initialization
//
// Identify and initialize the receive and transmission virtqueues, up to N of each kind. If
// VIRTIO_NET_F_MQ feature bit is negotiated, N=max_virtqueue_pairs, otherwise identify N=1.
//
// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2040005
return is_mq_supported ? config.max_virtqueue_pairs : 1;
}
} // namespace
NetworkDevice::NetworkDevice(VirtioNetDriver* driver, zx::bti bti_handle,
std::unique_ptr<Backend> backend,
const std::shared_ptr<fdf::Namespace>& incoming,
const std::optional<std::string>& node_name)
: virtio::Device(std::move(bti_handle), std::move(backend)),
driver_(driver),
rx_(this),
tx_(this),
incoming_(incoming),
node_name_(node_name),
vmo_store_({
.map =
vmo_store::MapOptions{
.vm_option = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_REQUIRE_NON_RESIZABLE,
.vmar = nullptr,
},
.pin =
vmo_store::PinOptions{
.bti = zx::unowned_bti(bti()),
.bti_pin_options = ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE,
.index = true,
},
}) {}
NetworkDevice::~NetworkDevice() {}
zx_status_t NetworkDevice::Init() {
zx::result netdev_dispatcher = fdf::UnsynchronizedDispatcher::Create(
{}, "netdev-dispatcher",
[this](fdf_dispatcher_t*) { netdevice_dispatcher_shutdown_.Signal(); });
if (netdev_dispatcher.is_error()) {
fdf::error("Failed to create netdevice dispatcher: {}", netdev_dispatcher.status_string());
return netdev_dispatcher.status_value();
}
netdevice_dispatcher_ = std::move(netdev_dispatcher.value());
fbl::AutoLock lock(&state_lock_);
// Reset the device.
DeviceReset();
// Ack and set the driver status bit.
DriverStatusAck();
// Ack features. We do DeviceStatusFeaturesOk() when we actually start the network device (in
// NetworkDeviceImplStart()).
if (zx_status_t status =
AckFeatures(&is_status_supported_, &is_multiqueue_supported_, &virtio_hdr_len_);
status != ZX_OK) {
fdf::error("failed to ack features: {}", zx_status_get_string(status));
return status;
}
// Read device configuration.
virtio_net_config_t config;
CopyDeviceConfig(&config, sizeof(config));
// We've checked that the config.mac field is valid (VIRTIO_NET_F_MAC) in AckFeatures().
fdf::debug("mac: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", config.mac[0], config.mac[1],
config.mac[2], config.mac[3], config.mac[4], config.mac[5]);
fdf::debug("link active: {}", IsLinkActive(config, is_status_supported_));
fdf::debug("max virtqueue pairs: {}", MaxVirtqueuePairs(config, is_multiqueue_supported_));
static_assert(sizeof(config.mac) == sizeof(mac_.octets));
std::copy(std::begin(config.mac), std::end(config.mac), mac_.octets.begin());
if (zx_status_t status = vmo_store_.Reserve(fuchsia_hardware_network::wire::kMaxDataVmos);
status != ZX_OK) {
fdf::error("failed to initialize vmo store: {}", zx_status_get_string(status));
return status;
}
// Initialize the zx_device and publish us.
if (zx_status_t status = AddDevice(); status != ZX_OK) {
fdf::error("failed to add device: {}", zx_status_get_string(status));
return status;
}
tx_depth_ = std::min(GetRingSize(kTxId), kMaxDepth);
rx_depth_ = std::min(GetRingSize(kRxId), kMaxDepth);
// Start the interrupt thread.
StartIrqThread();
irq_thread_started_ = true;
return ZX_OK;
}
zx_status_t NetworkDevice::AddDevice() {
if (zx::result result =
compat_server_.Initialize(incoming_, driver_->outgoing(), node_name_, kChildNodeName);
result.is_error()) {
fdf::error("Failed to initialize compat server: {}", result.status_string());
return result.status_value();
}
// This callback is invoked when this service is being connected.
auto protocol = [this](fdf::ServerEnd<netdev::NetworkDeviceImpl> server_end) mutable {
fdf::BindServer(netdevice_dispatcher_.get(), std::move(server_end), this);
};
// Register the callback to handler.
netdev::Service::InstanceHandler handler({.network_device_impl = std::move(protocol)});
auto status = driver_->outgoing()->AddService<netdev::Service>(std::move(handler));
if (status.is_error()) {
fdf::error("Failed to add service to outgoing directory: {}", status.status_string());
return status.error_value();
}
std::vector offers = compat_server_.CreateOffers2();
offers.push_back(fdf::MakeOffer2<netdev::Service>());
auto [controller_client, controller_server] =
fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
auto args = fuchsia_driver_framework::NodeAddArgs{{
.name = "virtio-net-netdev",
.offers2 = std::move(offers),
.bus_info = fuchsia_driver_framework::BusInfo{{
.bus = fuchsia_driver_framework::BusType::kVirtio,
}},
}};
auto result = fidl::Call(driver_->node())
->AddChild({{
.args = std::move(args),
.controller = std::move(controller_server),
.node = {},
}});
if (result.is_error()) {
fdf::error("Failed to add net device child node: {}", result.error_value());
return result.error_value().is_domain_error() ? ZX_ERR_INTERNAL
: result.error_value().framework_error().status();
}
netdev_child_ = std::move(controller_client);
return ZX_OK;
}
void NetworkDevice::RemoveDevice() {
// Remove the driver by destroying the node channel.
driver_->take_node().reset();
}
zx_status_t NetworkDevice::AckFeatures(bool* is_status_supported, bool* is_multiqueue_supported,
uint16_t* virtio_hdr_len) {
const uint64_t supported_features = DeviceFeaturesSupported();
if (!(supported_features & VIRTIO_NET_F_MAC)) {
fdf::error("device does not have a given MAC address.");
return ZX_ERR_NOT_SUPPORTED;
}
uint64_t enable_features = VIRTIO_NET_F_MAC;
if (supported_features & VIRTIO_NET_F_STATUS) {
enable_features |= VIRTIO_NET_F_STATUS;
*is_status_supported = true;
} else {
*is_status_supported = false;
}
if (supported_features & VIRTIO_NET_F_MQ) {
enable_features |= VIRTIO_NET_F_MQ;
*is_multiqueue_supported = true;
} else {
*is_multiqueue_supported = false;
}
if (supported_features & VIRTIO_F_VERSION_1) {
enable_features |= VIRTIO_F_VERSION_1;
*virtio_hdr_len = sizeof(virtio_net_hdr_t);
} else {
// 5.1.6.1 Legacy Interface: Device Operation.
//
// The legacy driver only presented num_buffers in the struct
// virtio_net_hdr when VIRTIO_NET_F_MRG_RXBUF was negotiated; without
// that feature the structure was 2 bytes shorter.
//
// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2050006
*virtio_hdr_len = sizeof(virtio_legacy_net_hdr_t);
}
DriverFeaturesAck(enable_features);
return ZX_OK;
}
void NetworkDevice::Shutdown() {
if (netdevice_dispatcher_.get()) {
netdevice_dispatcher_.ShutdownAsync();
netdevice_dispatcher_shutdown_.Wait();
netdevice_dispatcher_.reset();
}
{
fbl::AutoLock lock(&state_lock_);
// Destroy the existing client by assigning a default constructed object to it.
ifc_ = {};
}
if (irq_thread_started_) {
// The Release call assumes that it's safe to join the IRQ thread which is only true if it was
// created and started.
virtio::Device::Release();
irq_thread_started_ = false;
}
}
void NetworkDevice::IrqRingUpdate() {
for (;;) {
bool again = IrqRingUpdateInternal();
if (!again) {
break;
}
}
}
bool NetworkDevice::IrqRingUpdateInternal() {
network::SharedAutoLock state_lock(&state_lock_);
if (!ifc_.is_valid()) {
return false;
}
bool more_work = false;
fdf::Arena arena(0u);
std::array<netdev::wire::TxResult, kMaxDepth> tx_results;
auto tx_it = tx_results.begin();
{
std::lock_guard lock(tx_lock_);
tx_.SetNoInterrupt();
// Ring::IrqRingUpdate will call this lambda on each tx buffer completed
// by the underlying device since the last IRQ.
tx_.IrqRingUpdate([this, &tx_it](vring_used_elem* used_elem) {
[]() __TA_ASSERT(tx_lock_) {}();
uint16_t id = static_cast<uint16_t>(used_elem->id & 0xffff);
ZX_ASSERT_MSG(id < kMaxDepth && tx_in_flight_active_[id], "%d is not active", id);
*tx_it++ = {.id = tx_in_flight_buffer_ids_[id], .status = ZX_OK};
tx_in_flight_active_[id] = false;
tx_.FreeDesc(id);
});
more_work |= tx_.ClearNoInterruptCheckHasWork();
}
if (size_t count = std::distance(tx_results.begin(), tx_it); count != 0) {
if (fidl::OneWayStatus status = ifc_.buffer(arena)->CompleteTx(
fidl::VectorView<netdev::wire::TxResult>::FromExternal(tx_results.data(), count));
!status.ok()) {
fdf::error("Failed to complete {} TX buffers: {}", count, status.FormatDescription());
RemoveDevice();
return false;
}
}
std::array<netdev::wire::RxBuffer, kMaxDepth> rx_buffers;
std::array<netdev::wire::RxBufferPart, kMaxDepth> rx_buffers_parts;
auto rx_part_it = rx_buffers_parts.begin();
auto rx_it = rx_buffers.begin();
{
std::lock_guard lock(rx_lock_);
rx_.SetNoInterrupt();
// Ring::IrqRingUpdate will call this lambda on each rx buffer filled by
// the underlying device since the last IRQ.
rx_.IrqRingUpdate([this, &rx_it, &rx_part_it](vring_used_elem* used_elem) {
[]() __TA_ASSERT(rx_lock_) {}();
uint16_t id = static_cast<uint16_t>(used_elem->id & 0xffff);
Descriptor in_flight = rx_in_flight_.Pop();
ZX_ASSERT_MSG(in_flight.ring_id == id,
"rx ring and FIFO id mismatch (%d != %d for buffer %d)", in_flight.ring_id, id,
in_flight.buffer_id);
vring_desc& desc = *rx_.DescFromIndex(id);
// Driver does not merge rx buffers.
ZX_ASSERT_MSG((desc.flags & VRING_DESC_F_NEXT) == 0, "descriptor chaining not supported");
auto parts_list = rx_part_it;
uint32_t len = used_elem->len - virtio_hdr_len_;
ZX_ASSERT_MSG(used_elem->len >= virtio_hdr_len_,
"got buffer (%u) smaller than virtio header (%u)", used_elem->len,
virtio_hdr_len_);
fdf::trace("Receiving {} bytes (hdrlen = {}):", len, virtio_hdr_len_);
if (driver_->logger().GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(&desc);
}
*rx_part_it++ = {
.id = in_flight.buffer_id,
.offset = virtio_hdr_len_,
.length = len,
};
*rx_it++ = netdev::wire::RxBuffer{
.meta =
{
.port = kPortId,
.frame_type = fuchsia_hardware_network::wire::FrameType::kEthernet,
},
.data = fidl::VectorView<netdev::wire::RxBufferPart>::FromExternal(&*parts_list, 1),
};
rx_.FreeDesc(id);
});
more_work |= rx_.ClearNoInterruptCheckHasWork();
}
if (size_t count = std::distance(rx_buffers.begin(), rx_it); count != 0) {
if (fidl::OneWayStatus status = ifc_.buffer(arena)->CompleteRx(
fidl::VectorView<netdev::wire::RxBuffer>::FromExternal(rx_buffers.data(), count));
!status.ok()) {
fdf::error("Failed to complete {} TX buffers: {}", count, status.FormatDescription());
RemoveDevice();
return false;
}
}
return more_work;
}
void NetworkDevice::IrqConfigChange() {
network::SharedAutoLock lock(&state_lock_);
if (!ifc_.is_valid()) {
return;
}
const fuchsia_hardware_network::PortStatus port_status = ReadStatus();
fdf::Arena arena(0u);
if (fidl::OneWayStatus status =
ifc_.buffer(arena)->PortStatusChanged(kPortId, fidl::ToWire(arena, port_status));
!status.ok()) {
fdf::error("Failed to send port status changed: {}", status.FormatDescription());
RemoveDevice();
}
}
fuchsia_hardware_network::PortStatus NetworkDevice::ReadStatus() const {
virtio_net_config config;
CopyDeviceConfig(&config, sizeof(config));
return fuchsia_hardware_network::PortStatus{}
.flags(IsLinkActive(config, is_status_supported_)
? fuchsia_hardware_network::wire::StatusFlags::kOnline
: fuchsia_hardware_network::wire::StatusFlags{})
.mtu(kMtu);
}
void NetworkDevice::Init(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplInitRequest* request, fdf::Arena& arena,
InitCompleter::Sync& completer) {
fbl::AutoLock lock(&state_lock_);
ifc_.Bind(std::move(request->iface), netdevice_dispatcher_.get());
auto [client, server] = fdf::Endpoints<fuchsia_hardware_network_driver::NetworkPort>::Create();
fdf::BindServer(netdevice_dispatcher_.get(), std::move(server), this);
ifc_.buffer(arena)
->AddPort(kPortId, std::move(client))
.Then([completer = completer.ToAsync()](
fdf::WireUnownedResult<netdev::NetworkDeviceIfc::AddPort>& result) mutable {
fdf::Arena arena(0u);
if (!result.ok()) {
fdf::error("failed to add port: {}", result.FormatDescription());
completer.buffer(arena).Reply(result.status());
return;
}
if (result->status != ZX_OK) {
fdf::error("failed to add port: {}", zx_status_get_string(result->status));
completer.buffer(arena).Reply(result->status);
return;
}
completer.buffer(arena).Reply(ZX_OK);
});
}
void NetworkDevice::Start(fdf::Arena& arena, StartCompleter::Sync& completer) {
zx_status_t status = [&]() {
// Always reset the device and reconfigure so we know where we are.
DeviceReset();
WaitForDeviceReset();
DriverStatusAck();
bool is_status_supported, is_multiqueue_supported;
uint16_t header_length;
if (zx_status_t status =
AckFeatures(&is_status_supported, &is_multiqueue_supported, &header_length);
status != ZX_OK) {
fdf::error("failed to ack features: {}", zx_status_get_string(status));
return status;
}
ZX_ASSERT_MSG(is_status_supported == is_status_supported_,
"status support changed from %u to %u between init and start",
is_status_supported_, is_status_supported);
ZX_ASSERT_MSG(is_multiqueue_supported == is_multiqueue_supported_,
"max queue support changed from %u to %u between init and start",
is_multiqueue_supported_, is_multiqueue_supported);
ZX_ASSERT_MSG(header_length == virtio_hdr_len_,
"header length changed from %u to %u between init and start", virtio_hdr_len_,
header_length);
if (zx_status_t status = DeviceStatusFeaturesOk(); status != ZX_OK) {
fdf::error("{}: Feature negotiation failed ({})", tag(), zx_status_get_string(status));
return status;
}
// Allocate virtqueues.
{
std::lock_guard rx_lock(rx_lock_);
std::lock_guard tx_lock(tx_lock_);
Ring rx_queue(this);
if (zx_status_t status = rx_queue.Init(kRxId, rx_depth_); status != ZX_OK) {
fdf::error("failed to allocate rx virtqueue: {}", zx_status_get_string(status));
return status;
}
rx_ = std::move(rx_queue);
Ring tx_queue(this);
if (zx_status_t status = tx_queue.Init(kTxId, tx_depth_); status != ZX_OK) {
fdf::error("failed to allocate tx virtqueue: {}", zx_status_get_string(status));
return status;
}
tx_ = std::move(tx_queue);
}
DriverStatusOk();
// Acquire an exclusive state lock to prevent racing with an interrupt, and
// update our status after bringing the device online.
{
fbl::AutoLock lock(&state_lock_);
if (ifc_.is_valid()) {
const fuchsia_hardware_network::PortStatus port_status = ReadStatus();
if (fidl::OneWayStatus status =
ifc_.buffer(arena)->PortStatusChanged(kPortId, fidl::ToWire(arena, port_status));
!status.ok()) {
fdf::error("Failed to send port status changed: {}", status.FormatDescription());
completer.Close(status.status());
RemoveDevice();
return status.status();
}
}
}
return ZX_OK;
}();
completer.buffer(arena).Reply(status);
}
void NetworkDevice::Stop(fdf::Arena& arena, StopCompleter::Sync& completer) {
DeviceReset();
WaitForDeviceReset();
// Once the device is reset, report that the link is offline since we're not
// going to get config interrupts anymore.
if (is_status_supported_) {
fbl::AutoLock lock(&state_lock_);
if (!ifc_.is_valid()) {
return;
}
fuchsia_hardware_network::PortStatus port_status = ReadStatus();
port_status.flags().value() &= ~fuchsia_hardware_network::wire::StatusFlags::kOnline;
if (fidl::OneWayStatus status =
ifc_.buffer(arena)->PortStatusChanged(kPortId, fidl::ToWire(arena, port_status));
!status.ok()) {
fdf::error("Failed to send port status changed: {}", status.FormatDescription());
completer.Close(status.status());
RemoveDevice();
return;
}
}
// Return all pending buffers.
{
network::SharedAutoLock state_lock(&state_lock_);
// Pending tx buffers.
{
std::array<netdev::wire::TxResult, kMaxDepth> tx_return;
auto iter = tx_return.begin();
{
std::lock_guard lock(tx_lock_);
// Free all TX ring entries to prevent the IRQ handler from completing these buffers.
tx_.IrqRingUpdate([this](vring_used_elem* used_elem) {
[]() __TA_ASSERT(tx_lock_) {}();
const uint16_t id = static_cast<uint16_t>(used_elem->id & 0xffff);
tx_.FreeDesc(id);
});
for (int i = 0; i < kMaxDepth; ++i) {
if (tx_in_flight_active_[i]) {
*iter++ = {
.id = tx_in_flight_buffer_ids_[i],
.status = ZX_ERR_BAD_STATE,
};
tx_in_flight_active_[i] = false;
}
}
}
if (iter != tx_return.begin()) {
const size_t count = std::distance(tx_return.begin(), iter);
if (fidl::OneWayStatus status = ifc_.buffer(arena)->CompleteTx(
fidl::VectorView<netdev::wire::TxResult>::FromExternal(tx_return.data(), count));
!status.ok()) {
fdf::error("Failed to complete {} TX buffers: {}", count, status.FormatDescription());
completer.Close(status.status());
RemoveDevice();
return;
}
}
}
// Pending rx buffers.
{
std::array<netdev::wire::RxBuffer, kMaxDepth> rx_return;
std::array<netdev::wire::RxBufferPart, kMaxDepth> rx_return_parts;
auto iter = rx_return.begin();
auto parts_iter = rx_return_parts.begin();
{
std::lock_guard lock(rx_lock_);
// Free all RX ring entries to prevent the IRQ handler from completing these buffers.
rx_.IrqRingUpdate([this](vring_used_elem* used_elem) {
[]() __TA_ASSERT(rx_lock_) {}();
const uint16_t id = static_cast<uint16_t>(used_elem->id & 0xffff);
rx_.FreeDesc(id);
});
while (!rx_in_flight_.Empty()) {
Descriptor d = rx_in_flight_.Pop();
*iter++ = {
.meta = {.frame_type = fuchsia_hardware_network::FrameType::kEthernet},
.data = fidl::VectorView<netdev::wire::RxBufferPart>::FromExternal(&*parts_iter, 1),
};
*parts_iter++ = {.id = d.buffer_id};
}
}
if (iter != rx_return.begin()) {
const size_t count = std::distance(rx_return.begin(), iter);
if (fidl::OneWayStatus status = ifc_.buffer(arena)->CompleteRx(
fidl::VectorView<netdev::wire::RxBuffer>::FromExternal(rx_return.data(), count));
!status.ok()) {
fdf::error("Failed to complete {} RX buffers: {}", count, status.FormatDescription());
completer.Close(status.status());
RemoveDevice();
return;
}
}
}
}
completer.buffer(arena).Reply();
}
void NetworkDevice::GetInfo(
fdf::Arena& arena,
fdf::WireServer<netdev::NetworkDeviceImpl>::GetInfoCompleter::Sync& completer) {
netdev::wire::DeviceImplInfo info = netdev::wire::DeviceImplInfo::Builder(arena)
.tx_depth(tx_depth_)
.rx_depth(rx_depth_)
.rx_threshold(static_cast<uint16_t>(rx_depth_ / 2))
.max_buffer_parts(1)
.max_buffer_length(kFrameSize)
.buffer_alignment(kBufferAlignment)
.min_rx_buffer_length(kFrameSize)
// Minimum Ethernet frame size on the wire according to
// IEEE 802.3, minus the frame check sequence.
.min_tx_buffer_length(60)
.tx_head_length(virtio_hdr_len_)
.Build();
completer.buffer(arena).Reply(info);
}
void NetworkDevice::QueueTx(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplQueueTxRequest* request,
fdf::Arena& arena, QueueTxCompleter::Sync& completer) {
network::SharedAutoLock lock(&state_lock_);
std::lock_guard tx_lock(tx_lock_);
for (const auto& buffer : request->buffers) {
ZX_DEBUG_ASSERT_MSG(buffer.data.size() == 1, "received unsupported scatter gather buffer %zu",
buffer.data.size());
const netdev::wire::BufferRegion& data = buffer.data[0];
// Grab a free descriptor.
uint16_t id;
vring_desc* desc = tx_.AllocDescChain(1, &id);
ZX_ASSERT_MSG(desc != nullptr, "failed to allocate descriptor");
// Add the data to be sent.
VmoStore::StoredVmo* stored_vmo = vmo_store_.GetVmo(data.vmo);
ZX_ASSERT_MSG(stored_vmo != nullptr, "invalid VMO id %d", data.vmo);
// Get a pointer to the header. Casting it to net header structs is valid
// because we requested alignment and tx header in
// NetworkDeviceImpl.GetInfo.
void* tx_hdr = stored_vmo->data().subspan(data.offset, virtio_hdr_len_).data();
constexpr virtio_legacy_net_hdr_t kBaseHeader = {
// If VIRTIO_NET_F_CSUM is not negotiated, the driver MUST set flags to
// zero and SHOULD supply a fully checksummed packet to the device.
.flags = 0,
// If none of the VIRTIO_NET_F_HOST_TSO4, TSO6 or UFO options have been
// negotiated, the driver MUST set gso_type to VIRTIO_NET_HDR_GSO_NONE.
.gso_type = VIRTIO_NET_HDR_GSO_NONE,
};
switch (virtio_hdr_len_) {
case sizeof(virtio_net_hdr_t):
*static_cast<virtio_net_hdr_t*>(tx_hdr) = {
.base = kBaseHeader,
// 5.1.6.2.1 Driver Requirements: Packet Transmission
//
// The driver MUST set num_buffers to zero.
//
// Implementation note: This field doesn't exist if neither
// |VIRTIO_F_VERSION_1| or |VIRTIO_F_MRG_RXBUF| have been negotiated.
//
// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2050006
.num_buffers = 0,
};
break;
case sizeof(virtio_legacy_net_hdr_t):
*static_cast<virtio_legacy_net_hdr_t*>(tx_hdr) = kBaseHeader;
break;
default:
ZX_PANIC("invalid virtio header length %d", virtio_hdr_len_);
}
fzl::PinnedVmo::Region region;
size_t actual_regions = 0;
zx_status_t status =
stored_vmo->GetPinnedRegions(data.offset, data.length, &region, 1, &actual_regions);
ZX_ASSERT_MSG(status == ZX_OK, "failed to retrieve pinned region %s (actual=%zu)",
zx_status_get_string(status), actual_regions);
*desc = {
.addr = region.phys_addr,
.len = static_cast<uint32_t>(data.length),
};
tx_in_flight_buffer_ids_[id] = buffer.id;
tx_in_flight_active_[id] = true;
// Submit the descriptor and notify the back-end.
if (driver_->logger().GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(desc);
}
fdf::trace("Sending {} bytes (hdrlen = {}):", data.length, virtio_hdr_len_);
tx_.SubmitChain(id);
}
if (!tx_.NoNotify()) {
tx_.Kick();
}
}
void NetworkDevice::QueueRxSpace(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplQueueRxSpaceRequest* request,
fdf::Arena& arena, QueueRxSpaceCompleter::Sync& completer) {
network::SharedAutoLock lock(&state_lock_);
std::lock_guard rx_lock(rx_lock_);
for (const auto& buffer : request->buffers) {
const netdev::wire::BufferRegion& data = buffer.region;
// Grab a free descriptor.
uint16_t id;
vring_desc* desc = rx_.AllocDescChain(1, &id);
ZX_ASSERT_MSG(desc != nullptr, "failed to allocate descriptor");
// Add the data to be sent.
VmoStore::StoredVmo* stored_vmo = vmo_store_.GetVmo(data.vmo);
ZX_ASSERT_MSG(stored_vmo != nullptr, "invalid VMO id %d", data.vmo);
fzl::PinnedVmo::Region region;
size_t actual_regions = 0;
zx_status_t status =
stored_vmo->GetPinnedRegions(data.offset, data.length, &region, 1, &actual_regions);
ZX_ASSERT_MSG(status == ZX_OK, "failed to retrieve pinned region %s (actual=%zu)",
zx_status_get_string(status), actual_regions);
*desc = {
.addr = region.phys_addr,
.len = static_cast<uint32_t>(data.length),
.flags = VRING_DESC_F_WRITE,
};
rx_in_flight_.Push({
.buffer_id = buffer.id,
.ring_id = id,
});
// Submit the descriptor and notify the back-end.
if (driver_->logger().GetSeverity() <= FUCHSIA_LOG_TRACE) {
virtio_dump_desc(desc);
}
fdf::trace("Queueing rx space with {} bytes:", data.length);
rx_.SubmitChain(id);
}
if (!rx_.NoNotify()) {
rx_.Kick();
}
}
void NetworkDevice::PrepareVmo(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplPrepareVmoRequest* request,
fdf::Arena& arena, PrepareVmoCompleter::Sync& completer) {
zx_status_t status = [&]() {
fbl::AutoLock vmo_lock(&state_lock_);
return vmo_store_.RegisterWithKey(request->id, std::move(request->vmo));
}();
completer.buffer(arena).Reply(status);
}
void NetworkDevice::ReleaseVmo(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplReleaseVmoRequest* request,
fdf::Arena& arena, ReleaseVmoCompleter::Sync& completer) {
fbl::AutoLock vmo_lock(&state_lock_);
if (zx::result<zx::vmo> status = vmo_store_.Unregister(request->id);
status.status_value() != ZX_OK) {
fdf::error("failed to release vmo id = {}: {}", request->id, status);
}
completer.buffer(arena).Reply();
}
void NetworkDevice::GetInfo(
fdf::Arena& arena, fdf::WireServer<netdev::NetworkPort>::GetInfoCompleter::Sync& completer) {
constexpr fuchsia_hardware_network::wire::FrameType kRxTypesList[] = {
fuchsia_hardware_network::wire::FrameType::kEthernet};
constexpr fuchsia_hardware_network::wire::FrameTypeSupport kTxTypesList[] = {{
.type = fuchsia_hardware_network::wire::FrameType::kEthernet,
.features = fuchsia_hardware_network::wire::kFrameFeaturesRaw,
}};
fuchsia_hardware_network::wire::PortBaseInfo info =
fuchsia_hardware_network::wire::PortBaseInfo::Builder(arena)
.port_class(fuchsia_hardware_network::wire::PortClass::kEthernet)
.rx_types(kRxTypesList)
.tx_types(kTxTypesList)
.Build();
completer.buffer(arena).Reply(info);
}
void NetworkDevice::GetStatus(fdf::Arena& arena, GetStatusCompleter::Sync& completer) {
completer.buffer(arena).Reply(fidl::ToWire(arena, ReadStatus()));
}
void NetworkDevice::SetActive(
fuchsia_hardware_network_driver::wire::NetworkPortSetActiveRequest* request, fdf::Arena& arena,
SetActiveCompleter::Sync& completer) {}
void NetworkDevice::GetMac(fdf::Arena& arena, GetMacCompleter::Sync& completer) {
auto [client, server] = fdf::Endpoints<fuchsia_hardware_network_driver::MacAddr>::Create();
fdf::BindServer(netdevice_dispatcher_.get(), std::move(server), this);
completer.buffer(arena).Reply(std::move(client));
}
void NetworkDevice::Removed(fdf::Arena& arena, RemovedCompleter::Sync& completer) {
// Do nothing.
}
void NetworkDevice::GetAddress(fdf::Arena& arena, GetAddressCompleter::Sync& completer) {
fuchsia_net::wire::MacAddress mac;
std::copy(mac_.octets.begin(), mac_.octets.end(), mac.octets.data());
completer.buffer(arena).Reply(mac);
}
void NetworkDevice::GetFeatures(fdf::Arena& arena, GetFeaturesCompleter::Sync& completer) {
netdev::wire::Features features =
netdev::wire::Features::Builder(arena)
.multicast_filter_count(0)
.supported_modes(netdev::wire::SupportedMacFilterMode::kPromiscuous)
.Build();
completer.buffer(arena).Reply(features);
}
void NetworkDevice::SetMode(fuchsia_hardware_network_driver::wire::MacAddrSetModeRequest* request,
fdf::Arena& arena, SetModeCompleter::Sync& completer) {
/* We only support promiscuous mode, nothing to do */
ZX_ASSERT_MSG(request->mode == fuchsia_hardware_network::wire::MacFilterMode::kPromiscuous,
"unsupported mode %u", static_cast<uint32_t>(request->mode));
ZX_ASSERT_MSG(request->multicast_macs.size() == 0, "unsupported multicast count %zu",
request->multicast_macs.size());
completer.buffer(arena).Reply();
}
} // namespace virtio