| // 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(dispatcher, 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(); |
| |
| EnqueueRx(frame_type, id, 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; |
| } |
| other->EnqueueRx(tx_buff.frame_type(), rx_buff.id(), 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(async_dispatcher_t* dispatcher, DeviceAdapterParent* parent, |
| bool online) |
| : ddk::NetworkDeviceImplProtocol<DeviceAdapter>(), |
| parent_(parent), |
| has_sessions_(false), |
| online_(online), |
| device_info_(device_info_t{ |
| 0, // device_features |
| kFifoDepth, // rx_depth |
| kFifoDepth, // tx_depth |
| static_cast<uint8_t>(fuchsia::hardware::network::DeviceClass::UNKNOWN), // device_class |
| rx_types_.data(), // rx_types_list |
| parent->config().rx_types().size(), // rx_types_count |
| tx_types_.data(), // tx_types_list |
| parent->config().tx_types().size(), // tx_types_count |
| fuchsia::net::tun::MAX_MTU, // max_buffer_length |
| parent->config().mtu(), // min_rx_buffer_length |
| 0, // tx_head_lenght |
| 0, // tx_tail_length |
| nullptr, // rx_accel_list |
| 0, // rx_accel_count |
| nullptr, // tx_accel_list |
| 0, // tx_accel_count |
| }) { |
| // 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 |