blob: 839ea855fd2565f175a6664fd432023d1be5c30e [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 "spi.h"
#include <fuchsia/hardware/platform/bus/cpp/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/ddk/metadata.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/fidl/llcpp/client.h>
#include <lib/spi/spi.h>
#include <map>
#include <ddk/metadata/spi.h>
#include <zxtest/zxtest.h>
namespace spi {
class FakeDdkSpiImpl;
class FakeDdkSpiImpl : public fake_ddk::Bind,
public ddk::SpiImplProtocol<FakeDdkSpiImpl, ddk::base_protocol> {
public:
explicit FakeDdkSpiImpl() {
fake_ddk::Protocol proto = {&spi_impl_protocol_ops_, this};
SetProtocol(ZX_PROTOCOL_SPI_IMPL, &proto);
SetMetadata(DEVICE_METADATA_SPI_CHANNELS, &kSpiChannels, sizeof(kSpiChannels));
SetMetadata(DEVICE_METADATA_PRIVATE, &kTestBusId, sizeof(kTestBusId));
}
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_)) {
delete bus_device_;
bus_device_ = nullptr;
// The SpiChild device's unbind hooks will be called after the
// Spi device has replied to its unbind.
// We need to make a copy before iterating, as the SpiChild will be
// erased from this |children_| list when it replies to its unbind.
fbl::Vector<SpiChild*> children_copy;
for (size_t i = 0; i < children_.size(); i++) {
children_copy.push_back(children_[i]);
}
for (size_t i = 0; i < children_copy.size(); i++) {
zx_device_t* zxdev = reinterpret_cast<zx_device_t*>(children_copy[i]);
children_copy[i]->DdkUnbind(ddk::UnbindTxn(zxdev));
}
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;
}
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, "");
*out_rxdata_actual = 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);
memcpy(out_rxdata, kTestData, std::min(rxdata_size, sizeof(kTestData)));
*out_rxdata_actual = rxdata_size + (corrupt_rx_actual_ ? 1 : 0);
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);
memcpy(out_rxdata, txdata, std::min(rxdata_size, txdata_size));
*out_rxdata_actual = std::min(rxdata_size, txdata_size) + (corrupt_rx_actual_ ? 1 : 0);
break;
}
return ZX_OK;
}
zx_status_t SpiImplRegisterVmo(uint32_t chip_select, uint32_t vmo_id, zx::vmo vmo,
uint64_t offset, uint64_t size, uint32_t rights) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
if (map.find(vmo_id) != map.end()) {
return ZX_ERR_ALREADY_EXISTS;
}
map[vmo_id] = std::move(vmo);
return ZX_OK;
}
zx_status_t SpiImplUnregisterVmo(uint32_t chip_select, uint32_t vmo_id, zx::vmo* out_vmo) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto it = map.find(vmo_id);
if (it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
if (out_vmo) {
out_vmo->reset(std::get<1>(*it).release());
}
map.erase(it);
return ZX_OK;
}
zx_status_t SpiImplTransmitVmo(uint32_t chip_select, uint32_t vmo_id, uint64_t offset,
uint64_t size) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto it = map.find(vmo_id);
if (it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
uint8_t buf[sizeof(kTestData)];
zx_status_t status = std::get<1>(*it).read(buf, offset, std::max(size, sizeof(buf)));
if (status != ZX_OK) {
return status;
}
return memcmp(buf, kTestData, std::max(size, sizeof(buf))) == 0 ? ZX_OK : ZX_ERR_IO;
}
zx_status_t SpiImplReceiveVmo(uint32_t chip_select, uint32_t vmo_id, uint64_t offset,
uint64_t size) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto it = map.find(vmo_id);
if (it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
return std::get<1>(*it).write(kTestData, offset, std::max(size, sizeof(kTestData)));
}
zx_status_t SpiImplExchangeVmo(uint32_t chip_select, uint32_t tx_vmo_id, uint64_t tx_offset,
uint32_t rx_vmo_id, uint64_t rx_offset, uint64_t size) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto tx_it = map.find(tx_vmo_id);
auto rx_it = map.find(rx_vmo_id);
if (tx_it == map.end() || rx_it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
uint8_t buf[8];
zx_status_t status = std::get<1>(*tx_it).read(buf, tx_offset, std::max(size, sizeof(buf)));
if (status != ZX_OK) {
return status;
}
return std::get<1>(*rx_it).write(buf, rx_offset, std::max(size, sizeof(buf)));
}
SpiDevice* bus_device_;
fbl::Vector<SpiChild*> children_;
fbl::Vector<fake_ddk::FidlMessenger*> fidl_clients_;
uint32_t current_test_cs_;
bool corrupt_rx_actual_ = false;
enum class SpiTestMode {
kTransmit,
kReceive,
kExchange,
} test_mode_;
static constexpr uint32_t kTestBusId = 0;
static constexpr spi_channel_t kSpiChannels[] = {
{.bus_id = 0, .cs = 0, .vid = 0, .pid = 0, .did = 0},
{.bus_id = 0, .cs = 1, .vid = 0, .pid = 0, .did = 0}};
std::map<uint32_t, zx::vmo> cs0_vmos;
std::map<uint32_t, zx::vmo> cs1_vmos;
private:
static constexpr uint8_t kTestData[] = {1, 2, 3, 4, 5, 6, 7};
};
TEST(SpiDevice, SpiTest) {
FakeDdkSpiImpl ddk;
// make it
SpiDevice::Create(nullptr, fake_ddk::kFakeParent);
EXPECT_EQ(ddk.children_.size(), std::size(ddk.kSpiChannels));
// test it
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 = spilib_transmit(channel, txbuf, sizeof txbuf);
EXPECT_OK(status, "");
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kReceive;
status = spilib_receive(channel, rxbuf, sizeof rxbuf);
EXPECT_OK(status, "");
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kExchange;
status = spilib_exchange(channel, txbuf, rxbuf, sizeof txbuf);
EXPECT_OK(status, "");
}
// clean it up
zx_device_t* zxdev = reinterpret_cast<zx_device_t*>(ddk.bus_device_);
ddk.bus_device_->DdkUnbind(ddk::UnbindTxn(zxdev));
EXPECT_EQ(ddk.children_.size(), 0, "");
EXPECT_EQ(ddk.bus_device_, nullptr, "");
}
TEST(SpiDevice, SpiFidlVmoTest) {
using fuchsia_hardware_sharedmemory::wire::SharedVmoRight;
constexpr uint8_t kTestData[] = {1, 2, 3, 4, 5, 6, 7};
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
ASSERT_OK(loop.StartThread("spi-test-thread"));
FakeDdkSpiImpl ddk;
fidl::Client<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
SpiDevice::Create(nullptr, fake_ddk::kFakeParent);
EXPECT_EQ(ddk.children_.size(), std::size(ddk.kSpiChannels));
{
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ddk.children_[0]->SpiConnectServer(std::move(server));
cs0_client.Bind(std::move(client), loop.dispatcher());
}
{
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ddk.children_[1]->SpiConnectServer(std::move(server));
cs1_client.Bind(std::move(client), loop.dispatcher());
}
zx::vmo cs0_vmo, cs1_vmo;
ASSERT_OK(zx::vmo::create(4096, 0, &cs0_vmo));
ASSERT_OK(zx::vmo::create(4096, 0, &cs1_vmo));
{
fuchsia_mem::wire::Range vmo = {.offset = 0, .size = 4096};
ASSERT_OK(cs0_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo.vmo));
auto result = cs0_client->RegisterVmo_Sync(1, std::move(vmo),
SharedVmoRight::READ | SharedVmoRight::WRITE);
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
}
{
fuchsia_mem::wire::Range vmo = {.offset = 0, .size = 4096};
ASSERT_OK(cs1_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo.vmo));
auto result = cs1_client->RegisterVmo_Sync(2, std::move(vmo),
SharedVmoRight::READ | SharedVmoRight::WRITE);
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
}
ASSERT_OK(cs0_vmo.write(kTestData, 1024, sizeof(kTestData)));
{
auto result = cs0_client->Exchange_Sync(
{
.vmo_id = 1,
.offset = 1024,
.size = sizeof(kTestData),
},
{
.vmo_id = 1,
.offset = 2048,
.size = sizeof(kTestData),
});
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
uint8_t buf[sizeof(kTestData)];
ASSERT_OK(cs0_vmo.read(buf, 2048, sizeof(buf)));
EXPECT_BYTES_EQ(buf, kTestData, sizeof(buf));
}
ASSERT_OK(cs1_vmo.write(kTestData, 1024, sizeof(kTestData)));
{
auto result = cs1_client->Transmit_Sync({
.vmo_id = 2,
.offset = 1024,
.size = sizeof(kTestData),
});
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
}
{
auto result = cs0_client->Receive_Sync({
.vmo_id = 1,
.offset = 1024,
.size = sizeof(kTestData),
});
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
uint8_t buf[sizeof(kTestData)];
ASSERT_OK(cs0_vmo.read(buf, 1024, sizeof(buf)));
EXPECT_BYTES_EQ(buf, kTestData, sizeof(buf));
}
{
auto result = cs0_client->UnregisterVmo_Sync(1);
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
}
{
auto result = cs1_client->UnregisterVmo_Sync(2);
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->result.is_response());
}
zx_device_t* zxdev = reinterpret_cast<zx_device_t*>(ddk.bus_device_);
ddk.bus_device_->DdkUnbind(ddk::UnbindTxn(zxdev));
EXPECT_EQ(ddk.children_.size(), 0);
EXPECT_EQ(ddk.bus_device_, nullptr);
}
TEST(SpiDevice, SpiFidlVectorTest) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
ASSERT_OK(loop.StartThread("spi-test-thread"));
FakeDdkSpiImpl ddk;
fidl::Client<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
SpiDevice::Create(nullptr, fake_ddk::kFakeParent);
EXPECT_EQ(ddk.children_.size(), std::size(ddk.kSpiChannels));
{
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ddk.children_[0]->SpiConnectServer(std::move(server));
cs0_client.Bind(std::move(client), loop.dispatcher());
}
{
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ddk.children_[1]->SpiConnectServer(std::move(server));
cs1_client.Bind(std::move(client), loop.dispatcher());
}
uint8_t test_data[] = {1, 2, 3, 4, 5, 6, 7};
ddk.current_test_cs_ = 0;
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kTransmit;
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->TransmitVector_Sync(std::move(tx_buffer));
ASSERT_OK(result.status());
EXPECT_OK(result->status);
}
ddk.current_test_cs_ = 1;
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kReceive;
{
auto result = cs1_client->ReceiveVector_Sync(sizeof(test_data));
ASSERT_OK(result.status());
EXPECT_OK(result->status);
ASSERT_EQ(result->data.count(), countof(test_data));
EXPECT_BYTES_EQ(result->data.data(), test_data, sizeof(test_data));
}
ddk.current_test_cs_ = 0;
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kExchange;
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->ExchangeVector_Sync(std::move(tx_buffer));
ASSERT_OK(result.status());
EXPECT_OK(result->status);
ASSERT_EQ(result->rxdata.count(), countof(test_data));
EXPECT_BYTES_EQ(result->rxdata.data(), test_data, sizeof(test_data));
}
zx_device_t* zxdev = reinterpret_cast<zx_device_t*>(ddk.bus_device_);
ddk.bus_device_->DdkUnbind(ddk::UnbindTxn(zxdev));
EXPECT_EQ(ddk.children_.size(), 0);
EXPECT_EQ(ddk.bus_device_, nullptr);
}
TEST(SpiDevice, SpiFidlVectorErrorTest) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
ASSERT_OK(loop.StartThread("spi-test-thread"));
FakeDdkSpiImpl ddk;
fidl::Client<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
SpiDevice::Create(nullptr, fake_ddk::kFakeParent);
EXPECT_EQ(ddk.children_.size(), std::size(ddk.kSpiChannels));
{
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ddk.children_[0]->SpiConnectServer(std::move(server));
cs0_client.Bind(std::move(client), loop.dispatcher());
}
{
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ddk.children_[1]->SpiConnectServer(std::move(server));
cs1_client.Bind(std::move(client), loop.dispatcher());
}
ddk.corrupt_rx_actual_ = true;
uint8_t test_data[] = {1, 2, 3, 4, 5, 6, 7};
ddk.current_test_cs_ = 0;
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kTransmit;
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->TransmitVector_Sync(std::move(tx_buffer));
ASSERT_OK(result.status());
EXPECT_OK(result->status);
}
ddk.current_test_cs_ = 1;
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kReceive;
{
auto result = cs1_client->ReceiveVector_Sync(sizeof(test_data));
ASSERT_OK(result.status());
EXPECT_EQ(result->status, ZX_ERR_INTERNAL);
EXPECT_EQ(result->data.count(), 0);
}
ddk.current_test_cs_ = 0;
ddk.test_mode_ = FakeDdkSpiImpl::SpiTestMode::kExchange;
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->ExchangeVector_Sync(std::move(tx_buffer));
ASSERT_OK(result.status());
EXPECT_EQ(result->status, ZX_ERR_INTERNAL);
EXPECT_EQ(result->rxdata.count(), 0);
}
zx_device_t* zxdev = reinterpret_cast<zx_device_t*>(ddk.bus_device_);
ddk.bus_device_->DdkUnbind(ddk::UnbindTxn(zxdev));
EXPECT_EQ(ddk.children_.size(), 0);
EXPECT_EQ(ddk.bus_device_, nullptr);
}
} // namespace spi