| // 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 |