RE: [PATCH v4] serial: amba-pl011: don't wait for BUSY after every earlycon character
Michael Kelley <[email protected]> Wed, 22 Jul 2026 16:45:20 +0000
| Newsgroups | org.kernel.vger.linux-serial,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <SN6PR02MB4157A6147A1DBD19960B7ACFD4C12@SN6PR02MB4157.namprd02.prod.outlook.com> |
From: Eric Curtin <[email protected]> Sent: Monday, July 20, 2026 5:27 AM > > pl011_putc(), used exclusively by the pl011 earlycon (pl011_early_write() > -> uart_console_write()), waits for UART01x_FR_TXFF to clear before > writing a character (correct: don't overrun the TX FIFO) and then *also* > busy-waited for UART01x_FR_BUSY to clear before returning, i.e. it waited > for the character to be fully shifted out on the wire before the next > character in the string could even be considered. > > Waiting for BUSY per character defeats the TX FIFO: instead of letting > the UART buffer several queued bytes and transmit them back to back, > every single character printed through earlycon was forced to wait for > that character's own complete transmission (a full UART bit-time at the > configured baud rate) before the driver would even look at writing the > next one. This is wasted time on real hardware, and it is much worse > under virtualization: each read of UARTFR and each write to UARTDR is > an MMIO access that traps to the hypervisor, so every extra poll is a > full VM-exit/entry round trip. > > git blame on this function is unhelpful (this tree's history stops at a > shallow-clone boundary), but the equivalent history is available from > the upstream patch that added the QDF2400 erratum 44 workaround: > > commit d8a4995bcea1 ("tty: pl011: Work around QDF2400 E44 stuck BUSY bit"): > > That patch added a *separate* qdf2400_e44_putc() for hardware where > BUSY can get stuck at 1, rather than touching pl011_putc() itself, and > it left pl011_putc()'s per-character BUSY wait in place. So there is > precedent for "waiting on BUSY is fragile on some hardware", but no > indication that pl011_putc()'s per-character wait was ever intentional > for standard hardware as opposed to simply mirroring the already-present > final-drain wait used elsewhere in this same file. > > Both pl011_console_putchar() and pl011_put_poll_char() (the regular, > always-built console write path and the kgdb/kdb polling path) only > wait for TXFF before writing, never for BUSY. The regular console path > still gets a full-drain guarantee, but correctly obtains it only *once*, > after the whole buffer has been written: pl011_console_write_atomic() > and pl011_console_write_thread() each call uart_console_write() with > pl011_console_putchar (no per-character BUSY wait), then separately > wait for BUSY exactly once, after the loop, before restoring the CR > register. > > pl011_early_write() had no equivalent single final wait, so simply > deleting the wait from pl011_putc() would drop the "the UART has > actually finished transmitting by the time this function returns" > guarantee that earlycon currently provides, e.g. right before a panic > message is followed immediately by a reboot/poweroff. Instead, move the > same "wait for BUSY" out of the per-character pl011_putc() and place it > once in pl011_early_write(), after uart_console_write() completes, > mirroring the pattern already used by pl011_console_write_atomic()/ > _thread(). This keeps the flush guarantee while turning an O(n) wait > (once per character) into an O(1) wait (once per earlycon write call). > > This is easy to see, and to prove, in a fast-booting VMM. Using a small > KVM-based VMM that boots Linux guests directly on arm64 (no firmware), > booting an otherwise-identical 7.2.0-rc1 kernel (single vCPU, 512MB > guest, direct PL011 MMIO with no interrupt-driven UART - i.e. earlycon > is the only console active) up to the (expected, rootfs-less) > "Unable to mount root fs" panic, with 20 boot samples per kernel per > configuration: > > cmdline: console=ttyAMA0 earlycon=pl011,0x09000000 ignore_loglevel > initcall_debug printk.time=1 (~99KB printed over earlycon) > before: min 466.3ms avg 471.4ms max 480.3ms (n=20) > after: min 429.4ms avg 435.3ms max 449.0ms (n=20) > -> ~36ms / ~7.7% faster, non-overlapping distributions > > cmdline: <the VMM's actual default, quiet, earlycon=pl011,0x09000000> > before: min 74.3ms avg 76.5ms max 82.2ms (n=20) > after: min 53.8ms avg 56.8ms max 74.9ms (n=20) > -> ~20ms / ~25.8% faster > > The second measurement uses the VMM's real default boot configuration > (not a synthetic debug cmdline), so this is representative of ordinary > boots, not just verbose-logging ones. Console content was diffed > (timestamps and per-initcall "returned 0 after N usecs" numbers > excluded) between before/after runs and is identical line-for-line: > this change does not drop, reorder, or corrupt any output. > I tested this patch in two VMs running on Hyper-V in the Azure public cloud. Hyper-V provides an emulated PL011 to ARM64 guests, but the emulation has some quirks. I wanted to verify that the quirks didn't cause any issues. All my basic smoke testing passed, which is great. From a performance standpoint, the older Dp_v5 series VM saved an average of 196 milliseconds in boot time up to the point that the regular PL011 driver took over. The newer Dp_v6 series VM, which has a faster physical processor, saved an average of 158 ms. Both values are an average of 4 runs of an unmodified linux-next20260721 kernel vs. the same kernel with your patch. That's a nice win! Tested-by: Michael Kelley <[email protected]>