blob: 96b4153016119afafb6a9868d6607d82e9ce198f [file] [log] [blame]
// Copyright 2020 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_adapter.h"
#include <lib/fidl-async/cpp/bind.h>
#include <lib/sync/completion.h>
#include <lib/syslog/global.h>
#include <zircon/status.h>
namespace network {
namespace tun {
constexpr uint16_t kFifoDepth = 128;
zx_status_t DeviceAdapter::Create(async_dispatcher_t* dispatcher, DeviceAdapterParent* parent,
bool online, std::unique_ptr<DeviceAdapter>* out) {
std::unique_ptr<DeviceAdapter> adapter(new DeviceAdapter(parent, online));
network_device_impl_protocol_t proto = {&adapter->network_device_impl_protocol_ops_,
adapter.get()};
zx_status_t status = NetworkDeviceInterface::Create(
dispatcher, ddk::NetworkDeviceImplProtocolClient(&proto), "network-tun", &adapter->device_);
if (status == ZX_OK) {
*out = std::move(adapter);
}
return status;
}
zx_status_t DeviceAdapter::Bind(zx::channel req) { return device_->Bind(std::move(req)); }
zx_status_t DeviceAdapter::NetworkDeviceImplInit(const network_device_ifc_protocol_t* iface) {
device_iface_ = ddk::NetworkDeviceIfcProtocolClient(iface);
return ZX_OK;
}
void DeviceAdapter::NetworkDeviceImplStart(network_device_impl_start_callback callback,
void* cookie) {
{
fbl::AutoLock lock(&state_lock_);
has_sessions_ = true;
}
parent_->OnHasSessionsChanged(this);
callback(cookie);
}
void DeviceAdapter::NetworkDeviceImplStop(network_device_impl_stop_callback callback,
void* cookie) {
{
fbl::AutoLock lock(&state_lock_);
has_sessions_ = false;
}
{
// discard all rx buffers
fbl::AutoLock lock(&rx_lock_);
while (!rx_buffers_.empty()) {
rx_buffers_.pop();
}
}
{
// discard all tx buffers
fbl::AutoLock lock(&tx_lock_);
while (!tx_buffers_.empty()) {
tx_buffers_.pop();
}
}
parent_->OnHasSessionsChanged(this);
callback(cookie);
}
void DeviceAdapter::NetworkDeviceImplGetInfo(device_info_t* out_info) { *out_info = device_info_; }
void DeviceAdapter::NetworkDeviceImplGetStatus(status_t* out_status) {
fbl::AutoLock lock(&state_lock_);
*out_status = {
parent_->config().mtu(), // mtu
online_ ? static_cast<uint32_t>(fuchsia::hardware::network::StatusFlags::ONLINE) : 0 // flags
};
}
void DeviceAdapter::NetworkDeviceImplQueueTx(const tx_buffer_t* buf_list, size_t buf_count) {
{
fbl::AutoLock state_lock(&state_lock_);
if (!online_) {
FX_VLOGF(1, "tun", "Discarding %d tx buffers because device is offline", buf_count);
fbl::AutoLock lock(&tx_lock_);
while (buf_count--) {
EnqueueTx(buf_list->id, ZX_ERR_BAD_STATE);
buf_list++;
}
CommitTx();
return;
}
fbl::AutoLock lock(&tx_lock_);
while (buf_count--) {
tx_buffers_.emplace(vmos_.MakeTxBuffer(buf_list, parent_->config().report_metadata()));
buf_list++;
}
}
parent_->OnTxAvail(this);
}
void DeviceAdapter::NetworkDeviceImplQueueRxSpace(const rx_space_buffer_t* buf_list,
size_t buf_count) {
bool has_buffers;
{
fbl::AutoLock lock(&rx_lock_);
while (buf_count--) {
rx_buffers_.emplace(vmos_.MakeRxSpaceBuffer(buf_list++));
}
has_buffers = !rx_buffers_.empty();
}
if (has_buffers) {
parent_->OnRxAvail(this);
}
}
void DeviceAdapter::NetworkDeviceImplPrepareVmo(uint8_t vmo_id, zx::vmo vmo) {
zx_status_t status = vmos_.RegisterVmo(vmo_id, std::move(vmo));
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "DeviceAdapter failed to register vmo: %s", zx_status_get_string(status));
}
}
void DeviceAdapter::NetworkDeviceImplReleaseVmo(uint8_t vmo_id) {
zx_status_t status = vmos_.UnregisterVmo(vmo_id);
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "DeviceAdapter failed to unregister vmo: %s",
zx_status_get_string(status));
}
}
void DeviceAdapter::SetOnline(bool online) {
status_t new_status;
{
fbl::AutoLock lock(&state_lock_);
if (online == online_) {
return;
}
FX_VLOGF(1, "tun", "DeviceAdapter: SetOnline: %d", online);
online_ = online;
new_status.mtu = parent_->config().mtu();
new_status.flags =
online_ ? static_cast<uint32_t>(fuchsia::hardware::network::StatusFlags::ONLINE) : 0;
if (!online_) {
// if going offline, discard all pending tx buffers
{
fbl::AutoLock tx_lock(&tx_lock_);
while (!tx_buffers_.empty()) {
EnqueueTx(tx_buffers_.front().id(), ZX_ERR_BAD_STATE);
tx_buffers_.pop();
}
CommitTx();
}
}
}
device_iface_.StatusChanged(&new_status);
}
bool DeviceAdapter::HasSession() {
fbl::AutoLock lock(&state_lock_);
return has_sessions_;
}
bool DeviceAdapter::TryGetTxBuffer(fit::callback<void(Buffer*, size_t)> callback) {
uint32_t id;
fbl::AutoLock lock(&tx_lock_);
if (tx_buffers_.empty()) {
return false;
}
auto& buff = tx_buffers_.front();
auto avail = tx_buffers_.size() - 1;
callback(&buff, avail);
id = buff.id();
tx_buffers_.pop();
EnqueueTx(id, ZX_OK);
CommitTx();
return true;
}
zx_status_t DeviceAdapter::WriteRxFrame(fuchsia::hardware::network::FrameType frame_type,
const std::vector<uint8_t>& data,
const fuchsia::net::tun::FrameMetadata* meta,
size_t* out_avail) {
{
// can't write if device is offline
fbl::AutoLock lock(&state_lock_);
if (!online_) {
return ZX_ERR_BAD_STATE;
}
}
uint32_t id;
fbl::AutoLock lock(&rx_lock_);
if (rx_buffers_.empty()) {
return ZX_ERR_SHOULD_WAIT;
}
auto& buff = rx_buffers_.front();
auto status = buff.Write(data);
if (status != ZX_OK) {
return status;
}
id = buff.id();
rx_buffers_.pop();
// NB: cast is only safe as long as MAX_MTU in FIDL is less than uint32 max. Guard that with a
// static assertion.
static_assert(fuchsia::net::tun::MAX_MTU <= std::numeric_limits<uint32_t>::max());
EnqueueRx(frame_type, id, static_cast<uint32_t>(data.size()), meta);
CommitRx();
*out_avail = rx_buffers_.size();
return ZX_OK;
}
void DeviceAdapter::CopyTo(DeviceAdapter* other, bool return_failed_buffers) {
fbl::AutoLock tx_lock(&tx_lock_);
fbl::AutoLock rx_lock(&other->rx_lock_);
while (!tx_buffers_.empty()) {
auto& tx_buff = tx_buffers_.front();
if (other->rx_buffers_.empty()) {
if (!return_failed_buffers) {
// stop once we run out of rx buffers to copy to
FX_VLOG(1, "tun", "DeviceAdapter:CopyTo: no more rx buffers");
break;
}
EnqueueTx(tx_buff.id(), ZX_ERR_NO_RESOURCES);
} else {
auto& rx_buff = other->rx_buffers_.front();
size_t actual;
auto status = rx_buff.CopyFrom(&tx_buff, &actual);
if (status != ZX_OK) {
FX_LOGF(ERROR, "tun", "DeviceAdapter:CopyTo: Failed to copy buffer: %s",
zx_status_get_string(status));
EnqueueTx(tx_buff.id(), status);
} else {
// enqueue the data to be returned in other, and enqueue the complete tx in self.
auto meta = tx_buff.TakeMetadata();
if (meta) {
meta->flags = 0;
}
// NB: cast is only safe as long as MAX_MTU in FIDL is less than uint32 max. Guard that with
// a static assertion.
static_assert(fuchsia::net::tun::MAX_MTU <= std::numeric_limits<uint32_t>::max());
other->EnqueueRx(tx_buff.frame_type(), rx_buff.id(), static_cast<uint32_t>(actual),
meta.get());
EnqueueTx(tx_buff.id(), ZX_OK);
other->rx_buffers_.pop();
}
}
tx_buffers_.pop();
}
CommitTx();
other->CommitRx();
}
void DeviceAdapter::Teardown(fit::function<void()> callback) {
device_->Teardown(std::move(callback));
}
void DeviceAdapter::TeardownSync() {
sync_completion_t completion;
Teardown([&completion]() { sync_completion_signal(&completion); });
sync_completion_wait_deadline(&completion, ZX_TIME_INFINITE);
}
void DeviceAdapter::EnqueueRx(fuchsia::hardware::network::FrameType frame_type, uint32_t buffer_id,
uint32_t total_len, const fuchsia::net::tun::FrameMetadata* meta) {
auto& ret = return_rx_list_.emplace_back();
ret.id = buffer_id;
ret.meta.frame_type = static_cast<uint8_t>(frame_type);
ret.total_length = total_len;
if (meta) {
ret.meta.flags = meta->flags;
ret.meta.info_type = static_cast<uint32_t>(meta->info_type);
if (meta->info_type != fuchsia::hardware::network::InfoType::NO_INFO) {
FX_LOGF(WARNING, "tun", "Unrecognized info type %d", ret.meta.info_type);
}
} else {
ret.meta.flags = 0;
ret.meta.info_type = static_cast<uint32_t>(fuchsia::hardware::network::InfoType::NO_INFO);
}
}
void DeviceAdapter::CommitRx() {
if (!return_rx_list_.empty()) {
device_iface_.CompleteRx(return_rx_list_.data(), return_rx_list_.size());
return_rx_list_.clear();
}
}
void DeviceAdapter::EnqueueTx(uint32_t id, zx_status_t status) {
auto& tx = return_tx_list_.emplace_back();
tx.id = id;
tx.status = status;
}
void DeviceAdapter::CommitTx() {
if (!return_tx_list_.empty()) {
device_iface_.CompleteTx(return_tx_list_.data(), return_tx_list_.size());
return_tx_list_.clear();
}
}
DeviceAdapter::DeviceAdapter(DeviceAdapterParent* parent, bool online)
: ddk::NetworkDeviceImplProtocol<DeviceAdapter>(),
parent_(parent),
online_(online),
device_info_(device_info_t{
.tx_depth = kFifoDepth,
.rx_depth = kFifoDepth,
.rx_threshold = kFifoDepth / 2,
.device_class = static_cast<uint8_t>(fuchsia::hardware::network::DeviceClass::UNKNOWN),
.rx_types_list = rx_types_.data(),
.rx_types_count = parent->config().rx_types().size(),
.tx_types_list = tx_types_.data(),
.tx_types_count = parent->config().tx_types().size(),
.max_buffer_length = fuchsia::net::tun::MAX_MTU,
.buffer_alignment = 1,
.min_rx_buffer_length = parent->config().mtu(),
.min_tx_buffer_length = parent->config().min_tx_buffer_length(),
}) {
// Initialize rx_types_ and tx_types_ lists from parent config.
for (size_t i = 0; i < parent_->config().rx_types().size(); i++) {
rx_types_[i] = static_cast<uint8_t>(parent_->config().rx_types()[i]);
}
for (size_t i = 0; i < parent_->config().tx_types().size(); i++) {
tx_types_[i].features = parent_->config().tx_types()[i].features;
tx_types_[i].type = static_cast<uint8_t>(parent_->config().tx_types()[i].type);
tx_types_[i].supported_flags =
static_cast<uint32_t>(parent_->config().tx_types()[i].supported_flags);
}
}
} // namespace tun
} // namespace network