blob: 785849977f75234045695480592c3f7d2d1e5cf7 [file] [log] [blame]
// Copyright 2018 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 "garnet/lib/machina/virtio_net.h"
#include <virtio/virtio_ids.h>
#include <zircon/device/ethernet.h>
#include <fcntl.h>
#include <string.h>
#include "lib/fxl/logging.h"
namespace machina {
VirtioNet::VirtioNet(const PhysMem& phys_mem)
: VirtioDevice(VIRTIO_ID_NET,
&config_,
sizeof(config_),
queues_,
kNumQueues,
phys_mem) {
config_.status = VIRTIO_NET_S_LINK_UP;
config_.max_virtqueue_pairs = 1;
// TODO(abdulla): Support VIRTIO_NET_F_STATUS via IOCTL_ETHERNET_GET_STATUS.
add_device_features(VIRTIO_NET_F_MAC);
}
VirtioNet::~VirtioNet() {
zx_handle_close(fifos_.tx_fifo);
zx_handle_close(fifos_.rx_fifo);
}
zx_status_t VirtioNet::Start(const char* path) {
net_fd_.reset(open(path, O_RDONLY));
if (!net_fd_) {
FXL_LOG(INFO) << "Could not open Ethernet device " << path;
return ZX_ERR_IO;
}
eth_info_t info;
ssize_t ret = ioctl_ethernet_get_info(net_fd_.get(), &info);
if (ret < 0) {
FXL_LOG(ERROR) << "Failed to get Ethernet device info";
return ret;
}
// TODO(abdulla): Use a different MAC address from the host.
memcpy(config_.mac, info.mac, sizeof(config_.mac));
ret = ioctl_ethernet_get_fifos(net_fd_.get(), &fifos_);
if (ret < 0) {
FXL_LOG(ERROR) << "Failed to get FIFOs from Ethernet device";
return ret;
}
// TODO(ZX-1333): Limit how much of they guest physical address space
// is exposed to the Ethernet server.
zx_handle_t vmo;
zx_status_t status =
zx_handle_duplicate(phys_mem().vmo().get(), ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to duplicate guest physical memory";
return status;
}
ret = ioctl_ethernet_set_iobuf(net_fd_.get(), &vmo);
if (ret < 0) {
FXL_LOG(ERROR) << "Failed to set VMO for Ethernet device";
zx_handle_close(vmo);
return ret;
}
ret = ioctl_ethernet_set_client_name(net_fd_.get(), "machina", 7);
if (ret < 0) {
FXL_LOG(ERROR) << "Failed to set client name for Ethernet device";
return ret;
}
ret = ioctl_ethernet_start(net_fd_.get());
if (ret < 0) {
FXL_LOG(ERROR) << "Failed to start communication with Ethernet device";
return ret;
}
thrd_t tx_thread;
ret = thrd_create_with_name(
&tx_thread,
[](void* ctx) -> int {
return static_cast<VirtioNet*>(ctx)->TransmitLoop();
},
this, "virtio-net-transmit");
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
ret = thrd_detach(tx_thread);
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
thrd_t rx_thread;
ret = thrd_create_with_name(
&rx_thread,
[](void* ctx) -> int {
return static_cast<VirtioNet*>(ctx)->ReceiveLoop();
},
this, "virtio-net-receive");
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
ret = thrd_detach(rx_thread);
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
FXL_LOG(INFO) << "Polling device " << path << " for Ethernet frames";
return ZX_OK;
}
zx_status_t VirtioNet::DrainQueue(virtio_queue_t* queue,
uint32_t max_entries,
zx_handle_t fifo) {
eth_fifo_entry_t entries[max_entries];
// Wait on first descriptor chain to become available.
uint16_t head;
virtio_queue_wait(queue, &head);
// Read all available descriptor chains from the queue.
zx_status_t status = ZX_OK;
uint32_t num_entries = 0;
for (; num_entries < max_entries && status == ZX_OK; num_entries++) {
virtio_desc_t desc;
status = virtio_queue_read_desc(queue, head, &desc);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to read descriptor from queue";
return status;
}
if (desc.has_next) {
FXL_LOG(ERROR) << "Descriptor chain must have a length of 1";
return ZX_ERR_IO_DATA_INTEGRITY;
}
auto header = reinterpret_cast<virtio_net_hdr_t*>(desc.addr);
entries[num_entries] = {
.offset = static_cast<uint32_t>(
reinterpret_cast<uintptr_t>(header + 1) - phys_mem().addr()),
.length = static_cast<uint16_t>(desc.len - sizeof(*header)),
.flags = 0,
.cookie = reinterpret_cast<void*>(head),
};
status = virtio_queue_next_avail(queue, &head);
}
if (status != ZX_ERR_NOT_FOUND) {
FXL_LOG(ERROR) << "Failed to fetch descriptor chain from queue";
return status;
}
// Enqueue entries for the Ethernet device.
uint32_t count;
status = zx_fifo_write(fifo, entries, sizeof(*entries) * num_entries, &count);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to enqueue buffer";
return status;
}
if (count != num_entries) {
FXL_LOG(ERROR) << "Only wrote " << count << " of " << num_entries
<< " entries to Ethernet device";
return ZX_ERR_IO_DATA_LOSS;
}
// Wait for entries to dequeue.
while (true) {
zx_signals_t observed;
status = zx_object_wait_one(fifo, ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED,
ZX_TIME_INFINITE, &observed);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to wait on queue";
return status;
}
if (observed & ZX_FIFO_PEER_CLOSED) {
FXL_LOG(ERROR) << "FIFO was closed";
return status;
}
if (observed & ZX_FIFO_READABLE) {
break;
}
}
// Dequeue entries for the Ethernet device.
status = zx_fifo_read(fifo, entries, sizeof(entries), &count);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to read from queue";
}
for (uint32_t i = 0; i < count; i++) {
auto head = reinterpret_cast<uintptr_t>(entries[i].cookie);
auto length = entries[i].length + sizeof(virtio_net_hdr_t);
virtio_queue_return(rx_queue(), head, length);
}
// Notify guest of updates to the queue.
NotifyGuest();
return ZX_OK;
}
zx_status_t VirtioNet::ReceiveLoop() {
zx_status_t status;
do {
status = DrainQueue(rx_queue(), fifos_.rx_depth, fifos_.rx_fifo);
} while (status == ZX_OK);
return status;
}
zx_status_t VirtioNet::TransmitLoop() {
zx_status_t status;
do {
status = DrainQueue(tx_queue(), fifos_.tx_depth, fifos_.tx_fifo);
} while (status == ZX_OK);
return status;
}
} // namespace machina