blob: 245063a536c729ca25060a3d0b30c9ee1cee19c6 [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 <fidl/fuchsia.hardware.spiimpl/cpp/driver/wire.h>
#include <fidl/fuchsia.scheduler/cpp/fidl.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/component/incoming/cpp/service.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/metadata.h>
#include <lib/fidl/cpp/wire/client.h>
#include <lib/spi/spi.h>
#include <zircon/errors.h>
#include <latch>
#include <map>
#include <zxtest/zxtest.h>
#include "sdk/lib/driver/outgoing/cpp/outgoing_directory.h"
#include "spi-child.h"
#include "src/devices/lib/fidl-metadata/spi.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace spi {
using spi_channel_t = fidl_metadata::spi::Channel;
class FakeSpiImplServer : public fdf::WireServer<fuchsia_hardware_spiimpl::SpiImpl> {
public:
static constexpr uint8_t kChipSelectCount = 2;
fidl::ProtocolHandler<fuchsia_hardware_spiimpl::SpiImpl> GetHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->get(),
std::mem_fn(&FakeSpiImplServer::OnClosed));
}
// fuchsia_hardware_spiimpl::SpiImpl methods
void GetChipSelectCount(fdf::Arena& arena,
GetChipSelectCountCompleter::Sync& completer) override {
completer.buffer(arena).Reply(kChipSelectCount);
}
void TransmitVector(fuchsia_hardware_spiimpl::wire::SpiImplTransmitVectorRequest* request,
fdf::Arena& arena, TransmitVectorCompleter::Sync& completer) override {
size_t actual;
auto status = SpiImplExchange(request->chip_select, request->data.data(), request->data.count(),
nullptr, 0, &actual);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void ReceiveVector(fuchsia_hardware_spiimpl::wire::SpiImplReceiveVectorRequest* request,
fdf::Arena& arena, ReceiveVectorCompleter::Sync& completer) override {
std::vector<uint8_t> rxdata(request->size);
size_t actual;
auto status =
SpiImplExchange(request->chip_select, nullptr, 0, rxdata.data(), rxdata.size(), &actual);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
rxdata.resize(actual);
completer.buffer(arena).ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(rxdata));
}
void ExchangeVector(fuchsia_hardware_spiimpl::wire::SpiImplExchangeVectorRequest* request,
fdf::Arena& arena, ExchangeVectorCompleter::Sync& completer) override {
std::vector<uint8_t> rxdata(request->txdata.count());
size_t actual;
auto status = SpiImplExchange(request->chip_select, request->txdata.data(),
request->txdata.count(), rxdata.data(), rxdata.size(), &actual);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
rxdata.resize(actual);
completer.buffer(arena).ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(rxdata));
}
void LockBus(fuchsia_hardware_spiimpl::wire::SpiImplLockBusRequest* request, fdf::Arena& arena,
LockBusCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void UnlockBus(fuchsia_hardware_spiimpl::wire::SpiImplUnlockBusRequest* request,
fdf::Arena& arena, UnlockBusCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void RegisterVmo(fuchsia_hardware_spiimpl::wire::SpiImplRegisterVmoRequest* request,
fdf::Arena& arena, RegisterVmoCompleter::Sync& completer) override {
auto status = SpiImplRegisterVmo(request->chip_select, request->vmo_id,
std::move(request->vmo.vmo), request->vmo.offset,
request->vmo.size, static_cast<uint32_t>(request->rights));
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void UnregisterVmo(fuchsia_hardware_spiimpl::wire::SpiImplUnregisterVmoRequest* request,
fdf::Arena& arena, UnregisterVmoCompleter::Sync& completer) override {
zx::vmo vmo;
auto status = SpiImplUnregisterVmo(request->chip_select, request->vmo_id, &vmo);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess(std::move(vmo));
}
void ReleaseRegisteredVmos(
fuchsia_hardware_spiimpl::wire::SpiImplReleaseRegisteredVmosRequest* request,
fdf::Arena& arena, ReleaseRegisteredVmosCompleter::Sync& completer) override {
SpiImplReleaseRegisteredVmos(request->chip_select);
}
void TransmitVmo(fuchsia_hardware_spiimpl::wire::SpiImplTransmitVmoRequest* request,
fdf::Arena& arena, TransmitVmoCompleter::Sync& completer) override {
auto status = SpiImplTransmitVmo(request->chip_select, request->buffer.vmo_id,
request->buffer.offset, request->buffer.size);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void ReceiveVmo(fuchsia_hardware_spiimpl::wire::SpiImplReceiveVmoRequest* request,
fdf::Arena& arena, ReceiveVmoCompleter::Sync& completer) override {
auto status = SpiImplReceiveVmo(request->chip_select, request->buffer.vmo_id,
request->buffer.offset, request->buffer.size);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void ExchangeVmo(fuchsia_hardware_spiimpl::wire::SpiImplExchangeVmoRequest* request,
fdf::Arena& arena, ExchangeVmoCompleter::Sync& completer) override {
ASSERT_EQ(request->tx_buffer.size, request->rx_buffer.size);
auto status = SpiImplExchangeVmo(request->chip_select, request->tx_buffer.vmo_id,
request->tx_buffer.offset, request->rx_buffer.vmo_id,
request->rx_buffer.offset, request->tx_buffer.size);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
uint32_t current_test_cs_ = 0;
bool corrupt_rx_actual_ = false;
bool vmos_released_since_last_call_ = false;
enum class SpiTestMode {
kTransmit,
kReceive,
kExchange,
} test_mode_;
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};
void OnClosed(fidl::UnbindInfo info) {
// Called when a connection to this server is closed.
// This is provided to the binding group during |CreateHandler|.
}
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;
}
void SpiImplReleaseRegisteredVmos(uint32_t chip_select) { vmos_released_since_last_call_ = true; }
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)));
}
fdf::ServerBindingGroup<fuchsia_hardware_spiimpl::SpiImpl> bindings_;
};
class SpiDeviceTest : public zxtest::Test {
protected:
SpiDeviceTest()
: runtime_(mock_ddk::GetDriverRuntime()),
dispatcher_(runtime_->StartBackgroundDispatcher()),
incoming_dispatcher_(runtime_->StartBackgroundDispatcher()),
ns_(incoming_dispatcher_->async_dispatcher(), std::in_place) {}
static constexpr uint32_t kTestBusId = 0;
static constexpr spi_channel_t kSpiChannels[] = {
{.cs = 0, .vid = 0, .pid = 0, .did = 0},
{.cs = 1, .vid = 0, .pid = 0, .did = 0},
};
void SetUp() override {
// TODO(https://fxbug.dev/42075363): Migrate test to use dispatcher integration.
parent_ = MockDevice::FakeRootParent();
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
ns_.SyncCall([&](IncomingNamespace* ns) {
auto service_result = ns->outgoing.AddService<fuchsia_hardware_spiimpl::Service>(
fuchsia_hardware_spiimpl::Service::InstanceHandler({
.device = ns->fake_spi_impl.GetHandler(),
}));
ZX_ASSERT(service_result.is_ok());
ZX_ASSERT(ns->outgoing.Serve(std::move(endpoints.server)).is_ok());
});
parent_->AddFidlService(fuchsia_hardware_spiimpl::Service::Name, std::move(endpoints.client));
SetSpiChannelMetadata(0, kSpiChannels, std::size(kSpiChannels));
parent_->SetMetadata(DEVICE_METADATA_PRIVATE, &kTestBusId, sizeof(kTestBusId));
}
void CreateSpiDevice() {
std::latch done(1);
async::PostTask(dispatcher_->async_dispatcher(), [&]() {
SpiDevice::Create(nullptr, parent_.get());
done.count_down();
});
done.wait();
}
void RemoveDevice(MockDevice* device) {
device_async_remove(device);
ASSERT_OK(mock_ddk::ReleaseFlaggedDevices(parent_.get(), dispatcher_->async_dispatcher()));
}
zx::result<fidl::ClientEnd<fuchsia_hardware_spi::Device>> BindServer(
const std::shared_ptr<MockDevice>& child) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto path = std::string("svc/") +
component::MakeServiceMemberPath<fuchsia_hardware_spi::Service::Device>("default");
return component::ConnectAt<fuchsia_hardware_spi::Device>(child->outgoing(), path);
}
void SetSpiChannelMetadata(const uint32_t bus_id, const spi_channel_t* channels, size_t count) {
const auto result = fidl_metadata::spi::SpiChannelsToFidl(
bus_id, cpp20::span<const spi_channel_t>(channels, count));
ASSERT_OK(result.status_value());
parent_->SetMetadata(DEVICE_METADATA_SPI_CHANNELS, result->data(), result->size());
}
std::shared_ptr<fdf_testing::DriverRuntime> runtime_;
std::shared_ptr<MockDevice> parent_;
fdf::UnownedSynchronizedDispatcher dispatcher_;
// Helper Methods that access fake_spi_impl
void set_current_test_cs(uint32_t i) {
ns_.SyncCall([i](IncomingNamespace* ns) { ns->fake_spi_impl.current_test_cs_ = i; });
}
void set_test_mode(FakeSpiImplServer::SpiTestMode test_mode) {
ns_.SyncCall([test_mode](IncomingNamespace* ns) { ns->fake_spi_impl.test_mode_ = test_mode; });
}
void set_corrupt_rx_actual(bool actual) {
ns_.SyncCall(
[actual](IncomingNamespace* ns) { ns->fake_spi_impl.corrupt_rx_actual_ = actual; });
}
bool vmos_released_since_last_call() {
return ns_.SyncCall([](IncomingNamespace* ns) {
const bool value = ns->fake_spi_impl.vmos_released_since_last_call_;
ns->fake_spi_impl.vmos_released_since_last_call_ = false;
return value;
});
}
fdf::UnownedSynchronizedDispatcher incoming_dispatcher_;
struct IncomingNamespace {
FakeSpiImplServer fake_spi_impl;
fdf::OutgoingDirectory outgoing{fdf::Dispatcher::GetCurrent()->get()};
};
async_patterns::TestDispatcherBound<IncomingNamespace> ns_;
};
TEST_F(SpiDeviceTest, SpiTest) {
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == std::size(kSpiChannels); });
EXPECT_EQ(spi_bus->child_count(), std::size(kSpiChannels));
// test it
uint8_t txbuf[] = {0, 1, 2, 3, 4, 5, 6};
uint8_t rxbuf[sizeof txbuf];
uint32_t i = 0;
for (auto it = spi_bus->children().begin(); it != spi_bus->children().end(); it++, i++) {
set_current_test_cs(i);
auto [client, server] = fidl::Endpoints<fuchsia_hardware_spi::Device>::Create();
fidl::BindServer(dispatcher_->async_dispatcher(), std::move(server),
(*it)->GetDeviceContext<SpiChild>());
set_test_mode(FakeSpiImplServer::SpiTestMode::kTransmit);
EXPECT_OK(spilib_transmit(client, txbuf, sizeof txbuf));
set_test_mode(FakeSpiImplServer::SpiTestMode::kReceive);
EXPECT_OK(spilib_receive(client, rxbuf, sizeof rxbuf));
set_test_mode(FakeSpiImplServer::SpiTestMode::kExchange);
EXPECT_OK(spilib_exchange(client, txbuf, rxbuf, sizeof txbuf));
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, SpiFidlVmoTest) {
using fuchsia_hardware_sharedmemory::wire::SharedVmoRight;
constexpr uint8_t kTestData[] = {1, 2, 3, 4, 5, 6, 7};
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == std::size(kSpiChannels); });
EXPECT_EQ(spi_bus->child_count(), std::size(kSpiChannels));
fidl::WireSharedClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
{
const auto& child1 = *++spi_bus->children().begin();
zx::result client = BindServer(child1);
ASSERT_OK(client);
cs1_client.Bind(std::move(client.value()), dispatcher_->async_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.sync()->RegisterVmo(1, std::move(vmo),
SharedVmoRight::kRead | SharedVmoRight::kWrite);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
{
fuchsia_mem::wire::Range vmo = {.offset = 0, .size = 4096};
ASSERT_OK(cs1_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo.vmo));
auto result = cs1_client.sync()->RegisterVmo(2, std::move(vmo),
SharedVmoRight::kRead | SharedVmoRight::kWrite);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
ASSERT_OK(cs0_vmo.write(kTestData, 1024, sizeof(kTestData)));
{
auto result = cs0_client.sync()->Exchange(
{
.vmo_id = 1,
.offset = 1024,
.size = sizeof(kTestData),
},
{
.vmo_id = 1,
.offset = 2048,
.size = sizeof(kTestData),
});
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
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.sync()->Transmit({
.vmo_id = 2,
.offset = 1024,
.size = sizeof(kTestData),
});
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
{
auto result = cs0_client.sync()->Receive({
.vmo_id = 1,
.offset = 1024,
.size = sizeof(kTestData),
});
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
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.sync()->UnregisterVmo(1);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
{
auto result = cs1_client.sync()->UnregisterVmo(2);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, SpiFidlVectorTest) {
fidl::WireSharedClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == std::size(kSpiChannels); });
EXPECT_EQ(spi_bus->child_count(), std::size(kSpiChannels));
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
{
const auto& child1 = *++spi_bus->children().begin();
zx::result client = BindServer(child1);
ASSERT_OK(client);
cs1_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
uint8_t test_data[] = {1, 2, 3, 4, 5, 6, 7};
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kTransmit);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client.sync()->TransmitVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
set_current_test_cs(1);
set_test_mode(FakeSpiImplServer::SpiTestMode::kReceive);
{
auto result = cs1_client.sync()->ReceiveVector(sizeof(test_data));
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
ASSERT_EQ(result.value().data.count(), std::size(test_data));
EXPECT_BYTES_EQ(result.value().data.data(), test_data, sizeof(test_data));
}
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kExchange);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client.sync()->ExchangeVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
ASSERT_EQ(result.value().rxdata.count(), std::size(test_data));
EXPECT_BYTES_EQ(result.value().rxdata.data(), test_data, sizeof(test_data));
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, SpiFidlVectorErrorTest) {
fidl::WireSharedClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == std::size(kSpiChannels); });
EXPECT_EQ(spi_bus->child_count(), std::size(kSpiChannels));
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
{
const auto& child1 = *++spi_bus->children().begin();
zx::result client = BindServer(child1);
ASSERT_OK(client);
cs1_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
set_corrupt_rx_actual(true);
uint8_t test_data[] = {1, 2, 3, 4, 5, 6, 7};
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kTransmit);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client.sync()->TransmitVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
set_current_test_cs(1);
set_test_mode(FakeSpiImplServer::SpiTestMode::kReceive);
{
auto result = cs1_client.sync()->ReceiveVector(sizeof(test_data));
ASSERT_OK(result.status());
EXPECT_EQ(result.value().status, ZX_ERR_INTERNAL);
EXPECT_EQ(result.value().data.count(), 0);
}
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kExchange);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client.sync()->ExchangeVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_EQ(result.value().status, ZX_ERR_INTERNAL);
EXPECT_EQ(result.value().rxdata.count(), 0);
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, AssertCsWithSiblingTest) {
fidl::WireSharedClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == std::size(kSpiChannels); });
EXPECT_EQ(spi_bus->child_count(), std::size(kSpiChannels));
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
{
const auto& child1 = *++spi_bus->children().begin();
zx::result client = BindServer(child1);
ASSERT_OK(client);
cs1_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
{
auto result = cs0_client.sync()->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().can);
}
{
auto result = cs1_client.sync()->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().can);
}
{
auto result = cs0_client.sync()->AssertCs();
ASSERT_OK(result.status());
ASSERT_STATUS(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
{
auto result = cs1_client.sync()->AssertCs();
ASSERT_OK(result.status());
ASSERT_STATUS(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
{
auto result = cs0_client.sync()->DeassertCs();
ASSERT_OK(result.status());
ASSERT_STATUS(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
{
auto result = cs1_client.sync()->DeassertCs();
ASSERT_OK(result.status());
ASSERT_STATUS(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, AssertCsNoSiblingTest) {
SetSpiChannelMetadata(0, kSpiChannels, 1);
fidl::WireSharedClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == 1; });
EXPECT_EQ(spi_bus->child_count(), 1);
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()), dispatcher_->async_dispatcher());
}
{
auto result = cs0_client.sync()->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().can);
}
{
auto result = cs0_client.sync()->AssertCs();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
}
{
auto result = cs0_client.sync()->DeassertCs();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, OneClient) {
SetSpiChannelMetadata(0, kSpiChannels, 1);
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == 1; });
EXPECT_EQ(spi_bus->child_count(), 1);
std::shared_ptr<MockDevice> spi_child = nullptr;
for (auto& child : spi_bus->children()) {
if (std::string_view(child->name()) == "spi-0-0") {
spi_child = child;
break;
}
}
ASSERT_NOT_NULL(spi_child);
// Establish a FIDL connection and verify that it works.
{
zx::result client = BindServer(spi_child);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().can);
}
// Trying to make a new connection should fail.
{
zx::result client = BindServer(spi_child);
ASSERT_OK(client);
fidl::WireSyncClient cs0_client_1(std::move(client.value()));
EXPECT_STATUS(cs0_client_1->UnregisterVmo(1).status(), ZX_ERR_PEER_CLOSED);
}
EXPECT_FALSE(vmos_released_since_last_call());
// Close the first client so that another one can connect.
cs0_client = {};
// We don't know when the driver will be ready for a new client, just loop
// until the connection is established.
for (;;) {
zx::result client = BindServer(spi_child);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()));
// By the time this connection succeeds, the SPI device has already called to its parent to
// release registered VMOs. UnregisterVmo will then make a synchronous call to the parent, so
// waiting for the UnregisterVmo response is sufficient to guarantee that the first call to the
// parent (the fake spiimpl device) has been processed, and that vmos_released_since_last_call
// will return the correct value.
auto result = cs0_client->UnregisterVmo(1);
if (result.ok()) {
break;
}
EXPECT_STATUS(result.status(), ZX_ERR_PEER_CLOSED);
cs0_client = {};
}
EXPECT_TRUE(vmos_released_since_last_call());
// OpenSession should fail when another client is connected.
{
auto [controller, server] = fidl::Endpoints<fuchsia_hardware_spi::Controller>::Create();
fidl::BindServer(dispatcher_->async_dispatcher(), std::move(server),
spi_child->GetDeviceContext<SpiChild>());
{
auto [device, server] = fidl::Endpoints<fuchsia_hardware_spi::Device>::Create();
ASSERT_OK(fidl::WireCall(controller)->OpenSession(std::move(server)));
ASSERT_STATUS(fidl::WireCall(device)->CanAssertCs(), ZX_ERR_PEER_CLOSED);
}
}
// Close the first client and make sure OpenSession now works.
cs0_client = {};
fidl::ClientEnd<fuchsia_hardware_spi::Device> device;
while (true) {
auto [controller, server] = fidl::Endpoints<fuchsia_hardware_spi::Controller>::Create();
fidl::BindServer(dispatcher_->async_dispatcher(), std::move(server),
spi_child->GetDeviceContext<SpiChild>());
{
zx::result server = fidl::CreateEndpoints<fuchsia_hardware_spi::Device>(&device);
ASSERT_OK(server);
ASSERT_OK(fidl::WireCall(controller)->OpenSession(std::move(server.value())));
auto result = fidl::WireCall(device)->UnregisterVmo(1);
if (result.ok()) {
break;
}
ASSERT_STATUS(result, ZX_ERR_PEER_CLOSED);
}
}
EXPECT_TRUE(vmos_released_since_last_call());
// FIDL clients shouldn't be able to connect, and calling OpenSession a second time should fail.
{
zx::result client = BindServer(spi_child);
ASSERT_OK(client);
fidl::WireSyncClient cs0_client_1(std::move(client.value()));
EXPECT_STATUS(cs0_client_1->CanAssertCs().status(), ZX_ERR_PEER_CLOSED);
}
{
auto [controller, server] = fidl::Endpoints<fuchsia_hardware_spi::Controller>::Create();
fidl::BindServer(dispatcher_->async_dispatcher(), std::move(server),
spi_child->GetDeviceContext<SpiChild>());
{
auto [device, server] = fidl::Endpoints<fuchsia_hardware_spi::Device>::Create();
ASSERT_OK(fidl::WireCall(controller)->OpenSession(std::move(server)));
ASSERT_STATUS(fidl::WireCall(device)->CanAssertCs(), ZX_ERR_PEER_CLOSED);
}
}
// Close the open session and make sure that a new client can now connect.
device = {};
for (;;) {
zx::result client = BindServer(spi_child);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()));
auto result = cs0_client->UnregisterVmo(1);
if (result.ok()) {
break;
}
EXPECT_STATUS(result.status(), ZX_ERR_PEER_CLOSED);
cs0_client = {};
}
EXPECT_TRUE(vmos_released_since_last_call());
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
}
TEST_F(SpiDeviceTest, DdkLifecycle) {
SetSpiChannelMetadata(0, kSpiChannels, 1);
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == 1; });
EXPECT_EQ(spi_bus->child_count(), 1);
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->AssertCs();
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
RemoveDevice(spi_bus->children().front().get());
EXPECT_EQ(spi_bus->descendant_count(), 0);
{
auto result = cs0_client->DeassertCs();
EXPECT_STATUS(result.status(), ZX_ERR_PEER_CLOSED);
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
{
auto result = cs0_client->DeassertCs();
// The parent has stopped its loop, this should now fail.
EXPECT_STATUS(result.status(), ZX_ERR_PEER_CLOSED);
}
}
TEST_F(SpiDeviceTest, DdkLifecycleWithDispatcher) {
SetSpiChannelMetadata(0, kSpiChannels, 1);
{
// Add scheduler role metadata that will cause the core driver to create a new driver
// dispatcher. Verify that FIDL calls can still be made, and that dispatcher shutdown using the
// unbind hook works.
fuchsia_scheduler::RoleName role("no.such.scheduler.role");
const auto result = fidl::Persist(role);
ASSERT_TRUE(result.is_ok());
parent_->SetMetadata(DEVICE_METADATA_SCHEDULER_ROLE_NAME, result->data(), result->size());
}
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice();
auto* const spi_bus = parent_->GetLatestChild();
ASSERT_NOT_NULL(spi_bus);
runtime_->RunUntil([&]() { return spi_bus->child_count() == 1; });
EXPECT_EQ(spi_bus->child_count(), 1);
{
const auto& child0 = spi_bus->children().front();
zx::result client = BindServer(child0);
ASSERT_OK(client);
cs0_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->AssertCs();
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
RemoveDevice(spi_bus->children().front().get());
EXPECT_EQ(spi_bus->descendant_count(), 0);
{
auto result = cs0_client->DeassertCs();
EXPECT_STATUS(result.status(), ZX_ERR_PEER_CLOSED);
}
RemoveDevice(spi_bus);
EXPECT_EQ(parent_->descendant_count(), 0);
{
auto result = cs0_client->DeassertCs();
// The parent has stopped its loop, this should now fail.
EXPECT_STATUS(result.status(), ZX_ERR_PEER_CLOSED);
}
}
} // namespace spi