From f2ca017b44770e5dcd93342099c524b004b5818f Mon Sep 17 00:00:00 2001 From: Thomas Stokes Date: Sun, 9 Aug 2026 01:22:35 +0800 Subject: [PATCH] wasm: release queued console input after guest output --- tools/wasm/src/virtio/console.ts | 13 ++++++++++++- tools/wasm/test/wasm.test.ts | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tools/wasm/src/virtio/console.ts b/tools/wasm/src/virtio/console.ts index ab71e8f8d9b31d..fb5f5cde133c41 100644 --- a/tools/wasm/src/virtio/console.ts +++ b/tools/wasm/src/virtio/console.ts @@ -60,15 +60,19 @@ export function consoleDevice( // console port instead of being dropped. const receive_chains: VirtqueueChain[] = []; const pending_input: Uint8Array[] = []; + let guest_ready = false; function reset() { // Receive descriptors belong to the old queue, but host input belongs to // the console and must survive the reset Linux performs during probing. receive_chains.length = 0; + guest_ready = false; } function flush_input() { - while (receive_chains.length > 0 && pending_input.length > 0) { + while ( + guest_ready && receive_chains.length > 0 && pending_input.length > 0 + ) { const chain = receive_chains.shift()!; const chunk = pending_input[0]!; const [desc, trailing] = chain; @@ -100,6 +104,13 @@ export function consoleDevice( } async function notify_output(queue: Virtqueue) { + // Linux exposes the legacy console receive queue before hvc0 is ready to + // consume it. Its first output kick proves that the console handoff has + // completed, so input queued before boot is safe to release from here. + if (!guest_ready) { + guest_ready = true; + flush_input(); + } for (const chain of queue) { let n = 0; for (const { array, writable } of chain) { diff --git a/tools/wasm/test/wasm.test.ts b/tools/wasm/test/wasm.test.ts index 6599acd5e88a3e..5eaca0e129e91c 100644 --- a/tools/wasm/test/wasm.test.ts +++ b/tools/wasm/test/wasm.test.ts @@ -294,6 +294,24 @@ test("console input is held until the guest opens its port", async () => { replacement.setUint16(14, (1 << 7) | (1 << 1), true); imports.enable_vring(0, 0, 1, replacement_ring, 1); imports.notify(0, 0); + assert.deepEqual( + [...new Uint8Array(console_memory.buffer, 256, 2)], + [0, 0], + "input remains queued until the guest console can consume it", + ); + + const output_ring = 384; + const output_address = 512; + new Uint8Array(console_memory.buffer, output_address, 5).set( + new TextEncoder().encode("ready"), + ); + const output_descriptor = new DataView(console_memory.buffer, output_ring, 16); + output_descriptor.setBigUint64(0, BigInt(output_address), true); + output_descriptor.setUint32(8, 5, true); + output_descriptor.setUint16(12, 0, true); + output_descriptor.setUint16(14, 1 << 7); + imports.enable_vring(0, 1, 1, output_ring, 2); + imports.notify(0, 1); const undelivered = new Promise((resolve) => setTimeout(resolve, 50)); await Promise.race([delivered.promise, undelivered]);