blob: ee7e55d9b4f298ade75fe033fb7549d67e7d7fb4 [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 <dirent.h>
#include <endian.h>
#include <fcntl.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/hardware/ax88179/llcpp/fidl.h>
#include <fuchsia/hardware/ethernet/llcpp/fidl.h>
#include <fuchsia/hardware/usb/virtual/bus/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/watcher.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/usb-virtual-bus-launcher/usb-virtual-bus-launcher.h>
#include <lib/zx/clock.h>
#include <zircon/device/ethernet.h>
#include <zircon/hw/usb.h>
#include <zircon/hw/usb/cdc.h>
#include <zxtest/zxtest.h>
#include "asix-88179-regs.h"
namespace usb_ax88179 {
namespace {
namespace ethernet = ::llcpp::fuchsia::hardware::ethernet;
namespace ax88179 = ::llcpp::fuchsia::hardware::ax88179;
class USBVirtualBus : public usb_virtual_bus_base::USBVirtualBusBase {
public:
USBVirtualBus() = default;
void InitUsbAx88179(fbl::String* dev_path, fbl::String* test_function_path);
};
void USBVirtualBus::InitUsbAx88179(fbl::String* dev_path, fbl::String* test_function_path) {
namespace usb_peripheral = ::llcpp::fuchsia::hardware::usb::peripheral;
using ConfigurationDescriptor =
::fidl::VectorView<::llcpp::fuchsia::hardware::usb::peripheral::FunctionDescriptor>;
usb_peripheral::DeviceDescriptor device_desc = {};
device_desc.bcd_usb = htole16(0x0200);
device_desc.b_max_packet_size0 = 64;
device_desc.bcd_device = htole16(0x0100);
device_desc.b_num_configurations = 1;
usb_peripheral::FunctionDescriptor usb_ax88179_desc = {
.interface_class = USB_CLASS_COMM,
.interface_subclass = USB_CDC_SUBCLASS_ETHERNET,
.interface_protocol = 0,
};
device_desc.id_vendor = htole16(ASIX_VID);
device_desc.id_product = htole16(AX88179_PID);
std::vector<ConfigurationDescriptor> config_descs;
std::vector<usb_peripheral::FunctionDescriptor> function_descs;
function_descs.push_back(usb_ax88179_desc);
ConfigurationDescriptor config_desc;
config_desc = fidl::VectorView(fidl::unowned_ptr(function_descs.data()), function_descs.size());
config_descs.push_back(std::move(config_desc));
ASSERT_NO_FATAL_FAILURES(SetupPeripheralDevice(std::move(device_desc), std::move(config_descs)));
fbl::unique_fd fd(openat(devmgr_.devfs_root().get(), "class/ethernet", O_RDONLY));
while (fdio_watch_directory(fd.get(), usb_virtual_bus::WaitForAnyFile, ZX_TIME_INFINITE,
dev_path) != ZX_ERR_STOP) {
}
*dev_path = fbl::String::Concat({fbl::String("class/ethernet/"), *dev_path});
fd.reset(openat(devmgr_.devfs_root().get(), "class/test-asix-function", O_RDONLY));
while (fdio_watch_directory(fd.get(), usb_virtual_bus::WaitForAnyFile, ZX_TIME_INFINITE,
test_function_path) != ZX_ERR_STOP) {
}
*test_function_path =
fbl::String::Concat({fbl::String("class/test-asix-function/"), *test_function_path});
}
class UsbAx88179Test : public zxtest::Test {
public:
void SetUp() override {
ASSERT_NO_FATAL_FAILURES(bus_.InitUsbAx88179(&dev_path_, &test_function_path_));
}
void TearDown() override {
ASSERT_NO_FATAL_FAILURES(bus_.ClearPeripheralDeviceFunctions());
auto result2 = bus_.virtual_bus().Disable();
ASSERT_NO_FATAL_FAILURES(usb_virtual_bus::ValidateResult(result2));
}
void ConnectEthernetClient() {
fbl::unique_fd fd(openat(bus_.GetRootFd(), dev_path_.c_str(), O_RDWR));
zx::channel ethernet_handle;
ASSERT_OK(fdio_get_service_handle(fd.release(), ethernet_handle.reset_and_get_address()));
ethernet_client_.reset(new ethernet::Device::SyncClient(std::move(ethernet_handle)));
// Get device information
auto get_info_result = ethernet_client_->GetInfo();
ASSERT_OK(get_info_result.status());
auto info = get_info_result.Unwrap()->info;
auto get_fifos_result = ethernet_client_->GetFifos();
ASSERT_OK(get_fifos_result.status());
auto fifos = get_fifos_result.Unwrap()->info.get();
// Calculate optimal size of VMO, and set up RX and TX buffers.
size_t optimal_vmo_size = (fifos->rx_depth * info.mtu) + (fifos->tx_depth * info.mtu);
zx::vmo vmo;
fzl::VmoMapper mapper;
ASSERT_OK(mapper.CreateAndMap(optimal_vmo_size, ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE,
nullptr, &vmo));
uint8_t* io_buffer = nullptr;
size_t io_buffer_size = 0;
io_buffer = static_cast<uint8_t*>(mapper.start());
io_buffer_size = optimal_vmo_size;
auto set_io_buffer_result = ethernet_client_->SetIOBuffer(std::move(vmo));
ASSERT_OK(set_io_buffer_result.status());
rx_fifo_ = std::move(fifos->rx);
tx_fifo_ = std::move(fifos->tx);
}
void StartDevice() {
auto start_result = ethernet_client_->Start();
ASSERT_OK(start_result.status());
}
void SetDeviceOnline() {
fbl::unique_fd fd(openat(bus_.GetRootFd(), test_function_path_.c_str(), O_RDWR));
zx::channel test_handle;
ASSERT_OK(fdio_get_service_handle(fd.release(), test_handle.reset_and_get_address()));
std::unique_ptr<ax88179::Hooks::SyncClient> test_client;
test_client.reset(new ax88179::Hooks::SyncClient(std::move(test_handle)));
// Ensure SIGNAL_STATUS is de-asserted before we set it.
ASSERT_OK(ethernet_client_->GetStatus().status());
auto online_result = test_client->SetOnline(true);
ASSERT_OK(online_result.status());
auto result = online_result.Unwrap()->status;
ASSERT_OK(result);
}
ethernet::DeviceStatus GetDeviceStatus() {
auto status_result = ethernet_client_->GetStatus();
ZX_ASSERT(status_result.status() == ZX_OK);
return status_result.Unwrap()->device_status;
}
void ExpectStatusOnline() {
// Two attempts, as SIGNAL_STATUS is triggered by both ethernet.cc starting
// up and also by our fake function driver. As we can't control the delivery
// order, confirm that we get to online eventually.
for (int tries = 0; tries < 2; ++tries) {
zx_signals_t pending;
ASSERT_OK(rx_fifo_.wait_one(ethernet::SIGNAL_STATUS, zx::time::infinite(), &pending));
ASSERT_EQ((pending & ethernet::SIGNAL_STATUS), ethernet::SIGNAL_STATUS);
if (GetDeviceStatus() & ethernet::DeviceStatus::ONLINE) {
return;
}
}
ADD_FATAL_FAILURE();
}
protected:
USBVirtualBus bus_;
fbl::String dev_path_;
fbl::String test_function_path_;
std::unique_ptr<ethernet::Device::SyncClient> ethernet_client_;
zx::fifo rx_fifo_;
zx::fifo tx_fifo_;
};
TEST_F(UsbAx88179Test, SetupShutdownTest) { ASSERT_NO_FATAL_FAILURES(); }
TEST_F(UsbAx88179Test, OfflineByDefault) {
ASSERT_NO_FATAL_FAILURES(ConnectEthernetClient());
ASSERT_NO_FATAL_FAILURES(StartDevice());
ASSERT_FALSE(GetDeviceStatus() & ethernet::DeviceStatus::ONLINE);
}
TEST_F(UsbAx88179Test, SetOnlineAfterStart) {
ASSERT_NO_FATAL_FAILURES(ConnectEthernetClient());
ASSERT_NO_FATAL_FAILURES(StartDevice());
ASSERT_NO_FATAL_FAILURES(SetDeviceOnline());
ASSERT_NO_FATAL_FAILURES(ExpectStatusOnline());
}
// This is for https://fxbug.dev/40786#c41.
TEST_F(UsbAx88179Test, SetOnlineBeforeStart) {
ASSERT_NO_FATAL_FAILURES(ConnectEthernetClient());
ASSERT_NO_FATAL_FAILURES(SetDeviceOnline());
ASSERT_NO_FATAL_FAILURES(StartDevice());
ASSERT_NO_FATAL_FAILURES(ExpectStatusOnline());
}
} // namespace
} // namespace usb_ax88179