hw/char: sifive_uart: Print uart characters async

The current approach of using qemu_chr_fe_write() and ignoring the
return values results in dropped characters [1].

Let's update the SiFive UART to use a async sifive_uart_xmit() function
to transmit the characters and apply back pressure to the guest with
the SIFIVE_UART_TXFIFO_FULL status.

This should avoid dropped characters and more realisticly model the
hardware.

1: https://gitlab.com/qemu-project/qemu/-/issues/2114

Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
Tested-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Message-ID: <20240910045419.1252277-3-alistair.francis@wdc.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
This commit is contained in:
Alistair Francis 2024-08-15 10:57:28 +10:00
parent 4a0e8ca322
commit 53c1557b23
2 changed files with 105 additions and 8 deletions

View File

@ -26,6 +26,8 @@
#include "hw/char/sifive_uart.h" #include "hw/char/sifive_uart.h"
#include "hw/qdev-properties-system.h" #include "hw/qdev-properties-system.h"
#define TX_INTERRUPT_TRIGGER_DELAY_NS 100
/* /*
* Not yet implemented: * Not yet implemented:
* *
@ -64,6 +66,72 @@ static void sifive_uart_update_irq(SiFiveUARTState *s)
} }
} }
static gboolean sifive_uart_xmit(void *do_not_use, GIOCondition cond,
void *opaque)
{
SiFiveUARTState *s = opaque;
int ret;
const uint8_t *characters;
uint32_t numptr = 0;
/* instant drain the fifo when there's no back-end */
if (!qemu_chr_fe_backend_connected(&s->chr)) {
fifo8_reset(&s->tx_fifo);
return G_SOURCE_REMOVE;
}
if (fifo8_is_empty(&s->tx_fifo)) {
return G_SOURCE_REMOVE;
}
/* Don't pop the FIFO in case the write fails */
characters = fifo8_peek_bufptr(&s->tx_fifo,
fifo8_num_used(&s->tx_fifo), &numptr);
ret = qemu_chr_fe_write(&s->chr, characters, numptr);
if (ret >= 0) {
/* We wrote the data, actually pop the fifo */
fifo8_pop_bufptr(&s->tx_fifo, ret, NULL);
}
if (!fifo8_is_empty(&s->tx_fifo)) {
guint r = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
sifive_uart_xmit, s);
if (!r) {
fifo8_reset(&s->tx_fifo);
return G_SOURCE_REMOVE;
}
}
/* Clear the TX Full bit */
if (!fifo8_is_full(&s->tx_fifo)) {
s->txfifo &= ~SIFIVE_UART_TXFIFO_FULL;
}
sifive_uart_update_irq(s);
return G_SOURCE_REMOVE;
}
static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf,
int size)
{
uint64_t current_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
if (size > fifo8_num_free(&s->tx_fifo)) {
size = fifo8_num_free(&s->tx_fifo);
qemu_log_mask(LOG_GUEST_ERROR, "sifive_uart: TX FIFO overflow");
}
fifo8_push_all(&s->tx_fifo, buf, size);
if (fifo8_is_full(&s->tx_fifo)) {
s->txfifo |= SIFIVE_UART_TXFIFO_FULL;
}
timer_mod(s->fifo_trigger_handle, current_time +
TX_INTERRUPT_TRIGGER_DELAY_NS);
}
static uint64_t static uint64_t
sifive_uart_read(void *opaque, hwaddr addr, unsigned int size) sifive_uart_read(void *opaque, hwaddr addr, unsigned int size)
{ {
@ -82,7 +150,7 @@ sifive_uart_read(void *opaque, hwaddr addr, unsigned int size)
return 0x80000000; return 0x80000000;
case SIFIVE_UART_TXFIFO: case SIFIVE_UART_TXFIFO:
return 0; /* Should check tx fifo */ return s->txfifo;
case SIFIVE_UART_IE: case SIFIVE_UART_IE:
return s->ie; return s->ie;
case SIFIVE_UART_IP: case SIFIVE_UART_IP:
@ -106,12 +174,10 @@ sifive_uart_write(void *opaque, hwaddr addr,
{ {
SiFiveUARTState *s = opaque; SiFiveUARTState *s = opaque;
uint32_t value = val64; uint32_t value = val64;
unsigned char ch = value;
switch (addr) { switch (addr) {
case SIFIVE_UART_TXFIFO: case SIFIVE_UART_TXFIFO:
qemu_chr_fe_write(&s->chr, &ch, 1); sifive_uart_write_tx_fifo(s, (uint8_t *) &value, 1);
sifive_uart_update_irq(s);
return; return;
case SIFIVE_UART_IE: case SIFIVE_UART_IE:
s->ie = val64; s->ie = val64;
@ -131,6 +197,13 @@ sifive_uart_write(void *opaque, hwaddr addr,
__func__, (int)addr, (int)value); __func__, (int)addr, (int)value);
} }
static void fifo_trigger_update(void *opaque)
{
SiFiveUARTState *s = opaque;
sifive_uart_xmit(NULL, G_IO_OUT, s);
}
static const MemoryRegionOps sifive_uart_ops = { static const MemoryRegionOps sifive_uart_ops = {
.read = sifive_uart_read, .read = sifive_uart_read,
.write = sifive_uart_write, .write = sifive_uart_write,
@ -197,6 +270,9 @@ static void sifive_uart_realize(DeviceState *dev, Error **errp)
{ {
SiFiveUARTState *s = SIFIVE_UART(dev); SiFiveUARTState *s = SIFIVE_UART(dev);
s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL,
fifo_trigger_update, s);
qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx,
sifive_uart_event, sifive_uart_be_change, s, sifive_uart_event, sifive_uart_be_change, s,
NULL, true); NULL, true);
@ -206,12 +282,18 @@ static void sifive_uart_realize(DeviceState *dev, Error **errp)
static void sifive_uart_reset_enter(Object *obj, ResetType type) static void sifive_uart_reset_enter(Object *obj, ResetType type)
{ {
SiFiveUARTState *s = SIFIVE_UART(obj); SiFiveUARTState *s = SIFIVE_UART(obj);
s->txfifo = 0;
s->ie = 0; s->ie = 0;
s->ip = 0; s->ip = 0;
s->txctrl = 0; s->txctrl = 0;
s->rxctrl = 0; s->rxctrl = 0;
s->div = 0; s->div = 0;
s->rx_fifo_len = 0; s->rx_fifo_len = 0;
memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE);
fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE);
} }
static void sifive_uart_reset_hold(Object *obj, ResetType type) static void sifive_uart_reset_hold(Object *obj, ResetType type)
@ -222,8 +304,8 @@ static void sifive_uart_reset_hold(Object *obj, ResetType type)
static const VMStateDescription vmstate_sifive_uart = { static const VMStateDescription vmstate_sifive_uart = {
.name = TYPE_SIFIVE_UART, .name = TYPE_SIFIVE_UART,
.version_id = 1, .version_id = 2,
.minimum_version_id = 1, .minimum_version_id = 2,
.fields = (const VMStateField[]) { .fields = (const VMStateField[]) {
VMSTATE_UINT8_ARRAY(rx_fifo, SiFiveUARTState, VMSTATE_UINT8_ARRAY(rx_fifo, SiFiveUARTState,
SIFIVE_UART_RX_FIFO_SIZE), SIFIVE_UART_RX_FIFO_SIZE),
@ -233,6 +315,9 @@ static const VMStateDescription vmstate_sifive_uart = {
VMSTATE_UINT32(txctrl, SiFiveUARTState), VMSTATE_UINT32(txctrl, SiFiveUARTState),
VMSTATE_UINT32(rxctrl, SiFiveUARTState), VMSTATE_UINT32(rxctrl, SiFiveUARTState),
VMSTATE_UINT32(div, SiFiveUARTState), VMSTATE_UINT32(div, SiFiveUARTState),
VMSTATE_UINT32(txfifo, SiFiveUARTState),
VMSTATE_FIFO8(tx_fifo, SiFiveUARTState),
VMSTATE_TIMER_PTR(fifo_trigger_handle, SiFiveUARTState),
VMSTATE_END_OF_LIST() VMSTATE_END_OF_LIST()
}, },
}; };

View File

@ -24,6 +24,7 @@
#include "hw/qdev-properties.h" #include "hw/qdev-properties.h"
#include "hw/sysbus.h" #include "hw/sysbus.h"
#include "qom/object.h" #include "qom/object.h"
#include "qemu/fifo8.h"
enum { enum {
SIFIVE_UART_TXFIFO = 0, SIFIVE_UART_TXFIFO = 0,
@ -48,9 +49,13 @@ enum {
SIFIVE_UART_IP_RXWM = 2 /* Receive watermark interrupt pending */ SIFIVE_UART_IP_RXWM = 2 /* Receive watermark interrupt pending */
}; };
#define SIFIVE_UART_TXFIFO_FULL 0x80000000
#define SIFIVE_UART_GET_TXCNT(txctrl) ((txctrl >> 16) & 0x7) #define SIFIVE_UART_GET_TXCNT(txctrl) ((txctrl >> 16) & 0x7)
#define SIFIVE_UART_GET_RXCNT(rxctrl) ((rxctrl >> 16) & 0x7) #define SIFIVE_UART_GET_RXCNT(rxctrl) ((rxctrl >> 16) & 0x7)
#define SIFIVE_UART_RX_FIFO_SIZE 8 #define SIFIVE_UART_RX_FIFO_SIZE 8
#define SIFIVE_UART_TX_FIFO_SIZE 8
#define TYPE_SIFIVE_UART "riscv.sifive.uart" #define TYPE_SIFIVE_UART "riscv.sifive.uart"
OBJECT_DECLARE_SIMPLE_TYPE(SiFiveUARTState, SIFIVE_UART) OBJECT_DECLARE_SIMPLE_TYPE(SiFiveUARTState, SIFIVE_UART)
@ -63,13 +68,20 @@ struct SiFiveUARTState {
qemu_irq irq; qemu_irq irq;
MemoryRegion mmio; MemoryRegion mmio;
CharBackend chr; CharBackend chr;
uint8_t rx_fifo[SIFIVE_UART_RX_FIFO_SIZE];
uint8_t rx_fifo_len; uint32_t txfifo;
uint32_t ie; uint32_t ie;
uint32_t ip; uint32_t ip;
uint32_t txctrl; uint32_t txctrl;
uint32_t rxctrl; uint32_t rxctrl;
uint32_t div; uint32_t div;
uint8_t rx_fifo[SIFIVE_UART_RX_FIFO_SIZE];
uint8_t rx_fifo_len;
Fifo8 tx_fifo;
QEMUTimer *fifo_trigger_handle;
}; };
SiFiveUARTState *sifive_uart_create(MemoryRegion *address_space, hwaddr base, SiFiveUARTState *sifive_uart_create(MemoryRegion *address_space, hwaddr base,