git: f3003a0dfb91 - main - bhyve: Add PL011 UART emulation

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Wed, 10 Apr 2024 15:18:58 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=f3003a0dfb91ce887587ff73ce758f31a921e5a3

commit f3003a0dfb91ce887587ff73ce758f31a921e5a3
Author:     Andrew Turner <andrew@freebsd.org>
AuthorDate: 2024-04-03 16:46:43 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2024-04-10 15:17:55 +0000

    bhyve: Add PL011 UART emulation
    
    This will be use for arm64 guests, instead of the existing ns16550 UART
    model.
    
    Reviewed by:    corvink, jhb
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D40997
---
 usr.sbin/bhyve/uart_emul.h  |  11 +-
 usr.sbin/bhyve/uart_pl011.c | 394 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 404 insertions(+), 1 deletion(-)

diff --git a/usr.sbin/bhyve/uart_emul.h b/usr.sbin/bhyve/uart_emul.h
index 3487af9e1cb1..129314153d3f 100644
--- a/usr.sbin/bhyve/uart_emul.h
+++ b/usr.sbin/bhyve/uart_emul.h
@@ -32,6 +32,7 @@
 #define	UART_NS16550_IO_BAR_SIZE	8
 
 struct uart_ns16550_softc;
+struct uart_pl011_softc;
 struct vm_snapshot_meta;
 
 typedef void (*uart_intr_func_t)(void *arg);
