fix: enable THRE interrupt in emulate_serial_init for snapshot restore#5730
fix: enable THRE interrupt in emulate_serial_init for snapshot restore#5730ejc3 wants to merge 3 commits intofirecracker-microvm:mainfrom
Conversation
|
Hey @ejc3 , thanks again for reporting the issue! We'll take a deeper look in the next days. In the meanwhile, do you know what are the conditions to trigger this or how often this is happening for you? I don't think we've ever seen this issue, despite our testing. |
|
On x86 the test in commit 2 appears to be passing even without commit 1 being applied. |
|
Bleh, let me look more deeply. The manifestation was trying to get serial logs flowing after restore. |
fdbd689 to
881961d
Compare
|
Updated the PR with a new integration test and a clearer description of the reproduction conditions. Addressing both comments: @ilstam — You were right that the old unit test passed without the fix. I replaced it with two tests:
@Manciukic — The condition is: snapshot during active serial output where the TX circular buffer has >16 bytes. I experienced this in ejc3/fcvm when adding tests of systemd/journald logging. It didn't show up in |
After snapshot restore, the guest 8250 serial driver stalls on x86_64 because emulate_serial_init() only sets IER_RDA_BIT (bit 0), not IER_THR_EMPTY_BIT (bit 1). Without THRE interrupts enabled, the guest driver waits indefinitely for a TX completion notification that never arrives. On x86_64, additionally write a byte to DATA_OFFSET to trigger the initial THRE interrupt via thr_empty_interrupt(). Writing to IER alone only stores the value (vm-superio Serial::write for IER_OFFSET just sets self.interrupt_enable) — it does not generate an interrupt. The DATA write causes thr_empty_interrupt() to signal the eventfd, which KVM injects via irqfd when the vCPU resumes. This is not needed on aarch64 where the GIC interrupt delivery path handles the first guest-initiated TX write correctly. Tested: serial console test passes on x86_64 after warm start (30 fc-agent lines + UART marker found in host log).
Add a unit test that directly validates emulate_serial_init() enables
both RDA and THRE interrupts and triggers the THRE interrupt via a DATA
write. This replaces the previous test which bypassed emulate_serial_init()
and tested vm-superio behavior directly (which passes regardless of the fix).
The test verifies:
1. Fresh serial has IER=0x00 (all interrupts disabled)
2. After emulate_serial_init(): IER has both RDA (bit 0) and THRE (bit 1) set
3. The DATA write triggers the THRE interrupt via eventfd
Without the fix in commit 1, this test fails:
assertion failed: emulate_serial_init must enable THRE interrupt
left: 0
right: 2
Add test that reproduces the serial console stall after snapshot restore when the guest is actively transmitting. The bug manifests when the TX circular buffer has >16 bytes at snapshot time: 1. serial8250_tx_chars() can only drain 16 bytes (fifosize) per call 2. THRI stays set in the guest driver's cached IER 3. After restore, serial8250_start_tx() sees THRI already set and skips the UART_BUG_TXEN fallback path 4. vm-superio's IER only has RDA (no THRI), so no THRE interrupt fires 5. TX stalls permanently The test uses dd bs=4096 to generate writes larger than the 16-byte FIFO limit, keeping the buffer full during snapshot. Without the fix: 9 bytes initial burst, 0 bytes sustained (10s timeout). With fix: 148K+ bytes initial, sustained output immediately. The existing test_serial_after_snapshot doesn't catch this because it snapshots at idle (shell prompt), where serial8250_stop_tx() has already cleared THRI from the cached IER, allowing the TXEN fallback to work.
881961d to
01b0d6e
Compare
Problem
After snapshot restore, the serial console (ttyS0) stalls on x86_64. The guest 8250 driver stops transmitting — no output reaches Firecracker's stdout.
Root cause
emulate_serial_init()setsIER_RDA_BITto re-enable the Received Data Available interrupt, but does not setIER_THR_EMPTY_BIT(Transmitter Holding Register Empty). vm-superio'sSerial::writeforIER_OFFSETonly stores the value — it does not generate any interrupt. The guest 8250 driver uses interrupt-driven TX and waits for a THRE interrupt before sending each byte. After restore, that interrupt never fires, so TX stalls.When the bug manifests
The stall only occurs when the snapshot is taken during active serial transmission — specifically when the guest's TX circular buffer has more than 16 bytes (port->fifosize):
serial8250_tx_chars()sends at most 16 bytes per THRE interruptUART_IER_THRIstays set in the guest driver's cachedup->ierserial8250_start_tx()sees THRI already set and skips theUART_BUG_TXENfallbackWhen the snapshot is taken at idle (empty TX buffer),
serial8250_stop_tx()has already cleared THRI from the cached IER. After restore,serial8250_start_tx()re-enables THRI via theUART_BUG_TXENfallback which polls LSR directly — so the idle case works even without this fix. Real workloads (systemd, journald, application logging) frequently have >16 bytes buffered during active output.Fix
IER_RDA_BIT | IER_THR_EMPTY_BITinemulate_serial_init()(both architectures).DATA_OFFSETto triggerthr_empty_interrupt(), which signals the eventfd → KVM irqfd → IRQ 4 → guest 8250 driver resumes TX.The DATA write is only needed on x86_64 because ARM64's MMIO-based interrupt delivery picks up the THRE-enabled IER on the first guest TX attempt.
Tests
Unit test (
test_emulate_serial_init_enables_thre_interrupt): Callsemulate_serial_init()directly and verifies IER has both bits set and the interrupt eventfd was signaled.Integration test (
test_serial_output_after_snapshot_during_active_tx): Reproduces the actual stall by snapshotting during active TX withdd bs=4096to keep >16 bytes in the circular buffer, then checking for sustained output without sending any host input (which would mask the bug via RDA piggybacking).