| // 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 <memory> |
| |
| #include <ddktl/device.h> |
| #include <ddktl/protocol/ethernet.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 void* 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 {ðernet_ifc_protocol_ops_, this}; } |
| |
| zx_status_t StartProtocol(ddk::EthernetImplProtocolClient* client) { |
| return client->Start(this, ðernet_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 = ðernet_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 void* 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_FAILURES(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_FAILURES(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_FAILURES(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_FAILURES(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_FAILURES(ifc_dev.VerifyCalls()); |
| } |
| |
| } // namespace |