If the UART back-end chardev doesn't drain data as fast as stdout does or blocks, buffer in the TX FIFO to try again later.
This avoids having the IO-thread busy waiting on chardev back-ends, reported recently when testing the Trusted Reference Stack and using the socket backend. Implement registering a front-end 'watch' callback on back-end events, so we can resume transmitting when the back-end is writable again, not blocking the main loop. Similarly to the RX FIFO path, FIFO level selection is not implemented (interrupt is triggered when a single byte is available in the FIFO). Reported-by: Mikko Rapeli <mikko.rap...@linaro.org> Suggested-by: Alex Bennée <alex.ben...@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <phi...@linaro.org> --- hw/char/pl011.c | 39 +++++++++++++++++++++++++++++++-------- hw/char/trace-events | 1 + 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 3c4264869df..70eba224a9c 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -251,11 +251,15 @@ static gboolean pl011_xmit(void *do_not_use, GIOCondition cond, void *opaque) { PL011State *s = opaque; int bytes_consumed; - uint8_t data; + uint8_t buf[PL011_FIFO_DEPTH]; uint32_t count; count = fifo8_num_used(&s->xmit_fifo); trace_pl011_fifo_tx_xmit_used(count); + if (count < 1) { + /* FIFO empty */ + return G_SOURCE_REMOVE; + } if (!qemu_chr_fe_backend_connected(&s->chr)) { /* Instant drain the fifo when there's no back-end. */ @@ -263,19 +267,29 @@ static gboolean pl011_xmit(void *do_not_use, GIOCondition cond, void *opaque) return G_SOURCE_REMOVE; } - data = fifo8_pop(&s->xmit_fifo); - bytes_consumed = 1; + count = fifo8_peek_buf(&s->xmit_fifo, buf, fifo8_num_used(&s->xmit_fifo)); + trace_pl011_fifo_tx_xmit_peek(count); - /* - * 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, bytes_consumed); + /* Transmit as much data as we can. */ + bytes_consumed = qemu_chr_fe_write(&s->chr, buf, count); trace_pl011_fifo_tx_xmit_consumed(bytes_consumed); + if (bytes_consumed < 0) { + /* Error in back-end: drain the fifo. */ + pl011_drain_tx(s); + return G_SOURCE_REMOVE; + } + + /* Pop the data we could transmit. */ + fifo8_drop(&s->xmit_fifo, bytes_consumed); s->int_level |= INT_TX; pl011_update(s); + if (!fifo8_is_empty(&s->xmit_fifo)) { + /* Reschedule another transmission if we couldn't transmit all. */ + return G_SOURCE_CONTINUE; + } + return G_SOURCE_REMOVE; } @@ -300,6 +314,10 @@ static void pl011_write_txdata(PL011State *s, uint8_t data) trace_pl011_fifo_tx_put(data); pl011_loopback_tx(s, data); fifo8_push(&s->xmit_fifo, data); + if (pl011_is_tx_fifo_full(s)) { + s->flags |= PL011_FLAG_TXFF; + } + pl011_xmit(NULL, G_IO_OUT, s); } @@ -651,6 +669,11 @@ static int pl011_post_load(void *opaque, int version_id) s->read_pos = 0; } + if (!fifo8_is_empty(&s->xmit_fifo)) { + /* Reschedule another transmission */ + qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, pl011_xmit, s); + } + s->ibrd &= IBRD_MASK; s->fbrd &= FBRD_MASK; diff --git a/hw/char/trace-events b/hw/char/trace-events index 7d1cba1b4f8..2d02c057483 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -69,6 +69,7 @@ pl011_fifo_rx_put(uint32_t c, int read_count) "new char 0x%02x read_count now %d pl011_fifo_rx_full(void) "RX FIFO now full, RXFF set" pl011_fifo_tx_is_full(const char *desc, bool full) "mode:%s full:%u" pl011_fifo_tx_put(uint8_t byte) "TX FIFO push char [0x%02x]" +pl011_fifo_tx_xmit_peek(unsigned sent) "TX FIFO peek %u chars" pl011_fifo_tx_xmit_used(unsigned sent) "TX FIFO used %u chars" pl011_fifo_tx_xmit_consumed(unsigned sent) "TX FIFO consumed %u chars" pl011_fifo_tx_overrun(void) "TX FIFO overrun" -- 2.47.1