blob: 66bf6204e8255ab01f85d14c3489a6b85335c8c0 [file] [log] [blame]
// Copyright 2019 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 "ethernet.h"
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/zx/process.h>
#include <memory>
#include <thread>
#include <zxtest/zxtest.h>
class FakeEthernetImplProtocol
: public ddk::Device<FakeEthernetImplProtocol, ddk::GetProtocolable>,
public ddk::EthernetImplProtocol<FakeEthernetImplProtocol, ddk::base_protocol> {
public:
FakeEthernetImplProtocol()
: ddk::Device<FakeEthernetImplProtocol, ddk::GetProtocolable>(fake_ddk::kFakeDevice),
proto_({&ethernet_impl_protocol_ops_, this}) {}
const ethernet_impl_protocol_t* proto() const { return &proto_; }
void DdkRelease() {}
zx_status_t EthernetImplQuery(uint32_t options, ethernet_info_t* info) {
info->netbuf_size = sizeof(ethernet_netbuf_t);
info->mtu = 1500;
memcpy(info->mac, mac_, sizeof(info->mac));
return ZX_OK;
}
void EthernetImplStop() {}
zx_status_t EthernetImplStart(const ethernet_ifc_protocol_t* ifc) {
client_ = std::make_unique<ddk::EthernetIfcProtocolClient>(ifc);
return ZX_OK;
}
void EthernetImplQueueTx(uint32_t options, ethernet_netbuf_t* netbuf,
ethernet_impl_queue_tx_callback completion_cb, void* cookie) {
queue_tx_called_ = true;
completion_cb(cookie, ZX_OK, netbuf);
}
zx_status_t EthernetImplSetParam(uint32_t param, int32_t value, const uint8_t* data,
size_t data_size) {
if (param == ETHERNET_SETPARAM_DUMP_REGS) {
dump_called_ = true;
}
if (param == ETHERNET_SETPARAM_PROMISC) {
promiscuous_ = value;
}
return ZX_OK;
}
void EthernetImplGetBti(zx::bti* bti) { bti->reset(); }
bool TestInfo(fuchsia_hardware_ethernet_Info* info) {
if (memcmp(mac_, info->mac.octets, ETH_MAC_SIZE) || (info->mtu != 1500)) {
return false;
}
return true;
}
bool TestDump() { return dump_called_; }
int32_t TestPromiscuous() { return promiscuous_; }
bool TestIfc() {
if (!client_)
return false;
// Use the provided client to test the ifc client.
client_->Status(0);
client_->Recv(nullptr, 0, 0);
return true;
}
bool SetStatus(uint32_t status) {
if (!client_)
return false;
client_->Status(status);
return true;
}
bool TestQueueTx() { return queue_tx_called_; }
bool TestRecv() {
if (!client_) {
return false;
}
uint8_t data = 0xAA;
client_->Recv(&data, 1, 0);
return true;
}
private:
ethernet_impl_protocol_t proto_;
const uint8_t mac_[ETH_MAC_SIZE] = {0xA, 0xB, 0xC, 0xD, 0xE, 0xF};
std::unique_ptr<ddk::EthernetIfcProtocolClient> client_;
bool dump_called_ = false;
int32_t promiscuous_ = -1;
bool queue_tx_called_ = false;
};
class EthernetTester : fake_ddk::Bind {
public:
EthernetTester() : fake_ddk::Bind() { SetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, ethernet_.proto()); }
fake_ddk::Bind& ddk() { return *this; }
FakeEthernetImplProtocol& ethmac() { return ethernet_; }
eth::EthDev0* eth0() { return eth0_; }
const std::vector<eth::EthDev*>& instances() { return instances_; }
protected:
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) override {
zx_status_t ret = Bind::DeviceAdd(drv, parent, args, out);
if (ret == ZX_OK) {
if (parent == fake_ddk::kFakeParent) {
eth0_ = static_cast<eth::EthDev0*>(args->ctx);
} else {
instances_.push_back(static_cast<eth::EthDev*>(args->ctx));
}
}
return ret;
}
private:
FakeEthernetImplProtocol ethernet_;
eth::EthDev0* eth0_;
std::vector<eth::EthDev*> instances_;
};
TEST(EthernetTest, BindTest) {
EthernetTester tester;
EXPECT_OK(eth::EthDev0::EthBind(nullptr, fake_ddk::kFakeParent), "Bind failed");
tester.eth0()->DdkRelease();
}
TEST(EthernetTest, DdkLifecycleTest) {
EthernetTester tester;
eth::EthDev0* eth(new eth::EthDev0(fake_ddk::kFakeParent));
EXPECT_OK(eth->AddDevice(), "AddDevice Failed");
eth->DdkAsyncRemove();
EXPECT_TRUE(tester.ddk().Ok());
eth->DdkRelease();
}
TEST(EthernetTest, OpenTest) {
EthernetTester tester;
eth::EthDev0* eth(new eth::EthDev0(fake_ddk::kFakeParent));
EXPECT_OK(eth->AddDevice(), "AddDevice Failed");
zx_device_t* eth_instance;
EXPECT_OK(eth->DdkOpen(&eth_instance, 0), "Open Failed");
eth->DdkAsyncRemove();
EXPECT_OK(tester.ddk().WaitUntilRemove());
eth->DdkRelease();
tester.instances()[0]->DdkRelease();
}
class EthDev0ForTest : public eth::EthDev0 {
public:
EthDev0ForTest(zx_device_t* parent) : eth::EthDev0(parent) {}
using eth::EthDev0::DestroyAllEthDev;
};
class EthernetDeviceTest {
public:
EthernetDeviceTest() : tester() {
edev0 = std::make_unique<EthDev0ForTest>(fake_ddk::kFakeParent);
ASSERT_OK(edev0->AddDevice());
edev = fbl::MakeRefCounted<eth::EthDev>(fake_ddk::kFakeParent, edev0.get());
zx_device_t* out;
ASSERT_OK(edev->AddDevice(&out));
}
~EthernetDeviceTest() { edev0->DestroyAllEthDev(); }
void Start() {
zx_status_t out_status = ZX_ERR_INTERNAL;
fuchsia_hardware_ethernet_Fifos fifos = {};
ASSERT_OK(fuchsia_hardware_ethernet_DeviceGetFifos(FidlChannel(), &out_status, &fifos));
tx_fifo_ = zx::fifo(fifos.tx);
EXPECT_TRUE(tx_fifo_.is_valid());
rx_fifo_ = zx::fifo(fifos.rx);
rx_fifo_depth_ = fifos.rx_depth;
tx_fifo_depth_ = fifos.tx_depth;
EXPECT_TRUE(rx_fifo_.is_valid());
ASSERT_OK(zx::vmo::create(2 * sizeof(ethernet_netbuf_t), 0, &buf_));
ASSERT_OK(fuchsia_hardware_ethernet_DeviceSetIOBuffer(FidlChannel(), buf_.get(), &out_status));
ASSERT_OK(out_status);
ASSERT_OK(fuchsia_hardware_ethernet_DeviceStart(FidlChannel(), &out_status));
ASSERT_OK(out_status);
}
zx_handle_t FidlChannel() { return tester.ddk().FidlClient().get(); }
zx::fifo& TransmitFifo() { return tx_fifo_; }
zx::fifo& ReceiveFifo() { return rx_fifo_; }
EthernetTester tester;
std::unique_ptr<EthDev0ForTest> edev0;
fbl::RefPtr<eth::EthDev> edev;
private:
zx::fifo tx_fifo_;
zx::fifo rx_fifo_;
uint32_t rx_fifo_depth_;
uint32_t tx_fifo_depth_;
zx::vmo buf_;
};
TEST(EthernetTest, MultipleOpenTest) {
EthernetDeviceTest test;
EXPECT_OK(test.edev->DdkOpen(nullptr, 0), "Instance 1 open failed");
EXPECT_OK(test.edev->DdkOpen(nullptr, 0), "Instance 2 open failed");
EXPECT_OK(test.edev->DdkClose(0), "Instance 0 close failed");
EXPECT_OK(test.edev->DdkClose(0), "Instance 1 close failed");
EXPECT_OK(test.edev->DdkClose(0), "Instance 2 close failed");
}
TEST(EthernetTest, SetClientNameTest) {
EthernetDeviceTest test;
zx_status_t call_status = ZX_OK;
ASSERT_OK(fuchsia_hardware_ethernet_DeviceSetClientName(test.FidlChannel(), "ethtest",
strlen("ethtest"), &call_status));
ASSERT_OK(call_status);
}
TEST(EthernetTest, GetInfoTest) {
EthernetDeviceTest test;
fuchsia_hardware_ethernet_Info info = {};
ASSERT_OK(fuchsia_hardware_ethernet_DeviceGetInfo(test.FidlChannel(), &info));
EXPECT_TRUE(test.tester.ethmac().TestInfo(&info));
}
TEST(EthernetTest, GetFifosTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
fuchsia_hardware_ethernet_Fifos fifos = {};
ASSERT_OK(fuchsia_hardware_ethernet_DeviceGetFifos(test.FidlChannel(), &out_status, &fifos));
ASSERT_OK(out_status);
EXPECT_TRUE(fifos.rx != ZX_HANDLE_INVALID);
EXPECT_TRUE(fifos.tx != ZX_HANDLE_INVALID);
}
TEST(EthernetTest, AddDeviceAsNotPromiscuous) {
EthernetDeviceTest test;
EXPECT_EQ(test.tester.ethmac().TestPromiscuous(), 0, "");
}
TEST(EthernetTest, SetPromiscuousModeTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
ASSERT_OK(
fuchsia_hardware_ethernet_DeviceSetPromiscuousMode(test.FidlChannel(), true, &out_status));
ASSERT_OK(out_status);
EXPECT_EQ(test.tester.ethmac().TestPromiscuous(), 1, "");
out_status = ZX_ERR_INTERNAL;
ASSERT_OK(
fuchsia_hardware_ethernet_DeviceSetPromiscuousMode(test.FidlChannel(), false, &out_status));
ASSERT_OK(out_status);
EXPECT_EQ(test.tester.ethmac().TestPromiscuous(), 0, "");
}
TEST(EthernetTest, ConfigMulticastAddMacTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
/* 1st bit should be 1 in multicast */
fuchsia_hardware_ethernet_MacAddress wrong_addr = {
.octets = {0x00, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc}};
ASSERT_OK(fuchsia_hardware_ethernet_DeviceConfigMulticastAddMac(test.FidlChannel(), &wrong_addr,
&out_status));
ASSERT_OK(!out_status);
fuchsia_hardware_ethernet_MacAddress right_addr = {
.octets = {0x01, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc}};
ASSERT_OK(fuchsia_hardware_ethernet_DeviceConfigMulticastAddMac(test.FidlChannel(), &right_addr,
&out_status));
ASSERT_OK(out_status);
}
TEST(EthernetTest, ConfigMulticastDeleteMacTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
fuchsia_hardware_ethernet_MacAddress addr = {.octets = {0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc}};
ASSERT_OK(fuchsia_hardware_ethernet_DeviceConfigMulticastDeleteMac(test.FidlChannel(), &addr,
&out_status));
ASSERT_OK(out_status);
}
TEST(EthernetTest, ConfigMulticastSetPromiscuousModeTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
ASSERT_OK(fuchsia_hardware_ethernet_DeviceConfigMulticastSetPromiscuousMode(test.FidlChannel(),
true, &out_status));
ASSERT_OK(out_status);
out_status = ZX_ERR_INTERNAL;
ASSERT_OK(fuchsia_hardware_ethernet_DeviceConfigMulticastSetPromiscuousMode(test.FidlChannel(),
false, &out_status));
ASSERT_OK(out_status);
}
TEST(EthernetTest, ConfigMulticastTestFilterTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
ASSERT_OK(
fuchsia_hardware_ethernet_DeviceConfigMulticastTestFilter(test.FidlChannel(), &out_status));
ASSERT_OK(out_status);
}
TEST(EthernetTest, DumpRegistersTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
ASSERT_OK(fuchsia_hardware_ethernet_DeviceDumpRegisters(test.FidlChannel(), &out_status));
ASSERT_OK(out_status);
EXPECT_TRUE(test.tester.ethmac().TestDump());
}
TEST(EthernetTest, SetIOBufferTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
EXPECT_TRUE(fuchsia_hardware_ethernet_DeviceSetIOBuffer(test.FidlChannel(), ZX_HANDLE_INVALID,
&out_status) != ZX_OK);
EXPECT_TRUE(out_status != ZX_OK);
zx::vmo buf;
ASSERT_OK(zx::vmo::create(2 * sizeof(ethernet_netbuf_t), 0, &buf));
ASSERT_OK(
fuchsia_hardware_ethernet_DeviceSetIOBuffer(test.FidlChannel(), buf.get(), &out_status));
ASSERT_OK(out_status);
}
TEST(EthernetTest, StartTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_OK;
// test bad state
ASSERT_OK(fuchsia_hardware_ethernet_DeviceStart(test.FidlChannel(), &out_status));
EXPECT_TRUE(out_status != ZX_OK);
// test valid case
test.Start();
// test client interfaces
EXPECT_TRUE(test.tester.ethmac().TestIfc());
}
TEST(EthernetTest, GetStatusTest) {
EthernetDeviceTest test;
uint32_t device_status;
// start device
test.Start();
// set mock ethmac status
EXPECT_TRUE(test.tester.ethmac().SetStatus(1));
// verify status
ASSERT_OK(fuchsia_hardware_ethernet_DeviceGetStatus(test.FidlChannel(), &device_status));
EXPECT_TRUE(device_status == 1);
}
TEST(EthernetTest, SendTest) {
EthernetDeviceTest test;
// start device
test.Start();
// send packet through the fifo
zx::fifo& tx = test.TransmitFifo();
eth_fifo_entry_t entry = {
.offset = 0,
.length = 1,
.flags = 0,
.cookie = 0,
};
ASSERT_OK(tx.write(sizeof(entry), &entry, 1, nullptr));
// wait for packet to be returned
ASSERT_OK(tx.wait_one(ZX_FIFO_READABLE, zx::time::infinite(), nullptr));
// TODO(fxbug.dev/21334): remove debug logs after flake fix is confirmed
printf("SendTest: Transmit wait completed\n");
ASSERT_OK(tx.read(sizeof(entry), &entry, 1, nullptr));
// check mock ethmac if packet was received
EXPECT_TRUE(test.tester.ethmac().TestQueueTx());
}
TEST(EthernetTest, ReceiveTest) {
EthernetDeviceTest test;
// start device
test.Start();
// Queue buffer to receive fifo
zx::fifo& rx = test.ReceiveFifo();
eth_fifo_entry_t entry = {
.offset = 0,
.length = 1,
.flags = 0,
.cookie = 0,
};
ASSERT_OK(rx.write(sizeof(entry), &entry, 1, nullptr));
// send packet through mock ethmac
EXPECT_TRUE(test.tester.ethmac().TestRecv());
// check if packet is received
ASSERT_OK(rx.wait_one(ZX_FIFO_READABLE, zx::time::infinite(), nullptr));
// TODO(fxbug.dev/21334): remove debug logs after flake fix is confirmed
printf("ReceiveTest: Receive wait completed\n");
ASSERT_OK(rx.read(sizeof(entry), &entry, 1, nullptr));
}
TEST(EthernetTest, ListenStartTest) {
EthernetDeviceTest test;
zx_status_t out_status = ZX_ERR_INTERNAL;
// start device
test.Start();
// Set listen start
ASSERT_OK(fuchsia_hardware_ethernet_DeviceListenStart(test.FidlChannel(), &out_status));
ASSERT_OK(out_status);
// send packet
eth_fifo_entry_t entry = {
.offset = 0,
.length = 1,
.flags = 0,
.cookie = 0,
};
zx::fifo& rx = test.ReceiveFifo();
ASSERT_OK(rx.write(sizeof(entry), &entry, 1, nullptr));
zx::fifo& tx = test.TransmitFifo();
ASSERT_OK(tx.write(sizeof(entry), &entry, 1, nullptr));
// wait for the send to complete
ASSERT_OK(tx.wait_one(ZX_FIFO_READABLE, zx::time::infinite(), nullptr));
// TODO(fxbug.dev/21334): remove debug logs after flake fix is confirmed
printf("ListenStartTest: Transmit wait completed\n");
ASSERT_OK(tx.read(sizeof(entry), &entry, 1, nullptr));
// check mock ethmac if packet was received
EXPECT_TRUE(test.tester.ethmac().TestQueueTx());
// check if it was echoed
ASSERT_OK(rx.wait_one(ZX_FIFO_READABLE, zx::time::infinite(), nullptr));
// TODO(fxbug.dev/21334): remove debug logs after flake fix is confirmed
printf("ListenStartTest: Receive wait completed\n");
ASSERT_OK(rx.read(sizeof(entry), &entry, 1, nullptr));
}
TEST(EthernetTest, ListenStopTest) {
EthernetDeviceTest test;
ASSERT_OK(fuchsia_hardware_ethernet_DeviceListenStop(test.FidlChannel()));
}
TEST(EthernetTest, StopTest) {
EthernetDeviceTest test;
test.Start();
ASSERT_OK(fuchsia_hardware_ethernet_DeviceStop(test.FidlChannel()));
}