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