On Fri, May 1, 2026 at 2:04 AM ~lexbaileylowrisc
<[email protected]> wrote:
>
> From: Lex Bailey <[email protected]>
>
> What is a break event:
> Normally, a UART wire is held high by the transmitting device any time the
> communication channel is idle (nothing to transmit). If a receiver does not
> see
> that its receive line is high, this means one of two things:
> 1. there is a transmission happening now
> 2. there is no transmitter connected, and the line is floating or pulled
> low, or the transmitting device is in a bad state or reset state and so
> it has not yet asserted its TX output.
>
> In case 1, the line will go high again very soon.
> In case 2, it is sometimes useful to detect that this is the case and signal
> to
> the software that this has happened (normally by means of an interrupt)
>
> The ot_uart device does exactly this in case 2. It detects that this is
> happening by detecting when the RX pin stays low for an extended period. This
> period can be configured to be 2, 4, 8, or 16 times a character period. The
> character period itself depends on the configuration of the device (baud rate,
> and parity bit)
>
> for more details about how this works, there is documentation about the UART
> break detection on this page:
> https://opentitan.org/book/hw/ip/uart/doc/theory_of_operation.html#rx_break_err
>
> This commit adds a handler for the UART break event, and logic to emulate a
> break when oversampling is enabled and there is a break event.
>
> Signed-off-by: Lex Bailey <[email protected]>
> ---
> docs/system/riscv/opentitan-uart.rst | 43 +++++++++++++
> hw/char/ot_uart.c | 92 +++++++++++++++++++++++++++-
> include/hw/char/ot_uart.h | 4 ++
> 3 files changed, 136 insertions(+), 3 deletions(-)
> create mode 100644 docs/system/riscv/opentitan-uart.rst
>
> diff --git a/docs/system/riscv/opentitan-uart.rst
> b/docs/system/riscv/opentitan-uart.rst
> new file mode 100644
> index 0000000000..7ca596712d
> --- /dev/null
> +++ b/docs/system/riscv/opentitan-uart.rst
We should just add an OpenTitan Machine documentation here and include
the UART there.
> @@ -0,0 +1,43 @@
> +# OpenTitan UART Support
> +
> +## Connecting to the UART
> +
> +* `-serial mon:stdio`, used as the first `-serial` option, redirects the
> virtual UART0 to the
> + current console/shell.
> +
> +* `-chardev socket,id=serial1,host=localhost,port=8001,server=on,wait=off`
> and
> + `-serial chardev:serial1` can be used to redirect UART1 (in this example)
> to a TCP socket. These
> + options are not specific to OpenTitan emulation, but are useful to
> communicate over a UART.
> + Note that QEMU offers many `chardev` backends, please check QEMU
> documentation for details.
> +
> +## Sending Break Conditions
> +
> +Break conditions can be sent to the UART on select supported CharDev
> backends (telnet, mux)
> +or by sending the `chardev-send-break` command with the CharDev ID via the
> QEMU Monitor.
> +Break conditions are treated as transient events and the length of time of a
> break condition
> +is not considered.
> +
> +## Oversampling
> +
> +OpenTitan's UART has a `VAL` register which oversamples the RX pin 16 times
> per bit.
> +This cannot be emulated by QEMU which uses a CharDev backend and does not
> have a notion of
> +accurate sampling times.
> +
> +If software wishes to poll the `VAL` register to determine break conditions,
> there are
> +some properties available to help with emulating this use case:
> +
> +* `-global ot-uart.oversample-break=true` is used to enable UART break
> oversampling.
> + This will attempt to display 16 samples of the last bit received in the
> `VAL` register,
> + which will be 16 high bits after any UART frame is transmitted (as these
> end with a stop
> + bit, which is high), or 16 low bits if the UART previously received a
> break condition
> + and has not received any frames since. That is, enabling this property
> assumes that
> + transmitted break conditions are "held" until the next UART transfer in
> terms of what
> + is being shown in the oversampled `VAL` register.
> +
> +* `-global ot-uart.toggle-break=true` is used to provide more control over
> "holding"
> + the UART RX break condition like a GPIO strap, and changes the behavior of
> a UART
> + such that received break condition events now *toggle* the break condition
> state
> + rather than keeping it asserted until the next transfer. This allows any
> device talking
> + to OpenTitan via UART to have more precise control over when the UART VAL
> register
> + displays idle and when it displays a break condition, as it can precisely
> toggle the
> + break condition on or off like a GPIO strapping being held down.
> diff --git a/hw/char/ot_uart.c b/hw/char/ot_uart.c
> index 3511a42fbe..cdf02da62b 100644
> --- a/hw/char/ot_uart.c
> +++ b/hw/char/ot_uart.c
> @@ -179,6 +179,11 @@ static void ot_uart_receive(void *opaque, const uint8_t
> *buf, int size)
> uint32_t rx_watermark_level;
> size_t count = MIN(fifo8_num_free(&s->rx_fifo), (size_t)size);
>
> + if (size && !s->toggle_break) {
> + /* no longer breaking, so emulate idle in oversampled VAL register */
> + s->in_break = false;
> + }
> +
> for (int index = 0; index < size; index++) {
> fifo8_push(&s->rx_fifo, buf[index]);
> }
> @@ -331,9 +336,40 @@ static void ot_uart_reset_enter(Object *obj, ResetType
> type)
>
> s->char_tx_time = (NANOSECONDS_PER_SECOND / 230400) * 10;
>
> + /*
> + * do not reset `s->in_break`, as that tracks whether we are currently
> + * receiving a break condition over UART RX from some device talking
> + * to OpenTitan, which should survive resets. The QEMU CharDev only
> + * supports transient break events and not the notion of holding the
> + * UART in break, so remembering breaks like this is required to
> + * support mocking of break conditions in the oversampled `VAL` reg.
> + */
> + if (s->in_break) {
> + /* ignore CTRL.RXBLVL as we have no notion of break "time" */
> + s->regs[R_INTR_STATE] |= INTR_RX_BREAK_ERR_MASK;
> + }
> +
> ot_uart_update_irqs(s);
> }
>
> +static void ot_uart_event_handler(void *opaque, QEMUChrEvent event)
> +{
> + OtUARTState *s = opaque;
> +
> + if (event == CHR_EVENT_BREAK) {
> + if (!s->in_break || !s->oversample_break) {
> + /* ignore CTRL.RXBLVL as we have no notion of break "time" */
> + s->regs[R_INTR_STATE] |= INTR_RX_BREAK_ERR_MASK;
> + ot_uart_update_irqs(s);
> + /* emulate break in the oversampled VAL register */
> + s->in_break = true;
> + } else if (s->toggle_break) {
> + /* emulate toggling break off in the oversampled VAL register */
> + s->in_break = false;
> + }
> + }
> +}
> +
> static uint8_t ot_uart_read_rx_fifo(OtUARTState *s)
> {
> uint8_t val;
> @@ -355,6 +391,17 @@ static uint8_t ot_uart_read_rx_fifo(OtUARTState *s)
> return val;
> }
>
> +static gboolean ot_uart_watch_cb(void *do_not_use, GIOCondition cond,
> + void *opaque)
> +{
> + OtUARTState *s = opaque;
> +
> + s->watch_tag = 0;
> + ot_uart_xmit(s);
> +
> + return FALSE;
> +}
> +
> static uint64_t ot_uart_get_baud(OtUARTState *s)
> {
> uint64_t baud;
> @@ -424,6 +471,26 @@ static uint64_t ot_uart_read(void *opaque, hwaddr addr,
> unsigned int size)
> break;
>
> case R_VAL:
> + /*
> + * This is not trivially implemented due to the QEMU UART
> + * interface. There is no way to reliably sample or oversample
> + * given our emulated interface, but some software might poll the
> + * value of this register to determine break conditions.
> + *
> + * As such, default to reporting 16 of the last sample received
> + * instead. This defaults to 16 idle high samples (as a stop bit is
> + * always the last received), except for when the `oversample-break`
> + * property is set and a break condition is received over UART RX,
> + * where we then show 16 low samples until the next valid UART
> + * transmission is received (or break is toggled off with the
> + * `toggle-break` property enabled). This will not be accurate, but
> + * should be sufficient to support basic software flows that
> + * essentially use UART break as a strapping mechanism.
> + */
> + retvalue = (s->in_break && s->oversample_break) ? 0u : UINT16_MAX;
> + qemu_log_mask(LOG_UNIMP, "%s: VAL only shows idle%s\n", __func__,
> + (s->oversample_break ? "/break" : ""));
> + break;
> case R_OVRD:
> case R_TIMEOUT_CTRL:
> retvalue = s->regs[reg];
> @@ -607,8 +674,27 @@ static const VMStateDescription vmstate_ot_uart = {
>
> static const Property ot_uart_properties[] = {
> DEFINE_PROP_CHR("chardev", OtUARTState, chr),
> + DEFINE_PROP_BOOL("oversample-break", OtUARTState, oversample_break,
> false),
> + DEFINE_PROP_BOOL("toggle-break", OtUARTState, toggle_break, false),
Why have these disabled by default?
Alistair