blob: cf9d0e8128fac6b891a1eb2a5051e9eba7d94e0f [file] [edit]
// 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 "ftdi-i2c.h"
#include <fidl/fuchsia.hardware.ftdi/cpp/wire.h>
#include <fuchsia/hardware/serialimpl/cpp/banjo.h>
#include <lib/ddk/debug.h>
#include <stdio.h>
#include <list>
#include <zxtest/zxtest.h>
#include "ftdi.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace ftdi_mpsse {
// Fake for the raw nand protocol.
class FakeSerial : public ddk::SerialImplProtocol<FakeSerial> {
public:
FakeSerial() : proto_({&serial_impl_protocol_ops_, this}) {}
const serial_impl_protocol_t* proto() const { return &proto_; }
void PushExpectedRead(std::vector<uint8_t> read) { expected_reads_.push_back(std::move(read)); }
void PushExpectedWrite(std::vector<uint8_t> write) {
expected_writes_.push_back(std::move(write));
}
void FailOnUnexpectedReadWrite(bool b) { unexpected_is_error_ = b; }
zx_status_t SerialImplGetInfo(serial_port_info_t* out_info) { return ZX_OK; }
zx_status_t SerialImplConfig(uint32_t baud_rate, uint32_t flags) { return ZX_OK; }
zx_status_t SerialImplEnable(bool enable) { return ZX_OK; }
zx_status_t SerialImplRead(uint8_t* out_buf_buffer, size_t buf_size, size_t* out_buf_actual) {
uint8_t* out_buf = out_buf_buffer;
if (expected_reads_.size() == 0) {
if (unexpected_is_error_) {
printf("Read with no expected read set\n");
return ZX_ERR_INTERNAL;
} else {
*out_buf_actual = buf_size;
return ZX_OK;
}
}
std::vector<uint8_t>& read = expected_reads_.front();
if (buf_size != read.size()) {
printf("Read size mismatch (%lx != %lx\n", buf_size, read.size());
return ZX_ERR_INTERNAL;
}
for (size_t i = 0; i < buf_size; i++) {
out_buf[i] = read[i];
}
expected_reads_.pop_front();
*out_buf_actual = buf_size;
return ZX_OK;
}
zx_status_t SerialImplWrite(const uint8_t* buf_buffer, size_t buf_size, size_t* out_actual) {
const uint8_t* out_buf = buf_buffer;
if (expected_writes_.size() == 0) {
if (unexpected_is_error_) {
printf("Write with no expected wrte set\n");
return ZX_ERR_INTERNAL;
} else {
*out_actual = buf_size;
return ZX_OK;
}
}
std::vector<uint8_t>& write = expected_writes_.front();
if (buf_size != write.size()) {
printf("Write size mismatch (0x%lx != 0x%lx\n", buf_size, write.size());
return ZX_ERR_INTERNAL;
}
for (size_t i = 0; i < buf_size; i++) {
if (out_buf[i] != write[i]) {
printf("Write data mismatch index %ld (0x%x != 0x%x)\n", i, out_buf[i], write[i]);
return ZX_ERR_INTERNAL;
}
}
expected_writes_.pop_front();
*out_actual = buf_size;
return ZX_OK;
}
zx_status_t SerialImplSetNotifyCallback(const serial_notify_t* cb) { return ZX_OK; }
private:
bool unexpected_is_error_ = false;
std::list<std::vector<uint8_t>> expected_reads_;
std::list<std::vector<uint8_t>> expected_writes_;
serial_impl_protocol_t proto_;
};
class FtdiI2cTest : public zxtest::Test {
public:
void SetUp() override {
fake_parent_ = MockDevice::FakeRootParent();
fake_parent_->AddProtocol(ZX_PROTOCOL_SERIAL_IMPL, serial_.proto()->ops, serial_.proto()->ctx);
}
protected:
FtdiI2c FtdiBasicInit(void) {
FtdiI2c::I2cLayout layout = {0, 1, 2};
std::vector<FtdiI2c::I2cDevice> i2c_devices(1);
i2c_devices[0].address = 0x3c;
i2c_devices[0].vid = 0;
i2c_devices[0].pid = 0;
i2c_devices[0].did = 31;
return FtdiI2c(fake_parent_.get(), layout, i2c_devices);
}
std::shared_ptr<MockDevice> fake_parent_;
FakeSerial serial_;
};
TEST_F(FtdiI2cTest, TrivialLifetimeTest) { FtdiI2c device = FtdiBasicInit(); }
TEST_F(FtdiI2cTest, DdkLifetimeTest) {
FtdiI2c::I2cLayout layout = {0, 1, 2};
std::vector<FtdiI2c::I2cDevice> i2c_devices(1);
i2c_devices[0].address = 0x3c;
i2c_devices[0].vid = 0;
i2c_devices[0].pid = 0;
i2c_devices[0].did = 31;
FtdiI2c* device(new FtdiI2c(fake_parent_.get(), layout, i2c_devices));
// These Reads and Writes are to sync the device on bind.
std::vector<uint8_t> first_write(1);
first_write[0] = 0xAB;
serial_.PushExpectedWrite(std::move(first_write));
std::vector<uint8_t> first_read(2);
first_read[0] = 0xFA;
first_read[1] = 0xAB;
serial_.PushExpectedRead(std::move(first_read));
// Check that bind works.
ASSERT_OK(device->Bind());
auto* child = fake_parent_->GetLatestChild();
child->InitOp();
child->WaitUntilInitReplyCalled();
device->DdkAsyncRemove();
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
}
TEST_F(FtdiI2cTest, DdkLifetimeFailedInit) {
FtdiI2c::I2cLayout layout = {0, 1, 2};
std::vector<FtdiI2c::I2cDevice> i2c_devices(1);
i2c_devices[0].address = 0x3c;
i2c_devices[0].vid = 0;
i2c_devices[0].pid = 0;
i2c_devices[0].did = 31;
FtdiI2c* device(new FtdiI2c(fake_parent_.get(), layout, i2c_devices));
// These Reads and Writes are to sync the device on bind.
std::vector<uint8_t> first_write(1);
first_write[0] = 0xAB;
serial_.PushExpectedWrite(std::move(first_write));
// Set bad read data. This will cause the enable worker thread to fail.
std::vector<uint8_t> first_read(2);
first_read[0] = 0x00;
first_read[1] = 0x00;
serial_.PushExpectedRead(std::move(first_read));
// Bind should spawn the worker thread which will fail the init.
ASSERT_OK(device->Bind());
auto* child = fake_parent_->GetLatestChild();
child->InitOp();
child->WaitUntilInitReplyCalled();
ASSERT_EQ(ZX_ERR_INTERNAL, child->InitReplyCallStatus());
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
}
TEST_F(FtdiI2cTest, PingTest) {
FtdiI2c device = FtdiBasicInit();
std::vector<uint8_t> ping_data = {
0x80, 0x3, 0x3, 0x82, 0x0, 0x0, 0x80, 0x1, 0x3, 0x82, 0x0, 0x0, 0x80, 0x0, 0x3, 0x82,
0x0, 0x0, 0x11, 0x0, 0x0, 0x78, 0x80, 0x2, 0x3, 0x82, 0x0, 0x0, 0x22, 0x0, 0x11, 0x0,
0x0, 0x0, 0x80, 0x2, 0x3, 0x82, 0x0, 0x0, 0x22, 0x0, 0x80, 0x0, 0x3, 0x82, 0x0, 0x0,
0x80, 0x1, 0x3, 0x82, 0x0, 0x0, 0x80, 0x3, 0x3, 0x82, 0x0, 0x0, 0x87};
serial_.PushExpectedWrite(std::move(ping_data));
zx_status_t status = device.Ping(0x3c);
ASSERT_OK(status);
}
TEST_F(FtdiI2cTest, ReadTest) {
FtdiI2c device = FtdiBasicInit();
serial_.FailOnUnexpectedReadWrite(false);
std::vector<uint8_t> serial_read_data = {
0x00, // The ACK for writing bus address.
0x00, // The ACK for writing register value.
0x00, // The ACK for initiating a read.
0xDE, // The Value we will be reading out.
};
serial_.PushExpectedRead(std::move(serial_read_data));
i2c_impl_op_t op_list[2] = {};
op_list[0].is_read = false;
op_list[0].stop = false;
uint8_t write_data = 0xAB;
op_list[0].data_buffer = &write_data;
op_list[0].data_size = sizeof(write_data);
op_list[1].is_read = true;
op_list[1].stop = true;
uint8_t read_data = 0;
op_list[1].data_buffer = &read_data;
op_list[1].data_size = sizeof(read_data);
zx_status_t status = device.I2cImplTransact(0, op_list, 2);
ASSERT_OK(status);
ASSERT_EQ(0xDE, read_data);
}
TEST_F(FtdiI2cTest, NackReadTest) {
FtdiI2c device = FtdiBasicInit();
serial_.FailOnUnexpectedReadWrite(false);
std::vector<uint8_t> serial_read_data = {
0x01, // The NACK for writing bus address.
0x01, // The NACK for writing register value.
0x01, // The NACK for initiating a read.
0x00, // The Value we will be reading out.
};
serial_.PushExpectedRead(std::move(serial_read_data));
i2c_impl_op_t op_list[2] = {};
op_list[0].is_read = false;
op_list[0].stop = false;
uint8_t write_data = 0xAB;
op_list[0].data_buffer = &write_data;
op_list[0].data_size = sizeof(write_data);
op_list[1].is_read = true;
op_list[1].stop = true;
uint8_t read_data = 0;
op_list[1].data_buffer = &read_data;
op_list[1].data_size = sizeof(read_data);
zx_status_t status = device.I2cImplTransact(0, op_list, 2);
ASSERT_EQ(ZX_ERR_INTERNAL, status);
}
} // namespace ftdi_mpsse