1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <ddktl/device.h>
6#include <ddktl/protocol/ethernet.h>
7#include <fbl/alloc_checker.h>
8#include <fbl/unique_ptr.h>
9#include <unittest/unittest.h>
10
11namespace {
12
13// These tests are testing interfaces that get included via multiple inheritance, and thus we must
14// make sure we get all the casts correct. We record the value of the "this" pointer in the
15// constructor, and then verify in each call the "this" pointer was the same as the original. (The
16// typical way for this to go wrong is to take a EthmacIfc<D>* instead of a D* in a function
17// signature.)
18#define get_this() reinterpret_cast<uintptr_t>(this)
19
20class TestEthmacIfc : public ddk::Device<TestEthmacIfc>,
21                      public ddk::EthmacIfc<TestEthmacIfc> {
22  public:
23    TestEthmacIfc() : ddk::Device<TestEthmacIfc>(nullptr) {
24        this_ = get_this();
25    }
26
27    void DdkRelease() {}
28
29    void EthmacStatus(uint32_t status) {
30        status_this_ = get_this();
31        status_called_ = true;
32    }
33
34    void EthmacRecv(void* data, size_t length, uint32_t flags) {
35        recv_this_ = get_this();
36        recv_called_ = true;
37    }
38
39    void EthmacCompleteTx(ethmac_netbuf_t* netbuf, zx_status_t status) {
40        complete_tx_this_ = get_this();
41        complete_tx_called_ = true;
42    }
43
44    bool VerifyCalls() const {
45        BEGIN_HELPER;
46        EXPECT_EQ(this_, status_this_, "");
47        EXPECT_EQ(this_, recv_this_, "");
48        EXPECT_EQ(this_, complete_tx_this_, "");
49        EXPECT_TRUE(status_called_, "");
50        EXPECT_TRUE(recv_called_, "");
51        EXPECT_TRUE(complete_tx_called_, "");
52        END_HELPER;
53    }
54
55    zx_status_t StartProtocol(ddk::EthmacProtocolProxy* proxy) {
56        return proxy->Start(this);
57    }
58
59  private:
60    uintptr_t this_ = 0u;
61    uintptr_t status_this_ = 0u;
62    uintptr_t recv_this_ = 0u;
63    uintptr_t complete_tx_this_ = 0u;
64    bool status_called_ = false;
65    bool recv_called_ = false;
66    bool complete_tx_called_ = false;
67};
68
69class TestEthmacProtocol : public ddk::Device<TestEthmacProtocol, ddk::GetProtocolable>,
70                           public ddk::EthmacProtocol<TestEthmacProtocol> {
71  public:
72    TestEthmacProtocol()
73      : ddk::Device<TestEthmacProtocol, ddk::GetProtocolable>(nullptr) {
74        this_ = get_this();
75    }
76
77    zx_status_t DdkGetProtocol(uint32_t proto_id, void* out) {
78        if (proto_id != ZX_PROTOCOL_ETHERNET_IMPL) return ZX_ERR_INVALID_ARGS;
79        ddk::AnyProtocol* proto = static_cast<ddk::AnyProtocol*>(out);
80        proto->ops = ddk_proto_ops_;
81        proto->ctx = this;
82        return ZX_OK;
83    }
84
85    void DdkRelease() {}
86
87    zx_status_t EthmacQuery(uint32_t options, ethmac_info_t* info) {
88        query_this_ = get_this();
89        query_called_ = true;
90        return ZX_OK;
91    }
92
93    void EthmacStop() {
94        stop_this_ = get_this();
95        stop_called_ = true;
96    }
97
98    zx_status_t EthmacStart(fbl::unique_ptr<ddk::EthmacIfcProxy> proxy) {
99        start_this_ = get_this();
100        proxy_.swap(proxy);
101        start_called_ = true;
102        return ZX_OK;
103    }
104
105    zx_status_t EthmacQueueTx(uint32_t options, ethmac_netbuf_t* netbuf) {
106        queue_tx_this_ = get_this();
107        queue_tx_called_ = true;
108        return ZX_OK;
109    }
110
111    zx_status_t EthmacSetParam(uint32_t param, int32_t value, void* data) {
112        set_param_this_ = get_this();
113        set_param_called_ = true;
114        return ZX_OK;
115    }
116    zx_handle_t EthmacGetBti() { return ZX_HANDLE_INVALID;}
117
118
119    bool VerifyCalls() const {
120        BEGIN_HELPER;
121        EXPECT_EQ(this_, query_this_, "");
122        EXPECT_EQ(this_, start_this_, "");
123        EXPECT_EQ(this_, stop_this_, "");
124        EXPECT_EQ(this_, queue_tx_this_, "");
125        EXPECT_EQ(this_, set_param_this_, "");
126        EXPECT_TRUE(query_called_, "");
127        EXPECT_TRUE(start_called_, "");
128        EXPECT_TRUE(stop_called_, "");
129        EXPECT_TRUE(queue_tx_called_, "");
130        EXPECT_TRUE(set_param_called_, "");
131        END_HELPER;
132    }
133
134    bool TestIfc() {
135        if (!proxy_) return false;
136        // Use the provided proxy to test the ifc proxy.
137        proxy_->Status(0);
138        proxy_->Recv(nullptr, 0, 0);
139        proxy_->CompleteTx(nullptr, ZX_OK);
140        return true;
141    }
142
143  private:
144    uintptr_t this_ = 0u;
145    uintptr_t query_this_ = 0u;
146    uintptr_t stop_this_ = 0u;
147    uintptr_t start_this_ = 0u;
148    uintptr_t queue_tx_this_ = 0u;
149    uintptr_t set_param_this_ = 0u;
150    bool query_called_ = false;
151    bool stop_called_ = false;
152    bool start_called_ = false;
153    bool queue_tx_called_ = false;
154    bool set_param_called_ = false;
155
156    fbl::unique_ptr<ddk::EthmacIfcProxy> proxy_;
157};
158
159static bool test_ethmac_ifc() {
160    BEGIN_TEST;
161
162    TestEthmacIfc dev;
163
164    auto ifc = dev.ethmac_ifc();
165    ifc->status(&dev, 0);
166    ifc->recv(&dev, nullptr, 0, 0);
167    ifc->complete_tx(&dev, nullptr, ZX_OK);
168
169    EXPECT_TRUE(dev.VerifyCalls(), "");
170
171    END_TEST;
172}
173
174static bool test_ethmac_ifc_proxy() {
175    BEGIN_TEST;
176
177    TestEthmacIfc dev;
178    ddk::EthmacIfcProxy proxy(dev.ethmac_ifc(), &dev);
179
180    proxy.Status(0);
181    proxy.Recv(nullptr, 0, 0);
182    proxy.CompleteTx(nullptr, ZX_OK);
183
184    EXPECT_TRUE(dev.VerifyCalls(), "");
185
186    END_TEST;
187}
188
189static bool test_ethmac_protocol() {
190    BEGIN_TEST;
191
192    TestEthmacProtocol dev;
193
194    // Normally we would use device_op_get_protocol, but we haven't added the device to devmgr so
195    // its ops table is currently invalid.
196    ethmac_protocol_t proto;
197    auto status = dev.DdkGetProtocol(0, reinterpret_cast<void*>(&proto));
198    EXPECT_EQ(ZX_ERR_INVALID_ARGS, status, "");
199
200    status = dev.DdkGetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, reinterpret_cast<void*>(&proto));
201    EXPECT_EQ(ZX_OK, status, "");
202
203    EXPECT_EQ(ZX_OK, proto.ops->query(proto.ctx, 0, nullptr), "");
204    proto.ops->stop(proto.ctx);
205    EXPECT_EQ(ZX_OK, proto.ops->start(proto.ctx, nullptr, nullptr), "");
206    ethmac_netbuf_t netbuf = {};
207    EXPECT_EQ(ZX_OK, proto.ops->queue_tx(proto.ctx, 0, &netbuf), "");
208    EXPECT_EQ(ZX_OK, proto.ops->set_param(proto.ctx, 0, 0, nullptr), "");
209
210    EXPECT_TRUE(dev.VerifyCalls(), "");
211
212    END_TEST;
213}
214
215static bool test_ethmac_protocol_proxy() {
216    BEGIN_TEST;
217
218    // The EthmacProtocol device to wrap. This would live in the parent device
219    // our driver was binding to.
220    TestEthmacProtocol protocol_dev;
221
222    ethmac_protocol_t proto;
223    auto status = protocol_dev.DdkGetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, reinterpret_cast<void*>(&proto));
224    EXPECT_EQ(ZX_OK, status, "");
225    // The proxy device to wrap the ops + device that represent the parent
226    // device.
227    ddk::EthmacProtocolProxy proxy(&proto);
228    // The EthmacIfc to hand to the parent device.
229    TestEthmacIfc ifc_dev;
230
231    EXPECT_EQ(ZX_OK, proxy.Query(0, nullptr), "");
232    proxy.Stop();
233    EXPECT_EQ(ZX_OK, proxy.Start(&ifc_dev), "");
234    ethmac_netbuf_t netbuf = {};
235    EXPECT_EQ(ZX_OK, proxy.QueueTx(0, &netbuf), "");
236    EXPECT_EQ(ZX_OK, proxy.SetParam(0, 0, nullptr));
237
238    EXPECT_TRUE(protocol_dev.VerifyCalls(), "");
239
240    END_TEST;
241}
242
243static bool test_ethmac_protocol_ifc_proxy() {
244    BEGIN_TEST;
245
246    // We create a protocol device that we will start from an ifc device. The protocol device will
247    // then use the pointer passed to it to call methods on the ifc device. This ensures the void*
248    // casting is correct.
249    TestEthmacProtocol protocol_dev;
250
251    ethmac_protocol_t proto;
252    auto status = protocol_dev.DdkGetProtocol(ZX_PROTOCOL_ETHERNET_IMPL, reinterpret_cast<void*>(&proto));
253    EXPECT_EQ(ZX_OK, status, "");
254
255    ddk::EthmacProtocolProxy proxy(&proto);
256    TestEthmacIfc ifc_dev;
257    EXPECT_EQ(ZX_OK, ifc_dev.StartProtocol(&proxy), "");
258
259    // Execute the EthmacIfc methods
260    ASSERT_TRUE(protocol_dev.TestIfc(), "");
261    // Verify that they were called
262    EXPECT_TRUE(ifc_dev.VerifyCalls(), "");
263
264    END_TEST;
265}
266
267
268}  // namespace
269
270BEGIN_TEST_CASE(ddktl_ethernet_device)
271RUN_NAMED_TEST("ddk::EthmacIfc", test_ethmac_ifc);
272RUN_NAMED_TEST("ddk::EthmacIfcProxy", test_ethmac_ifc_proxy);
273RUN_NAMED_TEST("ddk::EthmacProtocol", test_ethmac_protocol);
274RUN_NAMED_TEST("ddk::EthmacProtocolProxy", test_ethmac_protocol_proxy);
275RUN_NAMED_TEST("EthmacProtocol using EthmacIfcProxy", test_ethmac_protocol_ifc_proxy);
276END_TEST_CASE(ddktl_ethernet_device)
277
278test_case_element* test_case_ddktl_ethernet_device = TEST_CASE_ELEMENT(ddktl_ethernet_device);
279