Allow board code to observe PL011 DMA request line changes. The RP2040 Pico machine uses this to connect UART0 and UART1 TX/RX activity to the RP2040 DMA DREQ model without forking the PL011 device.
Signed-off-by: Gilles Grimaud <[email protected]> --- hw/char/pl011.c | 116 +++++++++++++++++++++++++++++++++++++--- include/hw/char/pl011.h | 8 +++ 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index cb12c3e224..40876b92df 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -80,6 +80,11 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr) #define LCR_FEN (1 << 4) #define LCR_BRK (1 << 0) +/* DMA Control Register, UARTDMACR */ +#define DMACR_RXDMAE (1 << 0) +#define DMACR_TXDMAE (1 << 1) +#define DMACR_DMAONERR (1 << 2) + /* Control Register, UARTCR */ #define CR_OUT2 (1 << 13) #define CR_OUT1 (1 << 12) @@ -96,6 +101,12 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr) /* Fractional Baud Rate Divider, UARTFBRD */ #define FBRD_MASK 0x3f +/* RP2040 APB atomic register aliases. */ +#define PL011_ATOMIC_ALIAS_MASK 0x3000 +#define PL011_ATOMIC_XOR 0x1000 +#define PL011_ATOMIC_SET 0x2000 +#define PL011_ATOMIC_CLR 0x3000 + static const unsigned char pl011_id_arm[8] = { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; static const unsigned char pl011_id_luminary[8] = @@ -157,6 +168,34 @@ static inline unsigned pl011_get_fifo_depth(PL011State *s) return pl011_is_fifo_enabled(s) ? PL011_FIFO_DEPTH : 1; } +static void pl011_update_dreq(PL011State *s) +{ + if (s->dreq_tx) { + qemu_set_irq(s->dreq_tx, + (s->dmacr & DMACR_TXDMAE) && + !(s->flags & PL011_FLAG_TXFF)); + } + if (s->dreq_rx) { + qemu_set_irq(s->dreq_rx, + (s->dmacr & DMACR_RXDMAE) && s->read_count > 0); + } +} + +static uint32_t pl011_apply_atomic_alias(uint32_t old, uint32_t value, + hwaddr alias) +{ + switch (alias) { + case PL011_ATOMIC_XOR: + return old ^ value; + case PL011_ATOMIC_SET: + return old | value; + case PL011_ATOMIC_CLR: + return old & ~value; + default: + return value; + } +} + static inline void pl011_reset_rx_fifo(PL011State *s) { s->read_count = 0; @@ -190,6 +229,7 @@ static void pl011_fifo_rx_put(void *opaque, uint32_t value) trace_pl011_fifo_rx_full(); s->flags |= PL011_FLAG_RXFF; } + pl011_update_dreq(s); if (s->read_count == s->read_trigger) { s->int_level |= INT_RX; pl011_update(s); @@ -254,10 +294,17 @@ static void pl011_write_txdata(PL011State *s, uint8_t data) * XXX this blocks entire thread. Rewrite to use * qemu_chr_fe_write and background I/O callbacks */ - qemu_chr_fe_write_all(&s->chr, &data, 1); + if (s->tx_connected) { + qemu_chr_fe_write_all(&s->chr, &data, 1); + } else if (!s->logged_disconnected_tx) { + qemu_log_mask(LOG_GUEST_ERROR, + "PL011 data written while TX pin is disconnected\n"); + s->logged_disconnected_tx = true; + } pl011_loopback_tx(s, data); s->int_level |= INT_TX; pl011_update(s); + pl011_update_dreq(s); } static uint32_t pl011_read_rxdata(PL011State *s) @@ -280,6 +327,7 @@ static uint32_t pl011_read_rxdata(PL011State *s) trace_pl011_read_fifo(s->read_count, fifo_depth); s->rsr = c >> 8; pl011_update(s); + pl011_update_dreq(s); qemu_chr_fe_accept_input(&s->chr); return c; } @@ -469,6 +517,7 @@ static void pl011_write(void *opaque, hwaddr offset, } s->lcr = value; pl011_set_read_trigger(s); + pl011_update_dreq(s); break; case 12: /* UARTCR */ /* ??? Need to implement the enable bit. */ @@ -492,10 +541,8 @@ static void pl011_write(void *opaque, hwaddr offset, pl011_update(s); break; case 18: /* UARTDMACR */ - s->dmacr = value; - if (value & 3) { - qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n"); - } + s->dmacr = value & (DMACR_RXDMAE | DMACR_TXDMAE | DMACR_DMAONERR); + pl011_update_dreq(s); break; default: qemu_log_mask(LOG_GUEST_ERROR, @@ -503,6 +550,23 @@ static void pl011_write(void *opaque, hwaddr offset, } } +static uint64_t pl011_atomic_alias_read(void *opaque, hwaddr offset, + unsigned size) +{ + return pl011_read(opaque, offset & 0xfff, size); +} + +static void pl011_atomic_alias_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + hwaddr alias = (offset + PL011_ATOMIC_XOR) & PL011_ATOMIC_ALIAS_MASK; + hwaddr reg = offset & 0xfff; + uint32_t old = pl011_read(opaque, reg, size); + uint32_t new = pl011_apply_atomic_alias(old, value, alias); + + pl011_write(opaque, reg, new, size); +} + static int pl011_can_receive(void *opaque) { PL011State *s = (PL011State *)opaque; @@ -518,6 +582,9 @@ static int pl011_can_receive(void *opaque) * historically never enforced that. So we effectively keep the * UART continuously enabled regardless of the enable bits. */ + if (!s->rx_connected) { + return 0; + } trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available); return fifo_available; @@ -526,6 +593,9 @@ static int pl011_can_receive(void *opaque) static void pl011_receive(void *opaque, const uint8_t *buf, int size) { trace_pl011_receive(size); + if (!PL011(opaque)->rx_connected) { + return; + } /* * In loopback mode, the RX input signal is internally disconnected * from the entire receiving logics; thus, all inputs are ignored, @@ -542,11 +612,28 @@ static void pl011_receive(void *opaque, const uint8_t *buf, int size) static void pl011_event(void *opaque, QEMUChrEvent event) { - if (event == CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) { + PL011State *s = opaque; + + if (event == CHR_EVENT_BREAK && s->rx_connected && + !pl011_loopback_enabled(opaque)) { pl011_fifo_rx_put(opaque, DR_BE); } } +void pl011_set_tx_connected(PL011State *s, bool connected) +{ + s->tx_connected = connected; + if (connected) { + s->logged_disconnected_tx = false; + } +} + +void pl011_set_rx_connected(PL011State *s, bool connected) +{ + s->rx_connected = connected; + qemu_chr_fe_accept_input(&s->chr); +} + static void pl011_clock_update(void *opaque, ClockEvent event) { PL011State *s = PL011(opaque); @@ -562,6 +649,14 @@ static const MemoryRegionOps pl011_ops = { .impl.max_access_size = 4, }; +static const MemoryRegionOps pl011_atomic_alias_ops = { + .read = pl011_atomic_alias_read, + .write = pl011_atomic_alias_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, +}; + static bool pl011_clock_needed(void *opaque) { PL011State *s = PL011(opaque); @@ -648,11 +743,19 @@ static void pl011_init(Object *obj) PL011State *s = PL011(obj); int i; + s->tx_connected = true; + s->rx_connected = true; memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000); sysbus_init_mmio(sbd, &s->iomem); + memory_region_init_io(&s->atomic_alias_iomem, OBJECT(s), + &pl011_atomic_alias_ops, s, + "pl011-atomic-alias", 0x3000); + sysbus_init_mmio(sbd, &s->atomic_alias_iomem); for (i = 0; i < ARRAY_SIZE(s->irq); i++) { sysbus_init_irq(sbd, &s->irq[i]); } + qdev_init_gpio_out_named(DEVICE(obj), &s->dreq_tx, "dreq-tx", 1); + qdev_init_gpio_out_named(DEVICE(obj), &s->dreq_rx, "dreq-rx", 1); s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s, ClockUpdate); @@ -687,6 +790,7 @@ static void pl011_reset(DeviceState *dev) s->logged_disabled_uart = false; pl011_reset_rx_fifo(s); pl011_reset_tx_fifo(s); + pl011_update_dreq(s); } static void pl011_class_init(ObjectClass *oc, const void *data) diff --git a/include/hw/char/pl011.h b/include/hw/char/pl011.h index 5695787650..20c0b5694b 100644 --- a/include/hw/char/pl011.h +++ b/include/hw/char/pl011.h @@ -32,6 +32,7 @@ struct PL011State { SysBusDevice parent_obj; MemoryRegion iomem; + MemoryRegion atomic_alias_iomem; uint32_t flags; uint32_t lcr; uint32_t rsr; @@ -49,9 +50,14 @@ struct PL011State { int read_trigger; CharFrontend chr; qemu_irq irq[6]; + qemu_irq dreq_tx; + qemu_irq dreq_rx; Clock *clk; bool migrate_clk; + bool tx_connected; + bool rx_connected; bool logged_disabled_uart; + bool logged_disconnected_tx; const unsigned char *id; /* * Since some users embed this struct directly, we must @@ -61,5 +67,7 @@ struct PL011State { }; DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr); +void pl011_set_tx_connected(PL011State *s, bool connected); +void pl011_set_rx_connected(PL011State *s, bool connected); #endif -- 2.53.0
