blob: fd30391989e1887f3e0d308794caa616129df9cc [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 <ddk/metadata.h>
#include <ddk/metadata/spi.h>
#include <ddktl/protocol/platform/bus.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/spi/spi.h>
#include <zxtest/zxtest.h>
#include "spi.h"
namespace spi {
class FakeDdkSpiImpl;
using DeviceType = ddk::Device<FakeDdkSpiImpl, ddk::Unbindable>;
class FakeDdkSpiImpl : public fake_ddk::Bind,
public DeviceType,
public ddk::SpiImplProtocol<FakeDdkSpiImpl, ddk::base_protocol> {
public:
explicit FakeDdkSpiImpl() : DeviceType(fake_ddk::kFakeParent) {
fbl::AllocChecker ac;
fbl::Array<fake_ddk::ProtocolEntry> protocols(new (&ac) fake_ddk::ProtocolEntry[1](), 1);
ASSERT_TRUE(ac.check());
protocols[0] = {ZX_PROTOCOL_SPI_IMPL, {&spi_impl_protocol_ops_, this}};
SetProtocols(std::move(protocols));
}
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) {
if (parent == fake_ddk::kFakeParent) {
bus_device_ = reinterpret_cast<SpiDevice*>(args->ctx);
} else if (parent == reinterpret_cast<zx_device_t*>(bus_device_)) {
children_.push_back(reinterpret_cast<SpiChild*>(args->ctx));
auto fidl = new fake_ddk::FidlMessenger;
const zx_protocol_device_t* ops = reinterpret_cast<const zx_protocol_device_t*>(args->ops);
fidl->SetMessageOp(args->ctx, ops->message);
fidl_clients_.push_back(fidl);
}
*out = reinterpret_cast<zx_device_t*>(args->ctx);
return ZX_OK;
}
zx_status_t DeviceRemove(zx_device_t* device) {
if (device == reinterpret_cast<zx_device_t*>(bus_device_)) {
bus_device_ = nullptr;
return ZX_OK;
} else {
for (size_t i = 0; i < children_.size(); i++) {
if (children_[i] == reinterpret_cast<SpiChild*>(device)) {
children_.erase(i);
delete fidl_clients_[i];
fidl_clients_.erase(i);
return ZX_OK;
}
}
}
return ZX_ERR_BAD_STATE;
}
zx_status_t DeviceGetMetadata(zx_device_t* dev, uint32_t type, void* buf, size_t buflen,
size_t* actual) {
switch (type) {
case DEVICE_METADATA_SPI_CHANNELS:
memcpy(buf, &spi_channels_, sizeof spi_channels_);
*actual = sizeof spi_channels_;
break;
case DEVICE_METADATA_PRIVATE:
memcpy(buf, &kTestBusId, sizeof kTestBusId);
*actual = sizeof kTestBusId;
break;
default:
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t DeviceGetMetadataSize(zx_device_t* dev, uint32_t type, size_t* out_size) {
switch (type) {
case DEVICE_METADATA_SPI_CHANNELS:
*out_size = sizeof spi_channels_;
break;
case DEVICE_METADATA_PRIVATE:
*out_size = sizeof kTestBusId;
break;
default:
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
uint32_t SpiImplGetChipSelectCount() { return 2; }
zx_status_t SpiImplExchange(uint32_t cs, const uint8_t* txdata, size_t txdata_size,
uint8_t* out_rxdata, size_t rxdata_size, size_t* out_rxdata_actual) {
EXPECT_EQ(cs, current_test_cs_, "");
switch (test_mode_) {
case SpiTestMode::kTransmit:
EXPECT_NE(txdata, nullptr, "");
EXPECT_NE(txdata_size, 0, "");
EXPECT_EQ(out_rxdata, nullptr, "");
EXPECT_EQ(rxdata_size, 0, "");
break;
case SpiTestMode::kReceive:
EXPECT_EQ(txdata, nullptr, "");
EXPECT_EQ(txdata_size, 0, "");
EXPECT_NE(out_rxdata, nullptr, "");
EXPECT_NE(rxdata_size, 0, "");
memset(out_rxdata, 0, rxdata_size);
*out_rxdata_actual = rxdata_size;
break;
case SpiTestMode::kExchange:
EXPECT_NE(txdata, nullptr, "");
EXPECT_NE(txdata_size, 0, "");
EXPECT_NE(out_rxdata, nullptr, "");
EXPECT_NE(rxdata_size, 0, "");
EXPECT_EQ(txdata_size, rxdata_size, "");
memset(out_rxdata, 0, rxdata_size);
*out_rxdata_actual = rxdata_size;
break;
}
return ZX_OK;
}
void DdkUnbind() { DdkRemove(); }
void DdkRelease() { delete this; }
SpiDevice* bus_device_;
fbl::Vector<SpiChild*> children_;
fbl::Vector<fake_ddk::FidlMessenger*> fidl_clients_;
uint32_t current_test_cs_;
enum class SpiTestMode {
kTransmit,
kReceive,
kExchange,
} test_mode_;
static constexpr uint32_t kTestBusId = 0;
static constexpr spi_channel_t spi_channels_[] = {
{.bus_id = 0, .cs = 0, .vid = 0, .pid = 0, .did = 0},
{.bus_id = 0, .cs = 1, .vid = 0, .pid = 0, .did = 0}};
};
TEST(SpiDevice, SpiTest) {
FakeDdkSpiImpl ddk;
// make it
SpiDevice::Create(nullptr, fake_ddk::kFakeParent);
EXPECT_EQ(ddk.children_.size(), countof(ddk.spi_channels_), "");
// test it
const uint8_t txbuf[] = {0, 1, 2, 3, 4, 5, 6};
uint8_t rxbuf[sizeof txbuf];
for (size_t i = 0; i < ddk.children_.size(); i++) {
ddk.current_test_cs_ = static_cast<uint32_t>(i);
auto channel = ddk.fidl_clients_[i]->local().get();
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kTransmit;
zx_status_t status = spi_transmit(channel, txbuf, sizeof txbuf);
EXPECT_OK(status, "");
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kReceive;
status = spi_receive(channel, rxbuf, sizeof rxbuf);
EXPECT_OK(status, "");
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kExchange;
status = spi_exchange(channel, txbuf, rxbuf, sizeof txbuf);
EXPECT_OK(status, "");
}
// clean it up
ddk.bus_device_->DdkUnbind();
EXPECT_EQ(ddk.children_.size(), 0, "");
EXPECT_EQ(ddk.bus_device_, nullptr, "");
}
} // namespace spi