@@ -49,4 +50,12 @@ int	uart_ns16550_tty_open(struct uart_ns16550_softc *sc,
 int	uart_ns16550_snapshot(struct uart_ns16550_softc *sc,
 	    struct vm_snapshot_meta *meta);
 #endif
-#endif
+
+uint32_t uart_pl011_read(struct uart_pl011_softc *sc, int offset);
+void	uart_pl011_write(struct uart_pl011_softc *sc, int offset,
+	    uint32_t value);
+struct uart_pl011_softc *uart_pl011_init(uart_intr_func_t intr_assert,
+	    uart_intr_func_t intr_deassert, void *arg);
+int	uart_pl011_tty_open(struct uart_pl011_softc *sc, const char *device);
+
+#endif /* _UART_EMUL_H_ */
diff --git a/usr.sbin/bhyve/uart_pl011.c b/usr.sbin/bhyve/uart_pl011.c
new file mode 100644
index 000000000000..e2d5c8bf5657
--- /dev/null
+++ b/usr.sbin/bhyve/uart_pl011.c
@@ -0,0 +1,394 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Andrew Turner
+ *
+ * This work was supported by Innovate UK project 105694, "Digital Security
+ * by Design (DSbD) Technology Platform Prototype".
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "uart_backend.h"
+#include "uart_emul.h"
+
+#define	UART_FIFO_SIZE		16
+
+#define	UARTDR			0x00
+#define	UARTDR_RSR_SHIFT	8
+
+#define	UARTRSR			0x01
+#define	UARTRSR_OE		(1 << 3)
+
+#define	UARTFR			0x06
+#define	UARTFR_TXFE		(1 << 7)
+#define	UARTFR_RXFF		(1 << 6)
+#define	UARTFR_TXFF		(1 << 5)
+#define	UARTFR_RXFE		(1 << 4)
+
+#define	UARTRTINTR		(1 << 6)
+#define	UARTTXINTR		(1 << 5)
+#define	UARTRXINTR		(1 << 4)
+
+#define	UARTIBRD		0x09
+
+#define	UARTFBRD		0x0a
+#define	UARTFBRD_MASK		0x003f
+
+#define	UARTLCR_H		0x0b
+#define	UARTLCR_H_MASK		0x00ff
+#define	UARTLCR_H_FEN		(1 << 4)
+
+#define	UARTCR			0x0c
+/* TODO: Check the flags in the UARTCR register */
+#define	UARTCR_MASK		0xffc7
+#define	UARTCR_LBE		(1 << 7)
+
+#define	UARTIFLS		0x0d
+#define	UARTIFLS_MASK		0x003f
+#define	UARTIFLS_RXIFLSEL(x)	(((x) >> 3) & 0x7)
+#define	UARTIFLS_TXIFLSEL(x)	(((x) >> 0) & 0x7)
+
+#define	UARTIMSC		0x0e
+#define	UARTIMSC_MASK		0x07ff
+
+#define	UARTRIS			0x0f
+#define	UARTMIS			0x10
+
+#define	UARTICR			0x11
+
+#define	UARTPeriphID		0x00241011
+#define	UARTPeriphID0		0x3f8
+#define	UARTPeriphID0_VAL	(((UARTPeriphID) >>  0) & 0xff)
+#define	UARTPeriphID1		0x3f9
+#define	UARTPeriphID1_VAL	(((UARTPeriphID) >>  8) & 0xff)
+#define	UARTPeriphID2		0x3fa
+#define	UARTPeriphID2_VAL	(((UARTPeriphID) >> 16) & 0xff)
+#define	UARTPeriphID3		0x3fb
+#define	UARTPeriphID3_VAL	(((UARTPeriphID) >> 24) & 0xff)
+
+#define	UARTPCellID		0xb105f00d
+#define	UARTPCellID0		0x3fc
+#define	UARTPCellID0_VAL	(((UARTPCellID) >>  0) & 0xff)
+#define	UARTPCellID1		0x3fd
+#define	UARTPCellID1_VAL	(((UARTPCellID) >>  8) & 0xff)
+#define	UARTPCellID2		0x3fe
+#define	UARTPCellID2_VAL	(((UARTPCellID) >> 16) & 0xff)
+#define	UARTPCellID3		0x3ff
+#define	UARTPCellID3_VAL	(((UARTPCellID) >> 24) & 0xff)
+
+struct uart_pl011_softc {
+	struct uart_softc *backend;
+	pthread_mutex_t mtx;	/* protects all softc elements */
+
+	uint16_t	irq_state;
+
+	uint16_t	rsr;
+
+	uint16_t	cr;
+	uint16_t	ifls;
+	uint16_t	imsc;
+	uint16_t	lcr_h;
+
+	uint16_t	ibrd;
+	uint16_t	fbrd;
+
+	void	*arg;
+	uart_intr_func_t intr_assert;
+	uart_intr_func_t intr_deassert;
+};
+
+static void
+uart_reset(struct uart_pl011_softc *sc)
+{
+	sc->ifls = 0x12;
+
+	/* no fifo until enabled by software */
+	uart_rxfifo_reset(sc->backend, 1);
+}
+
+static int
+uart_rx_trigger_level(struct uart_pl011_softc *sc)
+{
+	/* If the FIFO is disabled trigger when we have any data */
+	if ((sc->lcr_h & UARTLCR_H_FEN) != 0)
+		return (1);
+
+	/* Trigger base on how full the fifo is */
+	switch (UARTIFLS_RXIFLSEL(sc->ifls)) {
+	case 0:
+		return (UART_FIFO_SIZE / 8);
+	case 1:
+		return (UART_FIFO_SIZE / 4);
+	case 2:
+		return (UART_FIFO_SIZE / 2);
+	case 3:
+		return (UART_FIFO_SIZE * 3 / 4);
+	case 4:
+		return (UART_FIFO_SIZE * 7 / 8);
+	default:
+		/* TODO: Find out what happens in this case */
+		return (UART_FIFO_SIZE);
+	}
+}
+
+static void
+uart_toggle_intr(struct uart_pl011_softc *sc)
+{
+	if ((sc->irq_state & sc->imsc) == 0)
+		(*sc->intr_deassert)(sc->arg);
+	else
+		(*sc->intr_assert)(sc->arg);
+}
+
+static void
+uart_drain(int fd __unused, enum ev_type ev, void *arg)
+{
+	struct uart_pl011_softc *sc;
+	int old_size, trig_lvl;
+	bool loopback;
+
+	sc = arg;
+
+	assert(ev == EVF_READ);
+
+	/*
+	 * This routine is called in the context of the mevent thread
+	 * to take out the softc lock to protect against concurrent
+	 * access from a vCPU i/o exit
+	 */
+	pthread_mutex_lock(&sc->mtx);
+
+	old_size = uart_rxfifo_numchars(sc->backend);
+
+	loopback = (sc->cr & UARTCR_LBE) != 0;
+	uart_rxfifo_drain(sc->backend, loopback);
+
+	/* If we cross the trigger level raise UARTRXINTR */
+	trig_lvl = uart_rx_trigger_level(sc);
+	if (old_size < trig_lvl &&
+	    uart_rxfifo_numchars(sc->backend) >= trig_lvl)
+		sc->irq_state |= UARTRXINTR;
+
+	if (uart_rxfifo_numchars(sc->backend) > 0)
+		sc->irq_state |= UARTRTINTR;
+	if (!loopback)
+		uart_toggle_intr(sc);
+
+	pthread_mutex_unlock(&sc->mtx);
+}
+
+void
+uart_pl011_write(struct uart_pl011_softc *sc, int offset, uint32_t value)
+{
+	bool loopback;
+
+	pthread_mutex_lock(&sc->mtx);
+	switch (offset) {
+	case UARTDR:
+		loopback = (sc->cr & UARTCR_LBE) != 0;
+		if (uart_rxfifo_putchar(sc->backend, value & 0xff, loopback))
+			sc->rsr |= UARTRSR_OE;
+
+		/* We don't have a TX fifo, so trigger when we have data */
+		sc->irq_state |= UARTTXINTR;
+		break;
+	case UARTRSR:
+		/* Any write clears this register */
+		sc->rsr = 0;
+		break;
+	case UARTFR:
+		/* UARTFR is a read-only register */
+		break;
+	/* TODO: UARTILPR */
+	case UARTIBRD:
+		sc->ibrd = value;
+		break;
+	case UARTFBRD:
+		sc->fbrd = value & UARTFBRD_MASK;
+		break;
+	case UARTLCR_H:
+		/* Check if the FIFO enable bit changed */
+		if (((sc->lcr_h ^ value) & UARTLCR_H_FEN) != 0) {
+			if ((value & UARTLCR_H_FEN) != 0) {
+				uart_rxfifo_reset(sc->backend, UART_FIFO_SIZE);
+			} else {
+				uart_rxfifo_reset(sc->backend, 1);
+			}
+		}
+		sc->lcr_h = value & UARTLCR_H_MASK;
+		break;
+	case UARTCR:
+		sc->cr = value & UARTCR_MASK;
+		break;
+	case UARTIFLS:
+		sc->ifls = value & UARTCR_MASK;
+		break;
+	case UARTIMSC:
+		sc->imsc = value & UARTIMSC_MASK;
+		break;
+	case UARTRIS:
+	case UARTMIS:
+		/* UARTRIS and UARTMIS are read-only registers */
+		break;
+	case UARTICR:
+		sc->irq_state &= ~value;
+		break;
+	default:
+		/* Ignore writes to unassigned/ID registers */
+		break;
+	}
+	uart_toggle_intr(sc);
+	pthread_mutex_unlock(&sc->mtx);
+}
+
+uint32_t
+uart_pl011_read(struct uart_pl011_softc *sc, int offset)
+{
+	uint32_t reg;
+	int fifo_sz;
+
+	reg = 0;
+	pthread_mutex_lock(&sc->mtx);
+	switch (offset) {
+	case UARTDR:
+		reg = uart_rxfifo_getchar(sc->backend);
+		/* Deassert the irq if below the trigger level */
+		fifo_sz = uart_rxfifo_numchars(sc->backend);
+		if (fifo_sz < uart_rx_trigger_level(sc))
+			sc->irq_state &= ~UARTRXINTR;
+		if (fifo_sz == 0)
+			sc->irq_state &= ~UARTRTINTR;
+
+		reg |= sc->rsr << UARTDR_RSR_SHIFT;
+
+		/* After reading from the fifo there is now space in it */
+		sc->rsr &= UARTRSR_OE;
+		break;
+	case UARTRSR:
+		/* Any write clears this register */
+		reg = sc->rsr;
+		break;
+	case UARTFR:
+		/* Transmit is intstant, so the fifo is always empty */
+		reg = UARTFR_TXFE;
+
+		/* Set the receive fifo full/empty flags */
+		fifo_sz = uart_rxfifo_numchars(sc->backend);
+		if (fifo_sz == UART_FIFO_SIZE)
+			reg |= UARTFR_RXFF;
+		else if (fifo_sz == 0)
+			reg |= UARTFR_RXFE;
+		break;
+	/* TODO: UARTILPR */
+	case UARTIBRD:
+		reg = sc->ibrd;
+		break;
+	case UARTFBRD:
+		reg = sc->fbrd;
+		break;
+	case UARTLCR_H:
+		reg = sc->lcr_h;
+		break;
+	case UARTCR:
+		reg = sc->cr;
+		break;
+	case UARTIMSC:
+		reg = sc->imsc;
+		break;
+	case UARTRIS:
+		reg = sc->irq_state;
+		break;
+	case UARTMIS:
+		reg = sc->irq_state & sc->imsc;
+		break;
+	case UARTICR:
+		reg = 0;
+		break;
+	case UARTPeriphID0:
+		reg = UARTPeriphID0_VAL;
+		break;
+	case UARTPeriphID1:
+		reg =UARTPeriphID1_VAL;
+		break;
+	case UARTPeriphID2:
+		reg = UARTPeriphID2_VAL;
+		break;
+	case UARTPeriphID3:
+		reg = UARTPeriphID3_VAL;
+		break;
+	case UARTPCellID0:
+		reg = UARTPCellID0_VAL;
+		break;
+	case UARTPCellID1:
+		reg = UARTPCellID1_VAL;
+		break;
+	case UARTPCellID2:
+		reg = UARTPCellID2_VAL;
+		break;
+	case UARTPCellID3:
+		reg = UARTPCellID3_VAL;
+		break;
+	default:
+		/* Return 0 in reads from unasigned registers */
+		reg = 0;
+		break;
+	}
+	uart_toggle_intr(sc);
+	pthread_mutex_unlock(&sc->mtx);
+
+	return (reg);
+}
+
+struct uart_pl011_softc *
+uart_pl011_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert,
+    void *arg)
+{
+	struct uart_pl011_softc *sc;
+
+	sc = calloc(1, sizeof(struct uart_pl011_softc));
+
+	sc->arg = arg;
+	sc->intr_assert = intr_assert;
+	sc->intr_deassert = intr_deassert;
+	sc->backend = uart_init();
+
+	pthread_mutex_init(&sc->mtx, NULL);
+
+	uart_reset(sc);
+
+	return (sc);
+}
+
+int
+uart_pl011_tty_open(struct uart_pl011_softc *sc, const char *device)
+{
+	return (uart_tty_open(sc->backend, device, uart_drain, sc));
+}