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
This commit is contained in:
Andrew Turner 2024-04-03 12:46:43 -04:00 committed by Mark Johnston
parent afc10f8bba
commit f3003a0dfb
2 changed files with 404 additions and 1 deletions

View file

@ -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_ */

394
usr.sbin/bhyve/uart_pl011.c Normal file
View file

@ -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));
}