| // 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 <lib/async/default.h> |
| #include <lib/fxl/logging.h> |
| #include <zx/fifo.h> |
| |
| #include "guest_ethernet.h" |
| |
| // This is a locally administered MAC address (first byte 0x02) mixed with the |
| // Google Organizationally Unique Identifier (00:1a:11). The host gets ff:ff:ff |
| // and the guest gets 00:00:00 for the last three octets. |
| static constexpr uint8_t kHostMacAddress[6] = {0x02, 0x1a, 0x11, |
| 0xff, 0xff, 0xff}; |
| static constexpr uint32_t kMtu = 1500; |
| |
| zx_status_t GuestEthernet::Send(void* data, size_t length) { |
| if (!io_vmo_) { |
| FXL_LOG(ERROR) << "Send called before IO buffer was set up"; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (rx_entries_count_ == 0) { |
| size_t count; |
| zx_status_t status = |
| rx_fifo_.read(sizeof(fuchsia::hardware::ethernet::FifoEntry), |
| rx_entries_.data(), rx_entries_.size(), &count); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to read from rx fifo: " << status; |
| return status; |
| } |
| rx_entries_count_ = count; |
| } |
| |
| rx_entries_count_--; |
| fuchsia::hardware::ethernet::FifoEntry entry = rx_entries_[rx_entries_count_]; |
| if (entry.offset >= io_size_ || entry.length > (io_size_ - entry.offset) || |
| length > entry.length) { |
| FXL_LOG(ERROR) << "Invalid fifo entry for packet"; |
| entry.length = 0; |
| entry.flags = fuchsia::hardware::ethernet::FIFO_INVALID; |
| } else { |
| memcpy(reinterpret_cast<void*>(io_addr_ + entry.offset), data, length); |
| entry.length = length; |
| entry.flags = fuchsia::hardware::ethernet::FIFO_RX_OK; |
| } |
| |
| zx_status_t status = |
| rx_fifo_.write(sizeof(fuchsia::hardware::ethernet::FifoEntry), &entry, 1, |
| nullptr /* actual count */); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to write to rx fifo"; |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| void GuestEthernet::OnTxFifoReadable(async_dispatcher_t* dispatcher, |
| async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| FXL_CHECK(status == ZX_OK) << "Wait for tx fifo readable failed " << status; |
| std::vector<fuchsia::hardware::ethernet::FifoEntry> entries( |
| kVirtioNetQueueSize / 2); |
| size_t count; |
| while (true) { |
| status = tx_fifo_.read(sizeof(fuchsia::hardware::ethernet::FifoEntry), |
| entries.data(), entries.size(), &count); |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| status = tx_fifo_wait_.Begin(async_get_default_dispatcher()); |
| FXL_CHECK(status == ZX_OK) << "Failed to wait on tx fifo"; |
| return; |
| } |
| FXL_CHECK(status == ZX_OK) << "Failed to read tx fifo"; |
| for (size_t i = 0; i != count; ++i) { |
| receiver_->Receive(io_addr_ + entries[i].offset, entries[i].length, |
| entries[i]); |
| } |
| } |
| } |
| |
| void GuestEthernet::Complete( |
| const fuchsia::hardware::ethernet::FifoEntry& entry) { |
| size_t count; |
| zx_status_t status = tx_fifo_.write( |
| sizeof(fuchsia::hardware::ethernet::FifoEntry), &entry, 1, &count); |
| FXL_CHECK(status == ZX_OK); |
| FXL_CHECK(count == 1); |
| } |
| |
| void GuestEthernet::GetInfo(GetInfoCallback callback) { |
| fuchsia::hardware::ethernet::Info info; |
| info.features = fuchsia::hardware::ethernet::INFO_FEATURE_SYNTH; |
| info.mtu = kMtu; |
| memcpy(&info.mac, kHostMacAddress, sizeof(info.mac)); |
| 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(fuchsia::hardware::ethernet::FifoEntry), |
| /* options */ 0u, &fifos->rx, &rx_fifo_); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create fifo"; |
| callback(status, nullptr); |
| return; |
| } |
| status = zx::fifo::create(kVirtioNetQueueSize, |
| sizeof(fuchsia::hardware::ethernet::FifoEntry), |
| /* options */ 0u, &fifos->tx, &tx_fifo_); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create fifo"; |
| FXL_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) { |
| FXL_LOG(ERROR) << "Failed to get vmo size"; |
| callback(status); |
| return; |
| } |
| status = zx::vmar::root_self()->map( |
| 0, vmo, 0, vmo_size, |
| ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_REQUIRE_NON_RESIZABLE, |
| &io_addr_); |
| if (status != ZX_OK) { |
| FXL_LOG(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_) { |
| FXL_LOG(ERROR) << "Start called before IO buffer was set up"; |
| callback(ZX_ERR_BAD_STATE); |
| return; |
| } |
| tx_fifo_wait_.set_object(tx_fifo_.get()); |
| tx_fifo_wait_.set_trigger(ZX_SOCKET_READABLE); |
| zx_status_t status = tx_fifo_wait_.Begin(async_get_default_dispatcher()); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to wait on tx fifo"; |
| } |
| 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) { |
| FXL_LOG(INFO) << "Guest ethernet client set to " << name; |
| callback(ZX_OK); |
| } |
| |
| void GuestEthernet::GetStatus(GetStatusCallback callback) { callback(0); } |
| |
| void GuestEthernet::SetPromiscuousMode(bool enabled, |
| SetPromiscuousModeCallback callback) { |
| callback(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| 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_ERR_NOT_SUPPORTED); |
| } |
| |
| void GuestEthernet::ConfigMulticastTestFilter( |
| ConfigMulticastTestFilterCallback callback) { |
| callback(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void GuestEthernet::DumpRegisters(DumpRegistersCallback callback) { |
| callback(ZX_ERR_NOT_SUPPORTED); |
| } |