blob: 93a6369d6c73c6725741eb694abe728958e6407d [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 "guest_ethernet.h"
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/fifo.h>
static constexpr uint32_t kMtu = 1500;
zx_status_t GuestEthernet::Send(void* data, size_t length) {
if (!io_vmo_) {
FX_LOGS(ERROR) << "Send called before IO buffer was set up";
return ZX_ERR_BAD_STATE;
}
if (rx_fifo_wait_.is_pending()) {
return ZX_ERR_SHOULD_WAIT;
}
if (rx_entries_count_ == 0) {
size_t count;
zx_status_t status =
rx_fifo_.read(sizeof(eth_fifo_entry_t), rx_entries_.data(), rx_entries_.size(), &count);
if (status == ZX_ERR_SHOULD_WAIT) {
status = rx_fifo_wait_.Begin(async_get_default_dispatcher());
FX_CHECK(status == ZX_OK) << "Failed to wait on rx fifo: " << status;
return ZX_ERR_SHOULD_WAIT;
} else if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to read from rx fifo: " << status;
return status;
}
rx_entries_count_ = count;
}
rx_entries_count_--;
eth_fifo_entry_t entry = rx_entries_[rx_entries_count_];
if (entry.offset >= io_size_ || entry.length > (io_size_ - entry.offset) ||
length > entry.length) {
FX_LOGS(ERROR) << "Invalid fifo entry for packet";
entry.length = 0;
entry.flags = ETH_FIFO_INVALID;
} else {
memcpy(reinterpret_cast<void*>(io_addr_ + entry.offset), data, length);
entry.length = length;
entry.flags = ETH_FIFO_RX_OK;
}
zx_status_t status =
rx_fifo_.write(sizeof(eth_fifo_entry_t), &entry, 1, nullptr /* actual count */);
// There are a fixed number of entries in the system and if we read an entry out of the fifo then
// there should be enough space to write it back. However, if some transient error causes the fifo
// to not be writable then we should block here to avoid losing track of the fifo entry. Assuming
// that the netstack is behaving correctly then this should not deadlock.
if (status == ZX_ERR_SHOULD_WAIT) {
FX_LOGS(WARNING) << "Rx fifo is not writable. Guest ethernet will block.";
zx_signals_t pending;
rx_fifo_.wait_one(ZX_FIFO_WRITABLE, zx::time::infinite(), &pending);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to wait on rx fifo: " << status;
return status;
}
status = rx_fifo_.write(sizeof(eth_fifo_entry_t), &entry, 1, nullptr /* actual count */);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to write to rx fifo after waiting: " << status;
return status;
}
}
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to write to rx fifo " << status;
return status;
}
return ZX_OK;
}
void GuestEthernet::OnRxFifoReadable(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
FX_CHECK(status == ZX_OK) << "Wait for rx fifo readable failed " << status;
device_->ReadyToSend();
}
void GuestEthernet::OnTxFifoReadable(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
FX_CHECK(status == ZX_OK) << "Wait for tx fifo readable failed " << status;
std::vector<eth_fifo_entry_t> entries(kVirtioNetQueueSize / 2);
size_t count;
while (true) {
status = tx_fifo_.read(sizeof(eth_fifo_entry_t), entries.data(), entries.size(), &count);
if (status == ZX_ERR_SHOULD_WAIT) {
status = tx_fifo_wait_.Begin(async_get_default_dispatcher());
FX_CHECK(status == ZX_OK) << "Failed to wait on tx fifo";
return;
}
FX_CHECK(status == ZX_OK) << "Failed to read tx fifo";
for (size_t i = 0; i != count; ++i) {
device_->Receive(io_addr_ + entries[i].offset, entries[i].length, entries[i]);
}
}
}
void GuestEthernet::Complete(const eth_fifo_entry_t& entry) {
size_t count;
zx_status_t status = tx_fifo_.write(sizeof(eth_fifo_entry_t), &entry, 1, &count);
FX_CHECK(status == ZX_OK);
FX_CHECK(count == 1);
}
void GuestEthernet::GetInfo(GetInfoCallback callback) {
fuchsia::hardware::ethernet::Info info;
info.features = fuchsia::hardware::ethernet::Features::SYNTHETIC;
info.mtu = kMtu;
fuchsia::hardware::ethernet::MacAddress mac_address = device_->GetMacAddress();
memcpy(&info.mac, mac_address.octets.data(), mac_address.octets.size());
callback(info);
}
void GuestEthernet::GetFifos(GetFifosCallback callback) {
auto fifos = std::make_unique<fuchsia::hardware::ethernet::Fifos>();
zx_status_t status = zx::fifo::create(kVirtioNetQueueSize, sizeof(eth_fifo_entry_t),
/* options */ 0u, &fifos->rx, &rx_fifo_);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create fifo";
callback(status, nullptr);
return;
}
status = zx::fifo::create(kVirtioNetQueueSize, sizeof(eth_fifo_entry_t),
/* options */ 0u, &fifos->tx, &tx_fifo_);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create fifo";
FX_CHECK(rx_fifo_.release() == ZX_OK) << "Failed to release fifo";
callback(status, nullptr);
return;
}
fifos->rx_depth = kVirtioNetQueueSize;
fifos->tx_depth = kVirtioNetQueueSize;
callback(ZX_OK, std::move(fifos));
}
void GuestEthernet::SetIOBuffer(zx::vmo vmo, SetIOBufferCallback callback) {
if (io_vmo_) {
callback(ZX_ERR_ALREADY_BOUND);
return;
}
uint64_t vmo_size;
zx_status_t status = vmo.get_size(&vmo_size);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get vmo size";
callback(status);
return;
}
status =
zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_REQUIRE_NON_RESIZABLE,
0, vmo, 0, vmo_size, &io_addr_);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to map io buffer";
callback(status);
return;
}
io_vmo_ = std::move(vmo);
io_size_ = vmo_size;
callback(ZX_OK);
}
void GuestEthernet::Start(StartCallback callback) {
if (!io_vmo_) {
FX_LOGS(ERROR) << "Start called before IO buffer was set up";
callback(ZX_ERR_BAD_STATE);
return;
}
// Send a signal to the netstack to bring the link up.
rx_fifo_.signal_peer(0, fuchsia::hardware::ethernet::SIGNAL_STATUS);
tx_fifo_wait_.set_object(tx_fifo_.get());
tx_fifo_wait_.set_trigger(ZX_FIFO_READABLE);
zx_status_t status = tx_fifo_wait_.Begin(async_get_default_dispatcher());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to wait on tx fifo";
}
rx_fifo_wait_.set_object(rx_fifo_.get());
rx_fifo_wait_.set_trigger(ZX_FIFO_READABLE);
callback(status);
}
void GuestEthernet::Stop(StopCallback callback) { callback(); }
void GuestEthernet::ListenStart(ListenStartCallback callback) { callback(ZX_ERR_NOT_SUPPORTED); }
void GuestEthernet::ListenStop(ListenStopCallback callback) { callback(); }
void GuestEthernet::SetClientName(std::string name, SetClientNameCallback callback) {
FX_LOGS(INFO) << "Guest ethernet client set to " << name;
callback(ZX_OK);
}
void GuestEthernet::GetStatus(GetStatusCallback callback) {
// Clear the signal to the netstack to complete bringing the link up.
rx_fifo_.signal_peer(fuchsia::hardware::ethernet::SIGNAL_STATUS, 0);
callback(fuchsia::hardware::ethernet::DeviceStatus::ONLINE);
}
void GuestEthernet::SetPromiscuousMode(bool enabled, SetPromiscuousModeCallback callback) {
callback(ZX_OK);
}
void GuestEthernet::ConfigMulticastAddMac(fuchsia::hardware::ethernet::MacAddress addr,
ConfigMulticastAddMacCallback callback) {
callback(ZX_ERR_NOT_SUPPORTED);
}
void GuestEthernet::ConfigMulticastDeleteMac(fuchsia::hardware::ethernet::MacAddress addr,
ConfigMulticastDeleteMacCallback callback) {
callback(ZX_ERR_NOT_SUPPORTED);
}
void GuestEthernet::ConfigMulticastSetPromiscuousMode(
bool enabled, ConfigMulticastSetPromiscuousModeCallback callback) {
callback(ZX_OK);
}
void GuestEthernet::ConfigMulticastTestFilter(ConfigMulticastTestFilterCallback callback) {
callback(ZX_ERR_NOT_SUPPORTED);
}
void GuestEthernet::DumpRegisters(DumpRegistersCallback callback) {
callback(ZX_ERR_NOT_SUPPORTED);
}