git: c4b68e7e53bb - main - ns8250: Check if flush via FCR succeeded

From: Colin Percival <cperciva_at_FreeBSD.org>
Date: Tue, 18 Oct 2022 06:03:11 UTC
The branch main has been updated by cperciva:

URL: https://cgit.FreeBSD.org/src/commit/?id=c4b68e7e53bb352be3fa16995b99764c03097e66

commit c4b68e7e53bb352be3fa16995b99764c03097e66
Author:     Colin Percival <cperciva@FreeBSD.org>
AuthorDate: 2022-10-13 18:01:03 +0000
Commit:     Colin Percival <cperciva@FreeBSD.org>
CommitDate: 2022-10-18 06:02:21 +0000

    ns8250: Check if flush via FCR succeeded
    
    The emulated UART in the Firecracker VMM (aka the implementation in the
    rust-vmm/vm-superio project) includes FIFOs but does not implement the
    FCR register, which is used by ns8250_flush to flush the FIFOs.
    
    Check the LSR to see if there is still data in the FIFOs and call
    ns8250_drain if necessary.
    
    Discussed with: emaste, imp, jrtc27
    Sponsored by:   https://patreon.com/cperciva
    Differential Revision:  https://reviews.freebsd.org/D36979
---
 sys/dev/uart/uart_dev_ns8250.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/sys/dev/uart/uart_dev_ns8250.c b/sys/dev/uart/uart_dev_ns8250.c
index 182831da7611..d0eadeded943 100644
--- a/sys/dev/uart/uart_dev_ns8250.c
+++ b/sys/dev/uart/uart_dev_ns8250.c
@@ -210,6 +210,8 @@ static void
 ns8250_flush(struct uart_bas *bas, int what)
 {
 	uint8_t fcr;
+	uint8_t lsr;
+	int drain = 0;
 
 	fcr = FCR_ENABLE;
 #ifdef CPU_XBURST
@@ -221,6 +223,23 @@ ns8250_flush(struct uart_bas *bas, int what)
 		fcr |= FCR_RCV_RST;
 	uart_setreg(bas, REG_FCR, fcr);
 	uart_barrier(bas);
+
+	/*
+	 * Detect and work around emulated UARTs which don't implement the
+	 * FCR register; on these systems we need to drain the FIFO since
+	 * the flush we request doesn't happen.  One such system is the
+	 * Firecracker VMM, aka. the rust-vmm/vm-superio emulation code:
+	 * https://github.com/rust-vmm/vm-superio/issues/83
+	 */
+	lsr = uart_getreg(bas, REG_LSR);
+	if ((lsr & LSR_TEMT) && (what & UART_FLUSH_TRANSMITTER))
+		drain |= UART_DRAIN_TRANSMITTER;
+	if ((lsr & LSR_RXRDY) && (what & UART_FLUSH_RECEIVER))
+		drain |= UART_DRAIN_RECEIVER;
+	if (drain != 0) {
+		printf("ns8250: UART FCR is broken\n");
+		ns8250_drain(bas, drain);
+	}
 }
 
 static int