blob: 4b1c91639d2a9d46725d7d468674b2850829c625 [file] [log] [blame]
// 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