mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-15 12:54:27 +00:00
e10b9d6602
Currently, lock of uart in bhyve is placed in frontend. There are some problems about it: 1. If every frontend should has a lock, why not move it inside backend as they all have same uart_softc. 2. If backend needs to modify the information of uart after initialize, it will be impossible as backend cannot use lock. For example, if we want implement a telnet support for uart in backend, It should wait for connection when initialize. After some remote process connect it, it needs to modify rfd and wfd in backend. So I decide to move it to backend. Reviewed by: corvink, jhb, markj Differential Revision: https://reviews.freebsd.org/D44947
391 lines
9 KiB
C
391 lines
9 KiB
C
/*-
|
|
* 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 <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;
|
|
|
|
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
|
|
*/
|
|
uart_softc_lock(sc->backend);
|
|
|
|
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);
|
|
|
|
uart_softc_unlock(sc->backend);
|
|
}
|
|
|
|
void
|
|
uart_pl011_write(struct uart_pl011_softc *sc, int offset, uint32_t value)
|
|
{
|
|
bool loopback;
|
|
|
|
uart_softc_lock(sc->backend);
|
|
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);
|
|
uart_softc_unlock(sc->backend);
|
|
}
|
|
|
|
uint32_t
|
|
uart_pl011_read(struct uart_pl011_softc *sc, int offset)
|
|
{
|
|
uint32_t reg;
|
|
int fifo_sz;
|
|
|
|
reg = 0;
|
|
uart_softc_lock(sc->backend);
|
|
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);
|
|
uart_softc_unlock(sc->backend);
|
|
|
|
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();
|
|
|
|
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));
|
|
}
|