|  | // Copyright Microsoft and CHERIoT Contributors. | 
|  | // SPDX-License-Identifier: MIT | 
|  |  | 
|  | #pragma once | 
|  |  | 
|  | #include <concepts> | 
|  | #include <stdint.h> | 
|  |  | 
|  | /** | 
|  | * Concept for checking that a UART driver exposes the right interface. | 
|  | */ | 
|  | template<typename T> | 
|  | concept IsUart = requires(volatile T *v, uint8_t byte) | 
|  | { | 
|  | {v->init()}; | 
|  | { | 
|  | v->can_write() | 
|  | } -> std::same_as<bool>; | 
|  | { | 
|  | v->can_read() | 
|  | } -> std::same_as<bool>; | 
|  | { | 
|  | v->blocking_read() | 
|  | } -> std::same_as<uint8_t>; | 
|  | {v->blocking_write(byte)}; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Generic 16550A memory-mapped register layout. | 
|  | * | 
|  | * The registers are 8 bits wide, but typically the bus supports only 4-byte | 
|  | * (or larger) transactions and so they are padded to a 32-bit word.  The | 
|  | * template parameter allows this to be controlled. | 
|  | */ | 
|  | template<typename RegisterType = uint32_t> | 
|  | class Uart16550 | 
|  | { | 
|  | static void no_custom_init() {} | 
|  |  | 
|  | public: | 
|  | /** | 
|  | * The interface to the read/write FIFOs for this UART. | 
|  | * | 
|  | * This is also the low byte of the divisor when the divisor latch (bit 7 of | 
|  | * the `lineControl`) is set. | 
|  | */ | 
|  | RegisterType data; | 
|  | /** | 
|  | * Interrupt-enabled control / status.  Write 1 to enabled, 0 to disable, to | 
|  | * each of the low four bits: | 
|  | * | 
|  | * 0: Data-receive interrupt | 
|  | * 1: Transmit holding register empty interrupt | 
|  | * 2: Receive line status interrupts | 
|  | * 3: Modem status interrupts. | 
|  | * | 
|  | * When bit 7 of `lineControl` is set, this is instead the | 
|  | * divisor-latch-high register and stores the high 8 bits of the divisor. | 
|  | */ | 
|  | RegisterType intrEnable; | 
|  | /** | 
|  | * Interrupt identification and FIFO enable/disable. | 
|  | * | 
|  | * We only care about the low bit here, which enables the FIFO. | 
|  | */ | 
|  | RegisterType intrIDandFifo; | 
|  | /** | 
|  | * Specifies properties of the line (stop bit, parity, and so on).  The | 
|  | * only bit that we use is bit 7, the divisor latch, which enables writing | 
|  | * the speed. | 
|  | */ | 
|  | RegisterType lineControl; | 
|  | /** | 
|  | * Modem control. | 
|  | */ | 
|  | RegisterType modemControl; | 
|  | /** | 
|  | * The line status word.  The bits that we care about are: | 
|  | * | 
|  | * 0: Receive ready | 
|  | * 5: Transmit buffer empty | 
|  | */ | 
|  | const RegisterType LineStatus; | 
|  | /** | 
|  | * Modem status. | 
|  | */ | 
|  | const RegisterType ModemStatus; | 
|  | /** | 
|  | * Scratch register.  Unused. | 
|  | */ | 
|  | RegisterType scratch; | 
|  |  | 
|  | /** | 
|  | * Returns true if the transmit buffer is empty. | 
|  | */ | 
|  | __always_inline bool can_write() volatile | 
|  | { | 
|  | #ifdef SIMULATION | 
|  | // If we're in a simulator, we're running *much* slower than the thing | 
|  | // handling the UART, so assume that you can always write to the UART. | 
|  | return true; | 
|  | #else | 
|  | return LineStatus & (1 << 5); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if the receive buffer is not. | 
|  | */ | 
|  | __always_inline bool can_read() volatile | 
|  | { | 
|  | return LineStatus & (1 << 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Read one byte, blocking until a byte is available. | 
|  | */ | 
|  | uint8_t blocking_read() volatile | 
|  | { | 
|  | while (!can_read()) {} | 
|  | return data; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Write one byte, blocking until the byte is written. | 
|  | */ | 
|  | void blocking_write(uint8_t byte) volatile | 
|  | { | 
|  | while (!can_write()) {} | 
|  | data = byte; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialise the UART. | 
|  | */ | 
|  | template<typename T = decltype(no_custom_init)> | 
|  | void init(int divisor = 1, T &&otherSetup = no_custom_init) volatile | 
|  | { | 
|  | // Disable interrupts | 
|  | intrEnable = 0x00; | 
|  | // Set the divisor latch (we're going to write the divisor) and set the | 
|  | // character width to 8 bits, one stop bit, no parity. | 
|  | lineControl = 0x83; | 
|  | // Set the divisor | 
|  | data       = divisor & 0xff; | 
|  | intrEnable = (divisor >> 8) & 0xff; | 
|  | // Run any other setup that we were asked to do. | 
|  | otherSetup(); | 
|  | // Clear the divisor latch | 
|  | lineControl = 0x03; | 
|  | // Enable the FIFO and reset | 
|  | // Enabled bits: | 
|  | // 0 - Enable FIFOs | 
|  | // 1 - Clear receive FIFO | 
|  | // 2 - Clear send FIFO | 
|  | // 5 - 64-byte FIFO (if available) | 
|  | intrIDandFifo = 0x1; | 
|  | } | 
|  | }; | 
|  |  | 
|  | // A platform can provide a custom version of this. | 
|  | #ifndef CHERIOT_PLATFORM_CUSTOM_UART | 
|  | /// The default UART type. | 
|  | using Uart = Uart16550<uint32_t>; | 
|  | // Check that our UART matches the concept. | 
|  | static_assert(IsUart<Uart>); | 
|  | #endif |