blob: a0d20054ad372ed557c46aa112537f807fda9821 [file] [log] [blame]
// Copyright 2020 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 <lib/devicetree/devicetree.h>
#include <lib/stdcompat/array.h>
#include <lib/stdcompat/span.h>
#include <lib/uart/all.h>
#include <lib/uart/amlogic.h>
#include <lib/uart/mock.h>
#include <lib/uart/ns8250.h>
#include <lib/uart/null.h>
#include <lib/uart/pl011.h>
#include <lib/uart/uart.h>
#include <lib/zbi-format/driver-config.h>
#include <array>
#include <string_view>
#include <type_traits>
#include <zxtest/zxtest.h>
using namespace std::literals;
namespace {
TEST(UartTests, Nonblocking) {
uart::KernelDriver<uart::mock::Driver, uart::mock::IoProvider, uart::mock::SyncPolicy> driver;
driver.uart()
.ExpectLock()
.ExpectInit()
.ExpectUnlock()
// First Write call -> sends all chars, no waiting.
.ExpectLock()
.ExpectTxReady(true)
.ExpectWrite("hi!"sv)
.ExpectUnlock()
// Second Write call -> sends half, then waits.
.ExpectLock()
.ExpectTxReady(true)
.ExpectWrite("hello "sv)
.ExpectTxReady(false)
.ExpectWait(false)
.ExpectTxReady(true)
.ExpectWrite("world\r\n"sv)
.ExpectUnlock();
driver.Init<uart::mock::Locking>();
EXPECT_EQ(driver.Write<uart::mock::Locking>("hi!"), 3);
EXPECT_EQ(driver.Write<uart::mock::Locking>("hello world\n"), 12);
}
TEST(UartTests, LockPolicy) {
uart::KernelDriver<uart::mock::Driver, uart::mock::IoProvider, uart::mock::SyncPolicy> driver;
driver.uart()
.ExpectLock()
.ExpectInit()
.ExpectUnlock()
// First Write call -> sends all chars, no waiting.
.ExpectTxReady(true)
.ExpectWrite("hi!"sv)
// Second Write call -> sends half, then waits.
.ExpectTxReady(true)
.ExpectWrite("hello "sv)
.ExpectTxReady(false)
.ExpectWait(false)
.ExpectTxReady(true)
.ExpectWrite("world\r\n"sv);
driver.Init<uart::mock::Locking>();
// Just check that lock args are forwarded correctly.
EXPECT_EQ(driver.Write<uart::mock::NoopLocking>("hi!"), 3);
EXPECT_EQ(driver.Write<uart::mock::NoopLocking>("hello world\n"), 12);
}
TEST(UartTests, Blocking) {
uart::KernelDriver<uart::mock::Driver, uart::mock::IoProvider, uart::mock::SyncPolicy> driver;
driver.uart()
.ExpectLock()
.ExpectInit()
.ExpectUnlock()
// First Write call -> sends all chars, no waiting.
.ExpectLock()
.ExpectTxReady(true)
.ExpectWrite("hi!"sv)
.ExpectUnlock()
// Second Write call -> sends half, then waits.
.ExpectLock()
.ExpectTxReady(true)
.ExpectWrite("hello "sv)
.ExpectTxReady(false)
.ExpectWait(true)
.ExpectAssertHeld()
.ExpectEnableTxInterrupt()
.ExpectTxReady(true)
.ExpectWrite("world\r\n"sv)
.ExpectUnlock();
driver.Init<uart::mock::Locking>();
EXPECT_EQ(driver.Write<uart::mock::Locking>("hi!"), 3);
EXPECT_EQ(driver.Write<uart::mock::Locking>("hello world\n"), 12);
}
TEST(UartTests, Null) {
uart::KernelDriver<uart::null::Driver, uart::mock::IoProvider, uart::UnsynchronizedPolicy> driver;
// Unsynchronized LockPolicy is dropped.
driver.Init();
EXPECT_EQ(driver.Write("hi!"), 3);
EXPECT_EQ(driver.Write("hello world\n"), 12);
EXPECT_FALSE(driver.Read());
}
TEST(UartTests, All) {
using AllDriver = uart::all::KernelDriver<uart::mock::IoProvider, uart::UnsynchronizedPolicy>;
AllDriver driver;
// Match against ZBI items to instantiate.
EXPECT_FALSE(driver.Match(zbi_header_t{}, nullptr));
// Make sure Unparse is instantiated.
driver.Unparse();
// Use selected driver.
driver.Visit([](auto&& driver) {
driver.template Init();
EXPECT_EQ(driver.template Write("hi!"), 3);
});
// Transfer state to a new instantiation and pick up using it.
AllDriver newdriver{driver.uart()};
newdriver.Visit([](auto&& driver) {
EXPECT_EQ(driver.template Write("hello world\n"), 12);
EXPECT_FALSE(driver.template Read());
});
}
auto as_uint8 = [](auto& val) {
using byte_type = std::conditional_t<std::is_const_v<std::remove_reference_t<decltype(val)>>,
const uint8_t, uint8_t>;
return cpp20::span<byte_type>(reinterpret_cast<byte_type*>(&val), sizeof(val));
};
auto append = [](auto& vec, auto&& other) { vec.insert(vec.end(), other.begin(), other.end()); };
uint32_t byte_swap(uint32_t val) {
if constexpr (cpp20::endian::native == cpp20::endian::big) {
return val;
} else {
auto bytes = as_uint8(val);
return static_cast<uint32_t>(bytes[0]) << 24 | static_cast<uint32_t>(bytes[1]) << 16 |
static_cast<uint32_t>(bytes[2]) << 8 | static_cast<uint32_t>(bytes[3]);
}
}
// Small helper so we can verify the behavior of CachedProperties.
struct PropertyBuilder {
devicetree::Properties Build() {
return devicetree::Properties(
{property_block.data(), property_block.size()},
std::string_view(reinterpret_cast<const char*>(string_block.data()), string_block.size()));
}
void Add(std::string_view name, uint32_t value) {
uint32_t name_off = byte_swap(static_cast<uint32_t>(string_block.size()));
// String must be null terminated.
append(string_block, name);
string_block.push_back('\0');
uint32_t len = byte_swap(sizeof(uint32_t));
if (!property_block.empty()) {
const uint32_t kFdtPropToken = byte_swap(0x00000003);
append(property_block, as_uint8(kFdtPropToken));
}
// this are all 32b aliagned, no padding need.
append(property_block, as_uint8(len));
append(property_block, as_uint8(name_off));
uint32_t be_value = byte_swap(value);
append(property_block, as_uint8(be_value));
}
void Add(std::string_view key, cpp20::span<const std::string_view> str_list) {
constexpr std::array<uint8_t, sizeof(uint32_t) - 1> kPadding = {};
uint32_t name_off = byte_swap(static_cast<uint32_t>(string_block.size()));
// String must be null terminated.
append(string_block, key);
string_block.push_back('\0');
// Add the null terminator.
uint32_t len = 0;
for (std::string_view str : str_list) {
// Include terminator.
len += str.length() + 1;
}
cpp20::span<const uint8_t> padding;
if (auto remainder = len % sizeof(uint32_t); remainder != 0) {
padding = cpp20::span(kPadding).subspan(0, sizeof(uint32_t) - remainder);
}
len = byte_swap(len);
if (!property_block.empty()) {
const uint32_t kFdtPropToken = byte_swap(0x00000003);
append(property_block, as_uint8(kFdtPropToken));
}
append(property_block, as_uint8(len));
append(property_block, as_uint8(name_off));
for (auto str : str_list) {
append(property_block, str);
property_block.push_back('\0');
}
append(property_block, padding);
}
std::vector<uint8_t> property_block;
std::vector<uint8_t> string_block;
};
TEST(UartTests, MatchCompatible) {
using AllDrivers =
std::variant<uart::null::Driver, uart::pl011::Driver, uart::ns8250::Mmio32Driver,
uart::ns8250::Mmio8Driver, uart::ns8250::Dw8250Driver, uart::ns8250::PioDriver,
uart::amlogic::Driver>;
using AllDriver =
uart::all::KernelDriver<uart::mock::IoProvider, uart::UnsynchronizedPolicy, AllDrivers>;
AllDriver driver;
zbi_dcfg_simple_t dcfg = {
.mmio_phys = 1,
.irq = 2,
};
auto visit = [](auto&& visitor) {
auto actual_visitor = [visitor = std::move(visitor)](auto&& driver) {
using DriverType = std::decay_t<decltype(driver.uart())>;
if constexpr (!std::is_same_v<uart::null::Driver, DriverType> &&
!std::is_same_v<uart::internal::DummyDriver, DriverType>) {
if constexpr (std::is_same_v<zbi_dcfg_simple_t,
std::decay_t<decltype(driver.uart().config())>>) {
visitor(driver);
} else {
FAIL("Unexpected dcfg_simple_pio_t.");
}
} else {
FAIL("Unexpected uart::null::Driver.");
}
};
return actual_visitor;
};
constexpr std::array kCompatibles = {"foo,bar"sv, "ns16550a"sv};
{
// Match no reg shift or io width
PropertyBuilder builder;
builder.Add("compatible", kCompatibles);
auto props = builder.Build();
devicetree::PropertyDecoder decoder(props);
// Arbitrary range of string views.
auto emplacer = driver.MatchDevicetree(decoder);
EXPECT_TRUE(emplacer);
emplacer(dcfg);
driver.Visit(visit([&](auto&& driver) {
EXPECT_EQ(driver.uart().extra(), ZBI_KERNEL_DRIVER_I8250_MMIO8_UART);
EXPECT_EQ(driver.uart().config_name(), uart::ns8250::Mmio8Driver::config_name());
EXPECT_EQ(driver.uart().config().mmio_phys, 1);
EXPECT_EQ(driver.uart().config().irq, 2);
}));
}
// Match with reg shift and io width
{
// Match no reg shift or io width
PropertyBuilder builder;
builder.Add("compatible", kCompatibles);
builder.Add("reg-shift", 2);
builder.Add("reg-io-width", 4);
auto props = builder.Build();
devicetree::PropertyDecoder decoder(props);
// Arbitrary range of string views.
auto emplacer = driver.MatchDevicetree(decoder);
EXPECT_TRUE(emplacer);
emplacer(dcfg);
driver.Visit(visit([&](auto&& driver) {
EXPECT_EQ(driver.uart().extra(), ZBI_KERNEL_DRIVER_I8250_MMIO32_UART);
EXPECT_EQ(driver.uart().config_name(), uart::ns8250::Mmio32Driver::config_name());
EXPECT_EQ(driver.uart().config().mmio_phys, 1);
EXPECT_EQ(driver.uart().config().irq, 2);
}));
}
// Match Dw8250
constexpr std::array kDwCompatibles = {"foo,bar"sv, "snps,dw-apb-uart"sv};
{
PropertyBuilder builder;
builder.Add("compatible", kDwCompatibles);
auto props = builder.Build();
devicetree::PropertyDecoder decoder(props);
// Arbitrary range of string views. Must provide io width and reg shift.
EXPECT_FALSE(driver.MatchDevicetree(decoder));
}
{
// Match no reg shift or io width
PropertyBuilder builder;
builder.Add("compatible", kDwCompatibles);
builder.Add("reg-shift", 2);
builder.Add("reg-io-width", 4);
auto props = builder.Build();
devicetree::PropertyDecoder decoder(props);
// Arbitrary range of string views.
auto emplacer = driver.MatchDevicetree(decoder);
EXPECT_TRUE(emplacer);
emplacer(dcfg);
driver.Visit(visit([&](auto&& driver) {
EXPECT_EQ(driver.uart().extra(), ZBI_KERNEL_DRIVER_DW8250_UART);
EXPECT_EQ(driver.uart().config_name(), uart::ns8250::Dw8250Driver::config_name());
EXPECT_EQ(driver.uart().config().mmio_phys, 1);
EXPECT_EQ(driver.uart().config().irq, 2);
}));
}
{
// Match no reg shift or io width
PropertyBuilder builder;
builder.Add("compatible", cpp20::to_array<std::string_view>({"foo", "arm,pl011"}));
auto props = builder.Build();
devicetree::PropertyDecoder decoder(props);
// Arbitrary range of string views.
auto emplacer = driver.MatchDevicetree(decoder);
EXPECT_TRUE(emplacer);
emplacer(dcfg);
driver.Visit(visit([&](auto&& driver) {
EXPECT_EQ(driver.uart().extra(), ZBI_KERNEL_DRIVER_PL011_UART);
EXPECT_EQ(driver.uart().config_name(), uart::pl011::Driver::config_name());
EXPECT_EQ(driver.uart().config().mmio_phys, 1);
EXPECT_EQ(driver.uart().config().irq, 2);
}));
}
{
// Match no reg shift or io width
PropertyBuilder builder;
builder.Add("compatible", cpp20::to_array<std::string_view>({"foo", "amlogic,meson-gx-uart"}));
auto props = builder.Build();
devicetree::PropertyDecoder decoder(props);
// Arbitrary range of string views.
auto emplacer = driver.MatchDevicetree(decoder);
EXPECT_TRUE(emplacer);
emplacer(dcfg);
driver.Visit(visit([&](auto&& driver) {
EXPECT_EQ(driver.uart().extra(), ZBI_KERNEL_DRIVER_AMLOGIC_UART);
EXPECT_EQ(driver.uart().config_name(), uart::amlogic::Driver::config_name());
EXPECT_EQ(driver.uart().config().mmio_phys, 1);
EXPECT_EQ(driver.uart().config().irq, 2);
}));
}
}
} // namespace