blob: 9072e3825710efee7e2ce541ebeea5a26bbe3323 [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.
#ifndef LIB_UART_UART_H_
#define LIB_UART_UART_H_
#include <lib/arch/intrin.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <zircon/assert.h>
#include <zircon/boot/driver-config.h>
#include <zircon/boot/image.h>
#include <cstdlib>
#include <optional>
#include <string_view>
#include <type_traits>
#include <utility>
#include <hwreg/mmio.h>
#include <hwreg/pio.h>
#include "chars-from.h"
namespace uart {
// This template is specialized by payload configuration type (see below).
// It parses bits out of strings from the "kernel.serial" boot option.
template <typename Config>
inline std::optional<Config> ParseConfig(std::string_view) {
static_assert(std::is_void_v<Config>, "missing specialization");
return {};
// This template is specialized by payload configuration type (see below).
// It recreates a string for Parse.
template <typename Config>
inline void UnparseConfig(const Config& config, FILE* out) {
static_assert(std::is_void_v<Config>, "missing specialization");
// Specific hardware support is implemented in a class uart::xyz::Driver,
// referred to here as UartDriver. The uart::DriverBase template class
// provides a helper base class for UartDriver implementations.
// The UartDriver object represents the hardware itself. Many UartDriver
// classes hold no state other than the initial configuration data used in the
// constructor, but a UartDriver is not required to be stateless. However, a
// UartDriver is required to be copy-constructible, trivially destructible,
// and contain no pointers. This makes it safe to copy an object set up by
// physboot into a new object in the virtual-memory kernel to hand off the
// configuration and the state of the hardware.
// All access to the UartDriver object is serialized by its caller, so it does
// no synchronization of its own. This serves to serialize the actual access
// to the hardware.
// The UartDriver API fills four roles:
// 1. Match a ZBI item that configures this driver.
// 2. Generate a ZBI item for another kernel to match this configuration.
// 3. Configure the IoProvider (see below).
// 4. Drive the actual hardware.
// The first three are handled by DriverBase. The KdrvExtra and KdrvConfig
// template arguments give the KDRV_* value and the dcfg_*_t type for the ZBI
// item. The Pio template argument tells the IoProvider whether this driver
// uses MMIO or PIO (including PIO via MMIO): the number of consecutive PIO
// ports used, or 0 for simple MMIO.
// Item matching is done by the MaybeCreate static method. If the item
// matches KdrvExtra, then the UartDriver(KdrvConfig) constructor is called.
// DriverBase provides this constructor to fill the cfg_ field, which the
// UartDriver can then refer to. The UartDriver copy constructor copies it.
// The calls to drive the hardware are all template functions passed an
// IoProvider object (see below). The driver accesses device registers using
// hwreg ReadFrom and WriteTo calls on the pointers from the provider. The
// IoProvider constructed is passed uart.config() and uart.pio_size().
template <typename Driver, uint32_t KdrvExtra, typename KdrvConfig, uint16_t Pio = 0>
class DriverBase {
using config_type = KdrvConfig;
DriverBase() = delete;
DriverBase(const DriverBase&) = default;
explicit DriverBase(const config_type& cfg) : cfg_(cfg) {}
constexpr bool operator==(const Driver& other) const {
return memcmp(&cfg_, &other.cfg_, sizeof(cfg_)) == 0;
constexpr bool operator!=(const Driver& other) const { return !(*this == other); }
// API to fill a ZBI item describing this UART.
constexpr uint32_t type() const { return ZBI_TYPE_KERNEL_DRIVER; }
constexpr uint32_t extra() const { return KdrvExtra; }
constexpr size_t size() const { return sizeof(cfg_); }
void FillItem(void* payload) const { memcpy(payload, &cfg_, sizeof(cfg_)); }
// API to match a ZBI item describing this UART.
static std::optional<Driver> MaybeCreate(const zbi_header_t& header, const void* payload) {
static_assert(alignof(config_type) <= ZBI_ALIGNMENT);
if (header.type == ZBI_TYPE_KERNEL_DRIVER && header.extra == KdrvExtra &&
header.length >= sizeof(config_type)) {
return Driver{*reinterpret_cast<const config_type*>(payload)};
return {};
// API to match a configuration string.
static std::optional<Driver> MaybeCreate(std::string_view string) {
const auto config_name = Driver::config_name();
if (string.substr(0, config_name.size()) == config_name) {
auto config = ParseConfig<KdrvConfig>(string);
if (config) {
return Driver{*config};
return {};
// API to reproduce a configuration string.
void Unparse(FILE* out) const {
fprintf(out, "%.*s", static_cast<int>(Driver::config_name().size()),
UnparseConfig<KdrvConfig>(cfg_, out);
// API for use in IoProvider setup.
const config_type& config() const { return cfg_; }
constexpr uint16_t pio_size() const { return Pio; }
config_type cfg_;
template <typename T>
static constexpr bool Uninstantiated = false;
// uart::KernelDriver API
// These are here just to cause errors if Driver is missing its methods.
// They also serve to document the API required by uart::KernelDriver.
// They should all be overridden by Driver methods.
// Each method is a template parameterized by an hwreg-compatible type for
// accessing the hardware registers via hwreg ReadFrom and WriteTo methods.
// This lets Driver types be used with hwreg::Mock in tests independent of
// actual hardware access.
template <typename IoProvider>
void Init(IoProvider& io) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing Init");
// Return true if Write can make forward progress right now.
// The return value can be anything contextually convertible to bool.
// The value will be passed on to Write.
template <typename IoProvider>
bool TxReady(IoProvider& io) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing TxReady");
return false;
// This is called only when TxReady() has just returned something that
// converts to true; that's passed here so it can convey more information
// such as a count. Advance the iterator at least one and as many as is
// convenient but not past end, outputting each character before advancing.
template <typename IoProvider, typename It1, typename It2>
auto Write(IoProvider& io, bool ready, It1 it, const It2& end) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing Write");
return end;
// Poll for an incoming character and return one if there is one.
template <typename IoProvider>
std::optional<uint8_t> Read(IoProvider& io) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing Read");
return {};
// Set the UART up to deliver interrupts. This is called after Init.
// After this, Interrupt (below) may be called from interrupt context.
template <typename IoProvider>
void InitInterrupt(IoProvider& io) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing InitInterrupt");
// Enable transmit interrupts so Interrupt will be called when TxReady().
template <typename IoProvider>
void EnableTxInterrupt(IoProvider& io) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing EnableTxInterrupt");
// Service an interrupt. Call tx() if transmit has become ready.
// If receive has become ready, call rx(read_char, full) one or more
// times, where read_char() -> uint8_t if there is receive buffer
// space and full() -> void if there is no space.
template <typename IoProvider, typename Tx, typename Rx>
void Interrupt(IoProvider& io, Tx&& tx, Rx&& rx) {
static_assert(Uninstantiated<IoProvider>, "derived class is missing Interrupt");
// The IoProvider is a template class parameterized by UartDriver::config_type,
// i.e. the dcfg_*_t type for the ZBI item's format. This class is responsible
// for supplying pointers to be passed to hwreg types' ReadFrom and WriteTo.
// The IoProvider(UartDriver::config_type, uint16_t pio_size) constructor
// initializes the object. Then IoProvider::io() is called to yield the
// pointer to pass to hwreg calls.
// uart::BasicIoProvider handles the simple case where physical MMIO and PIO
// base addresses are used directly. It also provides base classes that can be
// subclassed with an overridden constructor that does address mapping.
template <typename Config>
class BasicIoProvider;
// This is the default "identity mapping" callback for BasicIoProvider::Init.
// A subclass can have an Init function that calls BasicIoProvider::Init with
// a different callback function.
inline auto DirectMapMmio(uint64_t phys) { return reinterpret_cast<volatile void*>(phys); }
// The specialization used most commonly handles simple MMIO devices.
template <>
class BasicIoProvider<dcfg_simple_t> {
// Just install the MMIO base pointer. The third argument can be passed by
// a subclass constructor method to map the physical address to a virtual
// address.
template <typename T>
BasicIoProvider(const dcfg_simple_t& cfg, uint16_t pio_size, T&& map_mmio) {
auto ptr = map_mmio(cfg.mmio_phys);
if (pio_size != 0) {
// This is PIO via MMIO, i.e. scaled MMIO.
} else {
// This is normal MMIO.
BasicIoProvider(const dcfg_simple_t& cfg, uint16_t pio_size)
: BasicIoProvider(cfg, pio_size, DirectMapMmio) {}
auto* io() { return &io_; }
std::variant<hwreg::RegisterMmio, hwreg::RegisterPio> io_{std::in_place_index<0>, nullptr};
// The specialization for devices using actual PIO only occurs on x86.
#if defined(__x86_64__) || defined(__i386__)
template <>
class BasicIoProvider<dcfg_simple_pio_t> {
BasicIoProvider(const dcfg_simple_pio_t& cfg, uint16_t pio_size) : io_(cfg.base) {
ZX_DEBUG_ASSERT(pio_size > 0);
auto* io() { return &io_; }
hwreg::RegisterDirectPio io_;
// The specialization for devices requiring two separate MMIO areas
// provides an additional entry point for the second one.
template <>
class BasicIoProvider<dcfg_soc_uart_t> {
// The third argument can be passed by a subclass's constructor.
template <typename T>
BasicIoProvider(const dcfg_soc_uart_t& cfg, uint16_t pio_size, T&& map_mmio)
: soc_mmio_(map_mmio(cfg.soc_mmio_phys)), uart_mmio_(map_mmio(cfg.uart_mmio_phys)) {
ZX_DEBUG_ASSERT(pio_size == 0);
BasicIoProvider(const dcfg_soc_uart_t& cfg, uint16_t pio_size)
: BasicIoProvider(cfg, pio_size, DirectMapMmio) {}
auto* io() { return &uart_mmio_; }
auto* soc_io() { return &soc_mmio_; }
hwreg::RegisterMmio soc_mmio_, uart_mmio_;
// The Sync class provides synchronization around the UartDriver.
// This is the degenerate case of the uart::KernelDriver Sync API.
// It busy-waits with no locking.
struct TA_CAP("uart") Unsynchronized {
// This is returned by lock() and passed back to unlock().
struct InterruptState {};
// The Sync object is normally default-constructed.
Unsynchronized() = default;
// The constructor argument is the UartDriver object. This is only used
// by uart::mock::Sync so other implementations use a template just to
// ignore the argument without specializing the whole class on its type.
template <typename T>
explicit Unsynchronized(const T&) {}
// This is the normal pair, used in "process context", i.e. where
// interrupts might happen. unlock takes the state returned by lock.
[[nodiscard]] const InterruptState lock() TA_ACQ() { return {}; }
void unlock(InterruptState) TA_REL() {}
// Wait for a good time to check again. Implementations that actually
// block pending an interrupt first call enable_tx_interrupt(), then
// unlock to block, and finally relock when woken before return.
template <typename T>
InterruptState Wait(InterruptState, T&& enable_tx_interrupt) TA_REQ(this) {
return {};
// In blocking implementations, the interrupt handler calls this. It should
// statically never be reached in an instantiation using this implementation,
// but static_assert for that only works in templates.
void Wake() TA_REQ(this) { ZX_PANIC("uart::Unsynchronized::Wake() should never be called"); }
// Forward declaration.
namespace mock {
class Driver;
// The KernelDriver template class is parameterized by those three to implement
// actual driver logic for some environment.
// The KernelDriver constructor just passes its arguments through to the
// UartDriver constructor. So it can be created directly from a configuration
// struct or copied from another UartDriver object. In this way, the device is
// handed off from one KernelDriver instantiation to a different one using a
// different IoProvider (physboot vs kernel) and/or Sync (polling vs blocking).
template <class UartDriver, template <typename> class IoProvider, class Sync>
class KernelDriver {
using uart_type = UartDriver;
static_assert(std::is_trivially_destructible_v<uart_type> ||
std::is_same_v<uart_type, mock::Driver>);
// This sets up the object but not the device itself. The device might
// already have been set up by a previous instantiation's Init function,
// or might never actually be set up because this instantiation gets
// replaced with a different one before ever calling Init.
template <typename... Args>
explicit KernelDriver(Args&&... args)
: uart_(std::forward<Args>(args)...), io_(uart_.config(), uart_.pio_size()) {}
// Access underlying hardware driver object.
const auto& uart() const { return uart_; }
auto& uart() { return uart_; }
// Access IoProvider object.
auto& io() { return io_; }
// Set up the device for nonblocking output and polling input.
// If the device is handed off from a different instantiation,
// this won't be called in the new instantiation.
void Init() {
Guard lock(sync_);
// TODO(mcgrathr): Add InitInterrupt for enabling interrupt-based i/o.
// This is the FILE-compatible API: `FILE::stdout_ = FILE{&driver};`.
int Write(std::string_view str) {
uart::CharsFrom chars(str); // Massage into uint8_t with \n -> CRLF.
auto it = chars.begin();
Guard lock(sync_);
while (it != chars.end()) {
// Wait until the UART is ready for Write.
auto ready = uart_.TxReady(io_);
while (!ready) {
// Block or just unlock and spin or whatever "wait" means to Sync.
// If that means blocking for interrupt wakeup, enable tx interrupts.
lock.Wait([this]() TA_REQ(sync_) { uart_.EnableTxInterrupt(io_); });
ready = uart_.TxReady(io_);
// Advance the iterator by writing some.
it = uart_.Write(io_, std::move(ready), it, chars.end());
return static_cast<int>(str.size());
// This is a direct polling read, not used in interrupt-based operation.
auto Read() {
Guard lock(sync_);
return uart_.Read(io_);
friend Sync;
uart_type uart_ TA_GUARDED(sync_);
IoProvider<typename uart_type::config_type> io_;
Sync sync_{uart_};
class TA_SCOPED_CAP Guard {
template <typename... T>
__WARN_UNUSED_CONSTRUCTOR explicit Guard(Sync& sync, T... args) TA_ACQ(sync) TA_ACQ(sync_)
: sync_(sync), state_(sync_.lock(std::forward<T>(args)...)) {}
~Guard() TA_REL() { sync_.unlock(std::move(state_)); }
template <typename T>
void Wait(T&& enable) TA_REQ(sync_) {
state_ = sync_.Wait(std::move(state_), std::forward<T>(enable));
void Wake() TA_REQ(sync_) { sync_.Wake(); }
Sync& sync_;
typename Sync::InterruptState state_;
// These specializations are defined in the library to cover all the ZBI item
// payload types used by the various drivers.
template <>
std::optional<dcfg_simple_t> ParseConfig<dcfg_simple_t>(std::string_view string);
template <>
void UnparseConfig(const dcfg_simple_t& config, FILE* out);
template <>
std::optional<dcfg_simple_pio_t> ParseConfig<dcfg_simple_pio_t>(std::string_view string);
template <>
void UnparseConfig(const dcfg_simple_pio_t& config, FILE* out);
template <>
std::optional<dcfg_soc_uart_t> ParseConfig<dcfg_soc_uart_t>(std::string_view string);
template <>
void UnparseConfig(const dcfg_soc_uart_t& config, FILE* out);
} // namespace uart
#endif // LIB_UART_UART_H_