blob: e24e5cde5219b9e534cc1d2f42431de8818161ed [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 "serial.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <memory>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <zxtest/zxtest.h>
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace {
constexpr size_t kBufferLength = 16;
constexpr zx_signals_t kEventWrittenSignal = ZX_USER_SIGNAL_0;
// Fake for the SerialImpl protocol.
class FakeSerialImpl : public ddk::SerialImplProtocol<FakeSerialImpl> {
public:
FakeSerialImpl() : proto_({&serial_impl_protocol_ops_, this}) {
zx::event::create(0, &write_event_);
}
// Getters.
const serial_impl_protocol_t* proto() const { return &proto_; }
bool enabled() const { return enabled_; }
const serial_notify_t* callback() {
fbl::AutoLock al(&cb_lock_);
return &callback_;
}
char* read_buffer() { return read_buffer_; }
const char* write_buffer() { return write_buffer_; }
size_t write_buffer_length() const { return write_buffer_length_; }
size_t total_written_bytes() const { return total_written_bytes_; }
// Test utility methods.
void set_state_and_notify(serial_state_t state) {
fbl::AutoLock al(&cb_lock_);
state_ = state;
if (callback_.callback) {
callback_.callback(callback_.ctx, state_);
}
}
zx_status_t wait_for_write(zx::time deadline, zx_signals_t* pending) {
return write_event_.wait_one(kEventWrittenSignal, deadline, pending);
}
zx_status_t SerialImplGetInfo(serial_port_info_t* info) { return ZX_OK; }
zx_status_t SerialImplConfig(uint32_t baud_rate, uint32_t flags) { return ZX_OK; }
zx_status_t SerialImplEnable(bool enable) {
enabled_ = enable;
return ZX_OK;
}
zx_status_t SerialImplRead(uint8_t* buf, size_t length, size_t* out_actual) {
if (!(state_ & SERIAL_STATE_READABLE)) {
*out_actual = 0;
return ZX_ERR_SHOULD_WAIT;
}
char* buffer = reinterpret_cast<char*>(buf);
size_t i;
for (i = 0; i < length && i < kBufferLength && read_buffer_[i]; ++i) {
buffer[i] = read_buffer_[i];
}
*out_actual = i;
if (i == kBufferLength || !read_buffer_[i]) {
// Simply reset the state, no advanced state machine.
set_state_and_notify(0);
}
return ZX_OK;
}
zx_status_t SerialImplWrite(const uint8_t* buf, size_t length, size_t* out_actual) {
if (!(state_ & SERIAL_STATE_WRITABLE)) {
*out_actual = 0;
return ZX_ERR_SHOULD_WAIT;
}
const char* buffer = reinterpret_cast<const char*>(buf);
size_t i;
for (i = 0; i < length && i < kBufferLength; ++i) {
write_buffer_[i] = buffer[i];
}
*out_actual = i;
// Signal that the write_buffer has been written to.
if (i > 0) {
write_buffer_length_ = i;
total_written_bytes_ += i;
write_event_.signal(0, kEventWrittenSignal);
}
return ZX_OK;
}
zx_status_t SerialImplSetNotifyCallback(const serial_notify_t* cb) {
fbl::AutoLock al(&cb_lock_);
callback_.callback = cb->callback;
callback_.ctx = cb->ctx;
return ZX_OK;
}
private:
serial_impl_protocol_t proto_;
bool enabled_;
fbl::Mutex cb_lock_;
serial_notify_t callback_ TA_GUARDED(cb_lock_) = {nullptr, nullptr};
serial_state_t state_;
char read_buffer_[kBufferLength];
char write_buffer_[kBufferLength];
size_t write_buffer_length_;
size_t total_written_bytes_ = 0;
zx::event write_event_;
};
class SerialTester {
public:
SerialTester() {
fake_parent_->AddProtocol(ZX_PROTOCOL_SERIAL_IMPL, serial_impl_.proto()->ops,
serial_impl_.proto()->ctx);
}
FakeSerialImpl& serial_impl() { return serial_impl_; }
zx_device_t* fake_parent() { return fake_parent_.get(); }
private:
std::shared_ptr<MockDevice> fake_parent_ = MockDevice::FakeRootParent();
FakeSerialImpl serial_impl_;
};
TEST(SerialTest, InitNoProtocolParent) {
// SerialTester is intentionally not defined in this scope as it would
// define the ZX_PROTOCOL_SERIAL_IMPL protocol.
auto fake_parent = MockDevice::FakeRootParent();
serial::SerialDevice device(fake_parent.get());
ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, device.Init());
}
TEST(SerialTest, Init) {
SerialTester tester;
serial::SerialDevice device(tester.fake_parent());
ASSERT_EQ(ZX_OK, device.Init());
}
TEST(SerialTest, DdkLifetime) {
SerialTester tester;
serial::SerialDevice* device(new serial::SerialDevice(tester.fake_parent()));
ASSERT_EQ(ZX_OK, device->Init());
ASSERT_EQ(ZX_OK, device->Bind());
device->DdkAsyncRemove();
ASSERT_EQ(ZX_OK, mock_ddk::ReleaseFlaggedDevices(tester.fake_parent()));
}
TEST(SerialTest, DdkRelease) {
SerialTester tester;
serial::SerialDevice* device(new serial::SerialDevice(tester.fake_parent()));
FakeSerialImpl& serial_impl = tester.serial_impl();
ASSERT_EQ(ZX_OK, device->Init());
// Manually set enabled to true.
serial_impl.SerialImplEnable(true);
EXPECT_TRUE(serial_impl.enabled());
device->DdkRelease();
EXPECT_FALSE(serial_impl.enabled());
ASSERT_EQ(nullptr, serial_impl.callback()->callback);
}
// Provides control primitives for tests that issue IO requests to the device.
class SerialDeviceTest : public zxtest::Test {
public:
SerialDeviceTest();
~SerialDeviceTest() override;
serial::SerialDevice* device() { return device_; }
FakeSerialImpl& serial_impl() { return tester_.serial_impl(); }
// DISALLOW_COPY_ASSIGN_AND_MOVE(SerialDeviceTest);
private:
SerialTester tester_;
serial::SerialDevice* device_;
};
SerialDeviceTest::SerialDeviceTest() {
device_ = new serial::SerialDevice(tester_.fake_parent());
if (ZX_OK != device_->Init()) {
delete device_;
device_ = nullptr;
}
}
SerialDeviceTest::~SerialDeviceTest() { device_->DdkRelease(); }
TEST_F(SerialDeviceTest, Read) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_serial::Device>();
ASSERT_OK(endpoints);
auto& [client_end, server] = endpoints.value();
fidl::BindServer(loop.dispatcher(), std::move(server), device());
fidl::WireClient client(std::move(client_end), loop.dispatcher());
constexpr std::string_view data = "test";
// Test set up.
*std::copy(data.begin(), data.end(), serial_impl().read_buffer()) = 0;
serial_impl().set_state_and_notify(SERIAL_STATE_READABLE);
// Test.
client->Read().ThenExactlyOnce(
[want = data](fidl::WireUnownedResult<fuchsia_hardware_serial::Device::Read>& result) {
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const cpp20::span data = response.value()->data.get();
const std::string_view got{reinterpret_cast<const char*>(data.data()), data.size_bytes()};
ASSERT_EQ(got, want);
});
ASSERT_OK(loop.RunUntilIdle());
}
TEST_F(SerialDeviceTest, Write) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_serial::Device>();
ASSERT_OK(endpoints);
auto& [client_end, server] = endpoints.value();
fidl::BindServer(loop.dispatcher(), std::move(server), device());
fidl::WireClient client(std::move(client_end), loop.dispatcher());
constexpr std::string_view data = "test";
uint8_t payload[data.size()];
std::copy(data.begin(), data.end(), payload);
// Test set up.
serial_impl().set_state_and_notify(SERIAL_STATE_WRITABLE);
// Test.
client->Write(fidl::VectorView<uint8_t>::FromExternal(payload))
.ThenExactlyOnce(
[this,
want = data](fidl::WireUnownedResult<fuchsia_hardware_serial::Device::Write>& result) {
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const std::string_view got{serial_impl().write_buffer(),
serial_impl().write_buffer_length()};
ASSERT_EQ(got, want);
});
ASSERT_OK(loop.RunUntilIdle());
}
} // namespace