Model the ASCLIN in UART mode. TX writes go to the chardev backend, RX fills a ring buffer. FLAGS tracks FIFO status and three sysbus IRQ lines (TX, RX, ERR) are pulsed on flag edges.
Originally-by: David Brenken <[email protected]> Signed-off-by: Parthiban Nallathambi <[email protected]> --- hw/char/Kconfig | 3 + hw/char/meson.build | 1 + hw/char/tricore_asclin.c | 457 +++++++++++++++++++++++++++++++++++++++ include/hw/char/tricore_asclin.h | 89 ++++++++ 4 files changed, 550 insertions(+) diff --git a/hw/char/Kconfig b/hw/char/Kconfig index 020c0a84bb..5e540241af 100644 --- a/hw/char/Kconfig +++ b/hw/char/Kconfig @@ -95,3 +95,6 @@ config IP_OCTAL_232 bool default y depends on IPACK + +config TRICORE_ASCLIN + bool diff --git a/hw/char/meson.build b/hw/char/meson.build index fc3d7ee506..4a7deb3ece 100644 --- a/hw/char/meson.build +++ b/hw/char/meson.build @@ -35,6 +35,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_UART', if_true: files('sifive_uart.c')) system_ss.add(when: 'CONFIG_SH_SCI', if_true: files('sh_serial.c')) system_ss.add(when: 'CONFIG_STM32F2XX_USART', if_true: files('stm32f2xx_usart.c')) system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c')) +system_ss.add(when: 'CONFIG_TRICORE_ASCLIN', if_true: files('tricore_asclin.c')) system_ss.add(when: 'CONFIG_MCHP_PFSOC_MMUART', if_true: files('mchp_pfsoc_mmuart.c')) system_ss.add(when: 'CONFIG_HTIF', if_true: files('riscv_htif.c')) system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c')) diff --git a/hw/char/tricore_asclin.c b/hw/char/tricore_asclin.c new file mode 100644 index 0000000000..724dddb38c --- /dev/null +++ b/hw/char/tricore_asclin.c @@ -0,0 +1,457 @@ +/* + * TriCore ASCLIN (Asynchronous/Synchronous Interface) UART controller + * + * Copyright (c) 2017 David Brenken <[email protected]> + * Copyright (c) 2024 Siemens AG + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "chardev/char-fe.h" +#include "chardev/char-serial.h" +#include "migration/vmstate.h" +#include "hw/char/tricore_asclin.h" +#include "hw/core/qdev-properties-system.h" + +/* + * Register offsets (byte address / 4) for TC3x ASCLIN. + * Each register occupies one 4-byte slot from 0x00 to 0x50. + */ +enum { + R_CLC = 0, + R_IOCR, + R_ID, + R_TXFIFOCON, + R_RXFIFOCON, + R_BITCON, + R_FRAMECON, + R_DATCON, + R_BRG, + R_BRD, + R_LINCON, + R_LINBTIMER, + R_LINHTIMER, + R_FLAGS, + R_FLAGSSET, + R_FLAGSCLEAR, + R_FLAGSENABLE, + R_TXDATA, + R_RXDATA, + R_CSR, + R_RXDATAD, +}; + +static void asclin_rx_buf_reset(TriCoreASCLINState *s) +{ + memset(s->rxbuf, 0, ASCLIN_RX_BUF_SIZE); + s->rx_rdidx = 0; + s->rx_wridx = 0; +} + +static uint32_t asclin_rx_buf_used(TriCoreASCLINState *s) +{ + return ((s->rx_wridx + ASCLIN_RX_BUF_SIZE) - s->rx_rdidx) + % ASCLIN_RX_BUF_SIZE; +} + +static uint32_t asclin_rx_buf_free(TriCoreASCLINState *s) +{ + return (ASCLIN_RX_BUF_SIZE - 1) - asclin_rx_buf_used(s); +} + +/* + * Pulse the appropriate IRQ line for each flag bit that is both + * set and enabled. The TriCore interrupt router is edge-triggered. + */ +static void asclin_pulse_irq(TriCoreASCLINState *s, uint32_t pulse_mask) +{ + uint32_t fired = pulse_mask & s->regs[R_FLAGSENABLE]; + + if (fired & ASCLIN_TX_INT_MASK) { + qemu_irq_pulse(s->irq_tx); + } + if (fired & ASCLIN_RX_INT_MASK) { + qemu_irq_pulse(s->irq_rx); + } + if (fired & ASCLIN_ERR_INT_MASK) { + qemu_irq_pulse(s->irq_err); + } +} + +/* + * Watch callback: retries TX when the chardev was previously busy. + */ +static gboolean asclin_tx_watch(void *do_not_use, GIOCondition cond, + void *opaque) +{ + TriCoreASCLINState *s = TRICORE_ASCLIN(opaque); + int ret; + + s->watch_tag = 0; + + ret = qemu_chr_fe_write_all(&s->chr, (uint8_t *)&s->txbuf, 1); + if (ret <= 0) { + s->watch_tag = qemu_chr_fe_add_watch(&s->chr, + G_IO_OUT | G_IO_HUP, + asclin_tx_watch, s); + if (!s->watch_tag) { + goto drained; + } + return G_SOURCE_REMOVE; + } + +drained: + qatomic_or(&s->regs[R_FLAGS], ASCLIN_FLAGS_TFL | ASCLIN_FLAGS_TC); + asclin_pulse_irq(s, ASCLIN_FLAGS_TFL | ASCLIN_FLAGS_TC); + return G_SOURCE_REMOVE; +} + +/* + * Transmit one byte immediately. Re-asserts TFL and TC flags so that + * interrupt-driven drivers can continue filling the FIFO. + */ +static void asclin_txdata_write(TriCoreASCLINState *s, uint32_t value) +{ + int ret; + + s->txbuf = value; + ret = qemu_chr_fe_write_all(&s->chr, (uint8_t *)&s->txbuf, 1); + if (ret <= 0) { + s->watch_tag = qemu_chr_fe_add_watch(&s->chr, + G_IO_OUT | G_IO_HUP, + asclin_tx_watch, s); + if (!s->watch_tag) { + goto drained; + } + return; + } + +drained: + qatomic_or(&s->regs[R_FLAGS], ASCLIN_FLAGS_TFL | ASCLIN_FLAGS_TC); + asclin_pulse_irq(s, ASCLIN_FLAGS_TFL | ASCLIN_FLAGS_TC); +} + +static void asclin_txfifocon_write(TriCoreASCLINState *s, uint32_t value) +{ + /* FILL field is read-only hardware status */ + s->regs[R_TXFIFOCON] = value & ~ASCLIN_FILL_MASK; +} + +static uint32_t asclin_txfifocon_read(TriCoreASCLINState *s) +{ + /* Instant-TX model: FILL always reads as 0 */ + return s->regs[R_TXFIFOCON] & ~ASCLIN_FILL_MASK; +} + +static void asclin_rxfifocon_write(TriCoreASCLINState *s, uint32_t value) +{ + s->regs[R_RXFIFOCON] = value & ~ASCLIN_FILL_MASK; + + if (value & ASCLIN_RXFIFOCON_FLUSH) { + asclin_rx_buf_reset(s); + } + if (value & ASCLIN_RXFIFOCON_ENI) { + qemu_chr_fe_accept_input(&s->chr); + } +} + +static uint32_t asclin_rxfifocon_read(TriCoreASCLINState *s) +{ + uint32_t used = asclin_rx_buf_used(s); + uint32_t fill = MIN(used, ASCLIN_HW_FIFO_DEPTH); + + return (s->regs[R_RXFIFOCON] & ~ASCLIN_FILL_MASK) | + (fill << ASCLIN_FILL_SHIFT); +} + +static uint32_t asclin_rxdata_read(TriCoreASCLINState *s, bool peek) +{ + uint32_t r; + + if (s->rx_rdidx == s->rx_wridx) { + return 0; + } + + r = s->rxbuf[s->rx_rdidx]; + if (!peek) { + s->rx_rdidx = (s->rx_rdidx + 1) % ASCLIN_RX_BUF_SIZE; + } + return r; +} + +static uint32_t asclin_csr_read(TriCoreASCLINState *s) +{ + uint32_t csr = s->regs[R_CSR]; + + /* CLKSEL valid: set CON bit (bit 31) when a clock is selected */ + if (csr & 0x1f) { + csr |= (1u << 31); + } + return csr; +} + +static void asclin_flagsset_write(TriCoreASCLINState *s, uint32_t value) +{ + qatomic_or(&s->regs[R_FLAGS], value); + asclin_pulse_irq(s, value); +} + +static void asclin_flagsclear_write(TriCoreASCLINState *s, uint32_t value) +{ + qatomic_and(&s->regs[R_FLAGS], ~value); +} + +/* + * FLAGSENABLE write: newly-enabled bits whose FLAGS is already set + * must produce a rising edge on the corresponding interrupt line. + */ +static void asclin_flagsenable_write(TriCoreASCLINState *s, uint32_t value) +{ + uint32_t old_en = s->regs[R_FLAGSENABLE]; + uint32_t newly_enabled = value & ~old_en; + + s->regs[R_FLAGSENABLE] = value; + asclin_pulse_irq(s, newly_enabled & s->regs[R_FLAGS]); +} + +static uint64_t asclin_read(void *opaque, hwaddr offset, unsigned size) +{ + TriCoreASCLINState *s = opaque; + hwaddr reg = offset >> 2; + + switch (reg) { + case R_CLC: + case R_IOCR: + case R_ID: + case R_BITCON: + case R_FRAMECON: + case R_DATCON: + case R_BRG: + case R_BRD: + case R_LINCON: + case R_LINBTIMER: + case R_LINHTIMER: + case R_FLAGS: + case R_FLAGSENABLE: + return s->regs[reg]; + case R_TXFIFOCON: + return asclin_txfifocon_read(s); + case R_RXFIFOCON: + return asclin_rxfifocon_read(s); + case R_FLAGSSET: + case R_FLAGSCLEAR: + return 0; + case R_TXDATA: + return 0; + case R_RXDATA: + return asclin_rxdata_read(s, false); + case R_CSR: + return asclin_csr_read(s); + case R_RXDATAD: + return asclin_rxdata_read(s, true); + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from unknown offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return 0; + } +} + +static void asclin_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + TriCoreASCLINState *s = opaque; + hwaddr reg = offset >> 2; + uint32_t val = (uint32_t)value; + + switch (reg) { + case R_CLC: + case R_IOCR: + case R_ID: + case R_BITCON: + case R_FRAMECON: + case R_DATCON: + case R_BRG: + case R_BRD: + case R_LINCON: + case R_LINBTIMER: + case R_LINHTIMER: + s->regs[reg] = val; + break; + case R_TXFIFOCON: + asclin_txfifocon_write(s, val); + break; + case R_RXFIFOCON: + asclin_rxfifocon_write(s, val); + break; + case R_FLAGS: + /* read-only hardware status */ + break; + case R_FLAGSSET: + asclin_flagsset_write(s, val); + break; + case R_FLAGSCLEAR: + asclin_flagsclear_write(s, val); + break; + case R_FLAGSENABLE: + asclin_flagsenable_write(s, val); + break; + case R_TXDATA: + asclin_txdata_write(s, val); + break; + case R_RXDATA: + case R_RXDATAD: + /* read-only */ + break; + case R_CSR: + s->regs[R_CSR] = val; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to unknown offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + break; + } +} + +static const MemoryRegionOps asclin_ops = { + .read = asclin_read, + .write = asclin_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void asclin_uart_rx(void *opaque, const uint8_t *buf, int size) +{ + TriCoreASCLINState *s = opaque; + + while (size > 0) { + if (asclin_rx_buf_free(s) == 0) { + qatomic_or(&s->regs[R_FLAGS], ASCLIN_FLAGS_RFO); + asclin_pulse_irq(s, ASCLIN_FLAGS_RFO); + break; + } + s->rxbuf[s->rx_wridx] = *buf++; + s->rx_wridx = (s->rx_wridx + 1) % ASCLIN_RX_BUF_SIZE; + size--; + } + + if (s->rx_rdidx != s->rx_wridx) { + qatomic_or(&s->regs[R_FLAGS], ASCLIN_FLAGS_RFL); + asclin_pulse_irq(s, ASCLIN_FLAGS_RFL); + } +} + +static int asclin_uart_can_rx(void *opaque) +{ + TriCoreASCLINState *s = TRICORE_ASCLIN(opaque); + + if ((s->regs[R_RXFIFOCON] & ASCLIN_RXFIFOCON_ENI) && + asclin_rx_buf_free(s) > 0) { + return 1; + } + return 0; +} + +static void asclin_uart_event(void *opaque, QEMUChrEvent event) +{ +} + +static void asclin_uart_reset_hold(Object *obj, ResetType type) +{ + TriCoreASCLINState *s = TRICORE_ASCLIN(obj); + int i; + + for (i = 0; i < ASCLIN_R_MAX; i++) { + s->regs[i] = 0; + } + asclin_rx_buf_reset(s); +} + +static void asclin_uart_realize(DeviceState *dev, Error **errp) +{ + TriCoreASCLINState *s = TRICORE_ASCLIN(dev); + + qemu_chr_fe_set_handlers(&s->chr, asclin_uart_can_rx, + asclin_uart_rx, asclin_uart_event, + NULL, s, NULL, true); +} + +static void asclin_uart_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + TriCoreASCLINState *s = TRICORE_ASCLIN(obj); + + memory_region_init_io(&s->iomem, obj, &asclin_ops, s, + TYPE_TRICORE_ASCLIN, 0x100); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq_rx); + sysbus_init_irq(sbd, &s->irq_tx); + sysbus_init_irq(sbd, &s->irq_err); +} + +static int asclin_uart_post_load(void *opaque, int version_id) +{ + TriCoreASCLINState *s = TRICORE_ASCLIN(opaque); + + if (s->regs[R_FLAGS] & ASCLIN_FLAGS_TFL) { + s->watch_tag = qemu_chr_fe_add_watch(&s->chr, + G_IO_OUT | G_IO_HUP, + asclin_tx_watch, s); + } + return 0; +} + +static const VMStateDescription vmstate_asclin_uart = { + .name = TYPE_TRICORE_ASCLIN, + .version_id = 1, + .minimum_version_id = 1, + .post_load = asclin_uart_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, TriCoreASCLINState, ASCLIN_R_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static const Property asclin_uart_properties[] = { + DEFINE_PROP_CHR("chardev", TriCoreASCLINState, chr), +}; + +static void asclin_uart_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = asclin_uart_realize; + rc->phases.hold = asclin_uart_reset_hold; + dc->vmsd = &vmstate_asclin_uart; + device_class_set_props(dc, asclin_uart_properties); +} + +static const TypeInfo asclin_uart_info = { + .name = TYPE_TRICORE_ASCLIN, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(TriCoreASCLINState), + .instance_init = asclin_uart_init, + .class_init = asclin_uart_class_init, +}; + +static void asclin_uart_register_types(void) +{ + type_register_static(&asclin_uart_info); +} + +type_init(asclin_uart_register_types) diff --git a/include/hw/char/tricore_asclin.h b/include/hw/char/tricore_asclin.h new file mode 100644 index 0000000000..b142515c50 --- /dev/null +++ b/include/hw/char/tricore_asclin.h @@ -0,0 +1,89 @@ +/* + * TriCore ASCLIN (Asynchronous/Synchronous Interface) UART controller + * + * Copyright (c) 2017 David Brenken <[email protected]> + * Copyright (c) 2024 Siemens AG + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_TRICORE_ASCLIN_H +#define HW_TRICORE_ASCLIN_H + +#include "chardev/char-fe.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +/* FLAGS register bits */ +#define ASCLIN_FLAGS_TH (1u << 0) +#define ASCLIN_FLAGS_TR (1u << 1) +#define ASCLIN_FLAGS_RH (1u << 2) +#define ASCLIN_FLAGS_RR (1u << 3) +#define ASCLIN_FLAGS_FED (1u << 5) +#define ASCLIN_FLAGS_RED (1u << 6) +#define ASCLIN_FLAGS_PE (1u << 16) +#define ASCLIN_FLAGS_TC (1u << 17) +#define ASCLIN_FLAGS_FE (1u << 18) +#define ASCLIN_FLAGS_HT (1u << 19) +#define ASCLIN_FLAGS_RT (1u << 20) +#define ASCLIN_FLAGS_BD (1u << 21) +#define ASCLIN_FLAGS_LP (1u << 22) +#define ASCLIN_FLAGS_LA (1u << 23) +#define ASCLIN_FLAGS_LC (1u << 24) +#define ASCLIN_FLAGS_CE (1u << 25) +#define ASCLIN_FLAGS_RFO (1u << 26) +#define ASCLIN_FLAGS_RFU (1u << 27) +#define ASCLIN_FLAGS_RFL (1u << 28) +#define ASCLIN_FLAGS_TFO (1u << 30) +#define ASCLIN_FLAGS_TFL (1u << 31) + +/* RXFIFOCON bits */ +#define ASCLIN_RXFIFOCON_FLUSH 0x1 +#define ASCLIN_RXFIFOCON_ENI 0x2 + +/* Interrupt line grouping masks */ +#define ASCLIN_TX_INT_MASK (ASCLIN_FLAGS_TH | ASCLIN_FLAGS_TR | \ + ASCLIN_FLAGS_TFL) +#define ASCLIN_RX_INT_MASK (ASCLIN_FLAGS_RH | ASCLIN_FLAGS_RR | \ + ASCLIN_FLAGS_RFL) +#define ASCLIN_ERR_INT_MASK (ASCLIN_FLAGS_FED | ASCLIN_FLAGS_RED | \ + ASCLIN_FLAGS_PE | ASCLIN_FLAGS_TC | \ + ASCLIN_FLAGS_FE | ASCLIN_FLAGS_HT | \ + ASCLIN_FLAGS_RT | ASCLIN_FLAGS_BD | \ + ASCLIN_FLAGS_LP | ASCLIN_FLAGS_LA | \ + ASCLIN_FLAGS_LC | ASCLIN_FLAGS_CE | \ + ASCLIN_FLAGS_RFO | ASCLIN_FLAGS_RFU | \ + ASCLIN_FLAGS_TFO) + +/* FIFO depth and FILL field position in TX/RXFIFOCON */ +#define ASCLIN_HW_FIFO_DEPTH 16 +#define ASCLIN_FILL_SHIFT 16 +#define ASCLIN_FILL_MASK (0x1fu << ASCLIN_FILL_SHIFT) + +#define ASCLIN_R_MAX 21 +#define ASCLIN_RX_BUF_SIZE 8192 + +#define TYPE_TRICORE_ASCLIN "tricore_asclin" +OBJECT_DECLARE_SIMPLE_TYPE(TriCoreASCLINState, TRICORE_ASCLIN) + +struct TriCoreASCLINState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion iomem; + CharFrontend chr; + qemu_irq irq_rx; + qemu_irq irq_tx; + qemu_irq irq_err; + + guint watch_tag; + uint32_t regs[ASCLIN_R_MAX]; + uint32_t txbuf; + + uint8_t rxbuf[ASCLIN_RX_BUF_SIZE]; + uint32_t rx_wridx; + uint32_t rx_rdidx; +}; + +#endif -- 2.47.3
