From d5f58ba0f5a8be544449f0b5c916331096639be9 Mon Sep 17 00:00:00 2001 From: Wenbo Li Date: Sun, 26 Apr 2026 21:54:35 +0800 Subject: [PATCH 1/2] virtio-serial: drain control queue in recv_control recv_control() previously popped only one used entry from the control RX queue per call. When QEMU delivers multiple control events (e.g. DeviceAdd, PortOpen), the remaining events could be left pending and never handled, leading to missing RX prefill / vq empty failures. Change recv_control() to drain all pending control messages after the initial wait, ensuring the full control sequence is processed. Signed-off-by: Wenbo Li --- src/devices/virtio_serial/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/devices/virtio_serial/src/lib.rs b/src/devices/virtio_serial/src/lib.rs index f75e52772..1443bbc47 100644 --- a/src/devices/virtio_serial/src/lib.rs +++ b/src/devices/virtio_serial/src/lib.rs @@ -482,7 +482,7 @@ impl VirtioSerial { self.timer.reset_timeout(); - if self + while self .queues .index(CONTROL_RECEIVEQ as usize) .borrow_mut() @@ -533,7 +533,6 @@ impl VirtioSerial { ControlEvent::DeviceAdd => { self.fill_port_queue(port_id)?; self.port_ready(port_id)?; - self.recv_control()?; } ControlEvent::DeviceRemove => { self.connected_ports.remove(&port_id); From 1e15f43703049ea65d04d18f8df0073f09359f9b Mon Sep 17 00:00:00 2001 From: Wenbo Li Date: Tue, 28 Apr 2026 14:39:48 +0800 Subject: [PATCH 2/2] virtio-serial: try drain control msg when port.open() failed VirtioSerialPort::open() may return PortNotAvailable because the PORT_OPEN control event arrives after the initial control setup. On each PortNotAvailable, non-blockingly drain pending control messages for at most 100 times. Signed-off-by: Wenbo Li --- src/devices/virtio_serial/src/lib.rs | 48 ++++++++++++++++++++++++++++ src/migtd/src/migration/transport.rs | 33 +++++++++++++++++-- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/devices/virtio_serial/src/lib.rs b/src/devices/virtio_serial/src/lib.rs index 1443bbc47..c7e21474a 100644 --- a/src/devices/virtio_serial/src/lib.rs +++ b/src/devices/virtio_serial/src/lib.rs @@ -467,6 +467,54 @@ impl VirtioSerial { Ok(()) } + /// Non-blocking control-queue pump: drains any already-pending control + /// messages without waiting for new IRQ/events. + fn recv_control_nonblock(&mut self) -> Result<()> { + let vq = self.queues.index(CONTROL_RECEIVEQ as usize); + if !vq.borrow_mut().can_pop() { + return Ok(()); + } + + while self + .queues + .index(CONTROL_RECEIVEQ as usize) + .borrow_mut() + .can_pop() + { + let mut g2h = Vec::new(); + let mut h2g = Vec::new(); + let _ = self + .queues + .index(CONTROL_RECEIVEQ as usize) + .borrow_mut() + .pop_used(&mut g2h, &mut h2g)?; + + for vq_buf in &h2g { + if let Some(record) = self.dma_allocation.get(&vq_buf.addr) { + let safe_len = core::cmp::min(vq_buf.len as usize, record.dma_size); + let control_msg = unsafe { + core::slice::from_raw_parts(vq_buf.addr as *const u8, safe_len) + }; + self.handle_control_msg(control_msg)?; + self.free_dma_memory(vq_buf.addr) + .ok_or(VirtioSerialError::OutOfResource)?; + } + } + } + + Ok(()) + } + + /// Non-blocking control-queue pump to help consume late `PORT_OPEN` events. + /// Returns `Ok(true)` if at least one control message was drained. + pub fn try_poll_control() -> Result<()> { + SERIAL_DEVICE + .lock() + .get_mut() + .ok_or(VirtioSerialError::InvalidParameter)? + .recv_control_nonblock() + } + fn recv_control(&mut self) -> Result<()> { let vq = self.queues.index(CONTROL_RECEIVEQ as usize); diff --git a/src/migtd/src/migration/transport.rs b/src/migtd/src/migration/transport.rs index 35f689dba..c0012ad04 100644 --- a/src/migtd/src/migration/transport.rs +++ b/src/migtd/src/migration/transport.rs @@ -39,11 +39,40 @@ pub(super) async fn setup_transport( #[cfg(all(feature = "virtio-serial", not(feature = "vmcall-raw")))] { - use virtio_serial::VirtioSerialPort; + use crate::driver::ticks::Timer; + use core::time::Duration; + use virtio_serial::{VirtioSerial, VirtioSerialError, VirtioSerialPort}; const VIRTIO_SERIAL_PORT_ID: u32 = 1; + const VIRTIO_SERIAL_OPEN_RETRY_TIMES: usize = 100; + const VIRTIO_SERIAL_OPEN_RETRY_DELAY: Duration = Duration::from_millis(10); let port = VirtioSerialPort::new(VIRTIO_SERIAL_PORT_ID); - port.open()?; + let mut opened = false; + for _ in 0..VIRTIO_SERIAL_OPEN_RETRY_TIMES { + match port.open() { + Ok(()) => { + opened = true; + break; + } + Err(VirtioSerialError::PortNotAvailable(_)) => { + // `PORT_OPEN` can arrive after init_control; pump control queue during retry. + // If we drained pending control msgs, try open again immediately to avoid + // waiting for the next retry tick. + Timer::after(VIRTIO_SERIAL_OPEN_RETRY_DELAY).await; + VirtioSerial::try_poll_control()?; + } + Err(e) => return Err(e.into()), + } + } + if !opened { + log::error!( + "virtio-serial port {} not available after {} retries ({}ms total)\n", + VIRTIO_SERIAL_PORT_ID, + VIRTIO_SERIAL_OPEN_RETRY_TIMES, + VIRTIO_SERIAL_OPEN_RETRY_TIMES * VIRTIO_SERIAL_OPEN_RETRY_DELAY.as_millis() as usize, + ); + return Err(VirtioSerialError::PortNotAvailable(VIRTIO_SERIAL_PORT_ID).into()); + } return Ok(port); }