Files
qemu/hw/char/sifive_uart.c
Frank Chang e07077a766 hw/char: Check interrupt after txctrl register is written
It's possible that the transmit watermark level (txctrl.txcnt) is
updated when the user writes to txctrl register, which may decrease the
transmit watermark level to less than the number of entries in the
transmit FIFO. In such a case, the interrupt should be raised so we need to
call sifive_uart_update_irq() to check and update interrupt when txctrl
register is written. Otherwise, the interrupt will have to be delayed
until next TX FIFO transmission is processed.

Suggested-by: Chao Liu <chao.liu.zevorn@gmail.com>
Signed-off-by: Frank Chang <frank.chang@sifive.com>
Reviewed-by: Jim Shu <jim.shu@sifive.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Message-ID: <20260513030503.3665414-1-frank.chang@sifive.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
2026-05-22 09:45:46 +10:00

412 lines
11 KiB
C

/*
* QEMU model of the UART on the SiFive E300 and U500 series SOCs.
*
* Copyright (c) 2016 Stefan O'Rear
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "migration/vmstate.h"
#include "chardev/char.h"
#include "chardev/char-fe.h"
#include "hw/core/irq.h"
#include "hw/char/sifive_uart.h"
#include "hw/core/qdev-properties-system.h"
#define TX_INTERRUPT_TRIGGER_DELAY_NS 100
/* Returns the state of the IP (interrupt pending) register */
static uint32_t sifive_uart_ip(SiFiveUARTState *s)
{
uint32_t ret = 0;
uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
uint32_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl);
if (fifo8_num_used(&s->tx_fifo) < txcnt) {
ret |= SIFIVE_UART_IP_TXWM;
}
if (s->rx_fifo_len > rxcnt) {
ret |= SIFIVE_UART_IP_RXWM;
}
return ret;
}
static void sifive_uart_update_irq(SiFiveUARTState *s)
{
int cond = 0;
uint32_t ip = sifive_uart_ip(s);
if (((ip & SIFIVE_UART_IP_TXWM) && (s->ie & SIFIVE_UART_IE_TXWM)) ||
((ip & SIFIVE_UART_IP_RXWM) && (s->ie & SIFIVE_UART_IE_RXWM))) {
cond = 1;
}
qemu_set_irq(s->irq, cond);
}
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 if transmit is disabled. */
if (!SIFIVE_UART_TXEN(s->txctrl)) {
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_trigger_tx_fifo(SiFiveUARTState *s)
{
uint64_t current_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
if (!timer_pending(s->fifo_trigger_handle)) {
timer_mod(s->fifo_trigger_handle, current_time +
TX_INTERRUPT_TRIGGER_DELAY_NS);
}
}
static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf,
int size)
{
uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
bool update_irq = false;
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.\n");
}
if (size > 0) {
if (fifo8_num_used(&s->tx_fifo) < txcnt &&
(fifo8_num_used(&s->tx_fifo) + size) >= txcnt) {
update_irq = true;
}
fifo8_push_all(&s->tx_fifo, buf, size);
}
if (fifo8_is_full(&s->tx_fifo)) {
s->txfifo |= SIFIVE_UART_TXFIFO_FULL;
}
/*
* Update txwm interrupt pending status when the number of entries
* in the transmit FIFO crosses or reaches the watermark.
*/
if (update_irq) {
sifive_uart_update_irq(s);
}
sifive_uart_trigger_tx_fifo(s);
}
static uint64_t
sifive_uart_read(void *opaque, hwaddr addr, unsigned int size)
{
SiFiveUARTState *s = opaque;
unsigned char r;
switch (addr) {
case SIFIVE_UART_RXFIFO:
if (s->rx_fifo_len) {
r = s->rx_fifo[0];
memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_fifo_len - 1);
s->rx_fifo_len--;
qemu_chr_fe_accept_input(&s->chr);
sifive_uart_update_irq(s);
return r;
}
return 0x80000000;
case SIFIVE_UART_TXFIFO:
return s->txfifo;
case SIFIVE_UART_IE:
return s->ie;
case SIFIVE_UART_IP:
return sifive_uart_ip(s);
case SIFIVE_UART_TXCTRL:
return s->txctrl;
case SIFIVE_UART_RXCTRL:
return s->rxctrl;
case SIFIVE_UART_DIV:
return s->div;
}
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n",
__func__, (int)addr);
return 0;
}
static void
sifive_uart_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
SiFiveUARTState *s = opaque;
uint32_t value = val64;
uint8_t ch = value;
switch (addr) {
case SIFIVE_UART_TXFIFO:
sifive_uart_write_tx_fifo(s, &ch, 1);
return;
case SIFIVE_UART_IE:
s->ie = val64;
sifive_uart_update_irq(s);
return;
case SIFIVE_UART_TXCTRL:
s->txctrl = val64;
if (SIFIVE_UART_TXEN(s->txctrl) && !fifo8_is_empty(&s->tx_fifo)) {
sifive_uart_trigger_tx_fifo(s);
}
sifive_uart_update_irq(s);
return;
case SIFIVE_UART_RXCTRL:
s->rxctrl = val64;
sifive_uart_update_irq(s);
return;
case SIFIVE_UART_DIV:
s->div = val64;
return;
}
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x v=0x%x\n",
__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 = {
.read = sifive_uart_read,
.write = sifive_uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4
}
};
static void sifive_uart_rx(void *opaque, const uint8_t *buf, int size)
{
SiFiveUARTState *s = opaque;
/* Got a byte. */
if (s->rx_fifo_len >= sizeof(s->rx_fifo)) {
printf("WARNING: UART dropped char.\n");
return;
}
s->rx_fifo[s->rx_fifo_len++] = *buf;
sifive_uart_update_irq(s);
}
static int sifive_uart_can_rx(void *opaque)
{
SiFiveUARTState *s = opaque;
return SIFIVE_UART_RXEN(s->rxctrl) && (s->rx_fifo_len < sizeof(s->rx_fifo));
}
static void sifive_uart_event(void *opaque, QEMUChrEvent event)
{
}
static int sifive_uart_be_change(void *opaque)
{
SiFiveUARTState *s = opaque;
qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx,
sifive_uart_event, sifive_uart_be_change, s,
NULL, true);
return 0;
}
static void sifive_uart_reset_enter(Object *obj, ResetType type)
{
SiFiveUARTState *s = SIFIVE_UART(obj);
s->txfifo = 0;
s->ie = 0;
s->txctrl = 0;
s->rxctrl = 0;
s->div = 0;
s->rx_fifo_len = 0;
memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE);
fifo8_reset(&s->tx_fifo);
}
static const Property sifive_uart_properties[] = {
DEFINE_PROP_CHR("chardev", SiFiveUARTState, chr),
};
static void sifive_uart_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
SiFiveUARTState *s = SIFIVE_UART(obj);
memory_region_init_io(&s->mmio, OBJECT(s), &sifive_uart_ops, s,
TYPE_SIFIVE_UART, SIFIVE_UART_MAX);
sysbus_init_mmio(sbd, &s->mmio);
sysbus_init_irq(sbd, &s->irq);
}
static void sifive_uart_realize(DeviceState *dev, Error **errp)
{
SiFiveUARTState *s = SIFIVE_UART(dev);
fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE);
s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL,
fifo_trigger_update, s);
if (qemu_chr_fe_backend_connected(&s->chr)) {
qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx,
sifive_uart_event, sifive_uart_be_change, s,
NULL, true);
}
}
static void sifive_uart_unrealize(DeviceState *dev)
{
SiFiveUARTState *s = SIFIVE_UART(dev);
fifo8_destroy(&s->tx_fifo);
}
static void sifive_uart_reset_hold(Object *obj, ResetType type)
{
SiFiveUARTState *s = SIFIVE_UART(obj);
qemu_irq_lower(s->irq);
}
static const VMStateDescription vmstate_sifive_uart = {
.name = TYPE_SIFIVE_UART,
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT8_ARRAY(rx_fifo, SiFiveUARTState,
SIFIVE_UART_RX_FIFO_SIZE),
VMSTATE_UINT8(rx_fifo_len, SiFiveUARTState),
VMSTATE_UINT32(ie, SiFiveUARTState),
VMSTATE_UINT32(txctrl, SiFiveUARTState),
VMSTATE_UINT32(rxctrl, 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()
},
};
static void sifive_uart_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
ResettableClass *rc = RESETTABLE_CLASS(oc);
dc->realize = sifive_uart_realize;
dc->unrealize = sifive_uart_unrealize;
dc->vmsd = &vmstate_sifive_uart;
rc->phases.enter = sifive_uart_reset_enter;
rc->phases.hold = sifive_uart_reset_hold;
device_class_set_props(dc, sifive_uart_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo sifive_uart_info = {
.name = TYPE_SIFIVE_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SiFiveUARTState),
.instance_init = sifive_uart_init,
.class_init = sifive_uart_class_init,
};
static void sifive_uart_register_types(void)
{
type_register_static(&sifive_uart_info);
}
type_init(sifive_uart_register_types)
/*
* Create UART device.
*/
SiFiveUARTState *sifive_uart_create(MemoryRegion *address_space, hwaddr base,
Chardev *chr, qemu_irq irq)
{
DeviceState *dev;
SysBusDevice *s;
dev = qdev_new("riscv.sifive.uart");
s = SYS_BUS_DEVICE(dev);
qdev_prop_set_chr(dev, "chardev", chr);
sysbus_realize_and_unref(s, &error_fatal);
memory_region_add_subregion(address_space, base,
sysbus_mmio_get_region(s, 0));
sysbus_connect_irq(s, 0, irq);
return SIFIVE_UART(dev);
}