blob: 1817e8754e347591b14f9474a51b87e3f9b1c52c [file] [log] [blame]
// Copyright 2017 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 <fuchsia/hardware/ethernet/cpp/banjo.h>
#include <memory>
#include <ddktl/device.h>
#include <zxtest/zxtest.h>
namespace {
// These tests are testing interfaces that get included via multiple inheritance, and thus we must
// make sure we get all the casts correct. We record the value of the "this" pointer in the
// constructor, and then verify in each call the "this" pointer was the same as the original. (The
// typical way for this to go wrong is to take a EthernetIfc<D>* instead of a D* in a function
// signature.)
#define get_this() reinterpret_cast<uintptr_t>(this)
class TestEthernetIfc : public ddk::Device<TestEthernetIfc>,
public ddk::EthernetIfcProtocol<TestEthernetIfc> {
public:
TestEthernetIfc() : ddk::Device<TestEthernetIfc>(nullptr) { this_ = get_this(); }
void DdkRelease() {}
void EthernetIfcStatus(uint32_t status) {
status_this_ = get_this();
status_called_ = true;
}
void EthernetIfcRecv(const uint8_t* data, size_t length, uint32_t flags) {
recv_this_ = get_this();
recv_called_ = true;
}
void VerifyCalls() const {
EXPECT_EQ(this_, status_this_, "");
EXPECT_EQ(this_, recv_this_, "");
EXPECT_TRUE(status_called_, "");
EXPECT_TRUE(recv_called_, "");
}
ethernet_ifc_protocol_t ethernet_ifc() { return {&ethernet_ifc_protocol_ops_, this}; }
zx_status_t StartProtocol(ddk::EthernetImplProtocolClient* client) {
return client->Start(this, &ethernet_ifc_protocol_ops_);
}
private:
uintptr_t this_ = 0u;
uintptr_t status_this_ = 0u;
uintptr_t recv_this_ = 0u;
bool status_called_ = false;
bool recv_called_ = false;
};
class TestEthernetImplProtocol
: public ddk::Device<TestEthernetImplProtocol, ddk::GetProtocolable>,
public ddk::EthernetImplProtocol<TestEthernetImplProtocol, ddk::base_protocol> {
public:
TestEthernetImplProtocol()
: ddk::Device<TestEthernetImplProtocol, ddk::GetProtocolable>(nullptr) {
this_ = get_this();
}
zx_status_t DdkGetProtocol(uint32_t proto_id, void* out) {
if (proto_id != ZX_PROTOCOL_ETHERNET_IMPL)
return ZX_ERR_INVALID_ARGS;
ddk::AnyProtocol* proto = static_cast<ddk::AnyProtocol*>(out);
proto->ops = &ethernet_impl_protocol_ops_;
proto->ctx = this;
return ZX_OK;
}
void DdkRelease() {}
zx_status_t EthernetImplQuery(uint32_t options, ethernet_info_t* info) {
query_this_ = get_this();
query_called_ = true;
return ZX_OK;
}
void EthernetImplStop() {
stop_this_ = get_this();
stop_called_ = true;
}
zx_status_t EthernetImplStart(const ethernet_ifc_protocol_t* ifc) {
start_this_ = get_this();
client_ = std::make_unique<ddk::EthernetIfcProtocolClient>(ifc);
start_called_ = true;
return ZX_OK;
}
void EthernetImplQueueTx(uint32_t options, ethernet_netbuf_t* netbuf,
ethernet_impl_queue_tx_callback completion_cb, void* cookie) {
queue_tx_this_ = get_this();
queue_tx_called_ = true;
}
zx_status_t EthernetImplSetParam(uint32_t param, int32_t value, const uint8_t* data,
size_t data_size) {
set_param_this_ = get_this();
set_param_called_ = true;
return ZX_OK;
}
void EthernetImplGetBti(zx::bti* bti) { bti->reset(); }
void VerifyCalls() const {
EXPECT_EQ(this_, query_this_, "");
EXPECT_EQ(this_, start_this_, "");
EXPECT_EQ(this_, stop_this_, "");
EXPECT_EQ(this_, queue_tx_this_, "");
EXPECT_EQ(this_, set_param_this_, "");
EXPECT_TRUE(query_called_, "");
EXPECT_TRUE(start_called_, "");
EXPECT_TRUE(stop_called_, "");
EXPECT_TRUE(queue_tx_called_, "");
EXPECT_TRUE(set_param_called_, "");
}
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;
}
private:
uintptr_t this_ = 0u;
uintptr_t query_this_ = 0u;
uintptr_t stop_this_ = 0u;
uintptr_t start_this_ = 0u;
uintptr_t queue_tx_this_ = 0u;
uintptr_t set_param_this_ = 0u;
bool query_called_ = false;
bool stop_called_ = false;
bool start_called_ = false;
bool queue_tx_called_ = false;
bool set_param_called_ = false;
std::unique_ptr<ddk::EthernetIfcProtocolClient> client_;
};
TEST(DdktlEthernet, EthernetIfc) {
TestEthernetIfc dev;
auto ifc = dev.ethernet_ifc();
ethernet_ifc_status(&ifc, 0);
ethernet_ifc_recv(&ifc, nullptr, 0, 0);
ASSERT_NO_FATAL_FAILURE(dev.VerifyCalls());
}
TEST(DdktlEthernet, EthernetIfcClient) {
TestEthernetIfc dev;
const ethernet_ifc_protocol_t ifc = dev.ethernet_ifc();
ddk::EthernetIfcProtocolClient client(&ifc);
client.Status(0);
client.Recv(nullptr, 0, 0);
ASSERT_NO_FATAL_FAILURE(dev.VerifyCalls());
}
TEST(DdktlEthernet, EthernetImplProtocol) {
TestEthernetImplProtocol dev;
// Normally we would use device_op_get_protocol, but we haven't added the device to devmgr so
// its ops table is currently invalid.
ethernet_impl_protocol_t proto;
auto status = dev.DdkGetProtocol(0, reinterpret_cast<void*>(&proto));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status, "");
status = dev.DdkGetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, reinterpret_cast<void*>(&proto));
EXPECT_EQ(ZX_OK, status, "");
EXPECT_EQ(ZX_OK, ethernet_impl_query(&proto, 0, nullptr), "");
proto.ops->stop(proto.ctx);
ethernet_ifc_protocol_t ifc = {nullptr, nullptr};
EXPECT_EQ(ZX_OK, ethernet_impl_start(&proto, ifc.ctx, ifc.ops), "");
ethernet_netbuf_t netbuf = {};
ethernet_impl_queue_tx(&proto, 0, &netbuf, nullptr, nullptr);
EXPECT_EQ(ZX_OK, ethernet_impl_set_param(&proto, 0, 0, nullptr, 0), "");
ASSERT_NO_FATAL_FAILURE(dev.VerifyCalls());
}
TEST(DdktlEthernet, EthernetImplProtocolClient) {
// The EthernetImplProtocol device to wrap. This would live in the parent device
// our driver was binding to.
TestEthernetImplProtocol protocol_dev;
ethernet_impl_protocol_t proto;
auto status =
protocol_dev.DdkGetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, reinterpret_cast<void*>(&proto));
EXPECT_EQ(ZX_OK, status, "");
// The client device to wrap the ops + device that represent the parent
// device.
ddk::EthernetImplProtocolClient client(&proto);
// The EthernetIfc to hand to the parent device.
TestEthernetIfc ifc_dev;
ethernet_ifc_protocol_t ifc = ifc_dev.ethernet_ifc();
EXPECT_EQ(ZX_OK, client.Query(0, nullptr), "");
client.Stop();
EXPECT_EQ(ZX_OK, client.Start(ifc.ctx, ifc.ops), "");
ethernet_netbuf_t netbuf = {};
client.QueueTx(0, &netbuf, nullptr, nullptr);
EXPECT_EQ(ZX_OK, client.SetParam(0, 0, nullptr, 0));
ASSERT_NO_FATAL_FAILURE(protocol_dev.VerifyCalls());
}
TEST(DdktlEthernet, EthernetImplProtocolIfcClient) {
// We create a protocol device that we will start from an ifc device. The protocol device will
// then use the pointer passed to it to call methods on the ifc device. This ensures the void*
// casting is correct.
TestEthernetImplProtocol protocol_dev;
ethernet_impl_protocol_t proto;
auto status =
protocol_dev.DdkGetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, reinterpret_cast<void*>(&proto));
EXPECT_EQ(ZX_OK, status, "");
ddk::EthernetImplProtocolClient client(&proto);
TestEthernetIfc ifc_dev;
EXPECT_EQ(ZX_OK, ifc_dev.StartProtocol(&client), "");
// Execute the EthernetIfc methods
ASSERT_TRUE(protocol_dev.TestIfc(), "");
// Verify that they were called
ASSERT_NO_FATAL_FAILURE(ifc_dev.VerifyCalls());
}
} // namespace