use super::super::super::*;
use super::*;
#[test]
fn drain_port1_pending_rx_empty_pending_is_noop() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
dev.set_mem(mem.clone());
wire_port1_rxq_to_mock(&mut dev, &mock);
open_port1(&mut dev);
let data_addr = GuestAddress(0x10000);
let descs = [RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
64,
VRING_DESC_F_WRITE as u16,
0,
))];
mock.build_desc_chain(&descs).expect("build chain");
assert!(
dev.ports[1].pending_rx.is_empty(),
"precondition: port1_pending_rx must start empty"
);
let int_before = dev.interrupt_status;
dev.drain_pending_rx(1);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(
used_idx, 0,
"empty-pending fast-exit must not touch the queue"
);
assert_eq!(
dev.interrupt_status, int_before,
"empty-pending fast-exit must not call signal_used"
);
}
#[test]
fn drain_port1_pending_rx_defers_without_driver_ok() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let data_addr = GuestAddress(0x10000);
let descs = [RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
64,
VRING_DESC_F_WRITE as u16,
0,
))];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_ACK);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_DRV);
write_reg(&mut dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 0);
write_reg(
&mut dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1 << VIRTIO_CONSOLE_F_MULTIPORT,
);
write_reg(&mut dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 1);
write_reg(
&mut dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1 << (VIRTIO_F_VERSION_1 - 32),
);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_FEAT);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_SEL, PORT1_RXQ as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NUM, 16);
let desc = mock.desc_table_addr().0;
let avail = mock.avail_addr().0;
let used = mock.used_addr().0;
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_DESC_LOW, desc as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_DESC_HIGH, (desc >> 32) as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_AVAIL_LOW, avail as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, (avail >> 32) as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_USED_LOW, used as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_USED_HIGH, (used >> 32) as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_READY, 1);
dev.ports[1].opened = true;
let payload = b"snapshot reply bytes";
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"DRIVER_OK gate must hold bytes in pending_rx"
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 0, "DRIVER_OK gate must skip add_used");
}
#[test]
fn drain_port1_pending_rx_defers_without_multiport() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let data_addr = GuestAddress(0x10000);
let descs = [RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
64,
VRING_DESC_F_WRITE as u16,
0,
))];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_ACK);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_DRV);
write_reg(&mut dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 1);
write_reg(
&mut dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1 << (VIRTIO_F_VERSION_1 - 32),
);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_FEAT);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_SEL, PORT1_RXQ as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NUM, 16);
let desc = mock.desc_table_addr().0;
let avail = mock.avail_addr().0;
let used = mock.used_addr().0;
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_DESC_LOW, desc as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_DESC_HIGH, (desc >> 32) as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_AVAIL_LOW, avail as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, (avail >> 32) as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_USED_LOW, used as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_USED_HIGH, (used >> 32) as u32);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_READY, 1);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, S_OK);
assert_eq!(
dev.device_status, S_OK,
"precondition: FSM must reach DRIVER_OK"
);
assert!(
!dev.multiport_negotiated(),
"precondition: F_MULTIPORT must NOT be negotiated"
);
dev.ports[1].opened = true;
let payload = b"reply bytes that must not leak";
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"F_MULTIPORT gate must hold bytes in pending_rx"
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 0, "F_MULTIPORT gate must skip add_used");
}
#[test]
fn drain_port1_pending_rx_defers_until_port_open() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let data_addr = GuestAddress(0x10000);
let payload = b"deferred snapshot reply";
let descs = [RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
64,
VRING_DESC_F_WRITE as u16,
0,
))];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_port1_rxq_to_mock(&mut dev, &mock);
assert!(
!dev.ports[1].opened,
"precondition: port_opened[1] must be false"
);
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"port_opened[1] gate must defer when guest has not opened port 1"
);
let used_idx_before: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx before open");
assert_eq!(used_idx_before, 0, "port_opened[1] gate must skip add_used");
open_port1(&mut dev);
assert!(
dev.ports[1].pending_rx.is_empty(),
"after PORT_OPEN, deferred bytes must drain"
);
let used_idx_after: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx after open");
assert_eq!(
used_idx_after, 1,
"after PORT_OPEN, the deferred chain must add_used"
);
let mut readback = vec![0u8; payload.len()];
mem.read_slice(&mut readback, data_addr)
.expect("read back delivered payload");
assert_eq!(
readback, payload,
"delivered bytes must match the queued payload verbatim"
);
}
#[test]
fn drain_port1_pending_rx_defers_without_mem() {
let mut dev = VirtioConsole::new();
init_device(&mut dev);
assert!(dev.mem.is_none(), "precondition: mem must be None");
dev.ports[1].opened = true;
let payload = b"reply bytes pre-set_mem";
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"no-mem gate must hold bytes in pending_rx"
);
}
#[test]
fn drain_port1_pending_rx_defers_when_queue_not_ready() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
dev.set_mem(mem);
init_device(&mut dev);
assert!(
!dev.queues[PORT1_RXQ].ready(),
"precondition: q4 must NOT be ready"
);
dev.ports[1].opened = true;
let payload = b"reply bytes before queue ready";
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"queue-not-ready gate must hold bytes in pending_rx"
);
}
#[test]
fn drain_port1_pending_rx_single_descriptor_happy_path() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let data_addr = GuestAddress(0x10000);
let payload = b"snapshot reply payload bytes";
let descs = [RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
payload.len() as u32,
VRING_DESC_F_WRITE as u16,
0,
))];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_port1_rxq_to_mock(&mut dev, &mock);
open_port1(&mut dev);
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
assert!(
dev.ports[1].pending_rx.is_empty(),
"happy-path drain must consume all pending bytes"
);
let mut readback = vec![0u8; payload.len()];
mem.read_slice(&mut readback, data_addr)
.expect("read back delivered payload");
assert_eq!(
readback, payload,
"delivered bytes must equal the queued payload"
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1, "happy-path drain must add_used exactly once");
assert_ne!(
dev.interrupt_status & VIRTIO_MMIO_INT_VRING,
0,
"INT_VRING must be set after a non-zero drain"
);
let irq_count = dev.irq_evt.read().expect("irq_evt was written");
assert!(
irq_count > 0,
"irq_evt counter must be non-zero after signal_used"
);
}
#[test]
fn drain_port1_pending_rx_torn_write_publishes_head_with_zero_len() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let valid_addr = GuestAddress(0x10000);
let unmapped_addr = GuestAddress(4 << 20);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
valid_addr.0,
32,
(VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16,
1,
)),
RawDescriptor::from(SplitDescriptor::new(
unmapped_addr.0,
32,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build torn chain");
dev.set_mem(mem.clone());
wire_port1_rxq_to_mock(&mut dev, &mock);
open_port1(&mut dev);
let payload: Vec<u8> = (0..64u8).collect();
dev.ports[1].pending_rx.extend(payload.iter().copied());
let int_before = dev.interrupt_status;
let _ = dev.irq_evt.read();
dev.drain_pending_rx(1);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(
used_idx, 1,
"torn-write recovery must add_used the chain head (with len=0)"
);
let used_elem_len: u32 = mem
.read_obj(mock.used_addr().checked_add(8).unwrap())
.expect("read used elem 0 len");
assert_eq!(
used_elem_len, 0,
"torn-write recovery must publish len=0 for the chain head \
— a non-zero len would tell the guest the descriptor was \
fully filled, leading to data corruption"
);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"torn-write recovery must preserve bytes in pending_rx \
for retry on the next drain cycle"
);
let preserved: Vec<u8> = dev.ports[1].pending_rx.iter().copied().collect();
assert_eq!(
preserved, payload,
"preserved bytes must be the original payload verbatim"
);
assert_eq!(
dev.interrupt_status, int_before,
"torn-only chain must not trigger signal_used (total_written=0)"
);
match dev.irq_evt.read() {
Ok(n) => panic!("irq_evt must NOT have been written, got {n}"),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(e) => panic!("unexpected irq_evt read error: {e}"),
}
}
#[test]
fn drain_port1_pending_rx_torn_write_breaks_drain_loop() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let valid_addr_a = GuestAddress(0x10000);
let unmapped_addr = GuestAddress(4 << 20);
let valid_addr_b = GuestAddress(0x20000);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
valid_addr_a.0,
32,
(VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16,
1,
)),
RawDescriptor::from(SplitDescriptor::new(
unmapped_addr.0,
32,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
valid_addr_b.0,
32,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.add_desc_chains(&descs, 0).expect("publish two chains");
dev.set_mem(mem.clone());
wire_port1_rxq_to_mock(&mut dev, &mock);
open_port1(&mut dev);
let payload: Vec<u8> = (0..64u8).collect();
dev.ports[1].pending_rx.extend(payload.iter().copied());
dev.drain_pending_rx(1);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(
used_idx, 1,
"torn-write recovery must break the drain loop — chain 1 \
must remain unconsumed even though its descriptor is valid"
);
let mut readback_b = vec![0u8; 32];
mem.read_slice(&mut readback_b, valid_addr_b)
.expect("read back chain 1 data region");
assert!(
readback_b.iter().all(|&b| b == 0),
"chain 1's data region must be untouched — the drain loop \
must NOT have reached chain 1 after the torn break"
);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len(),
"all bytes must remain in pending_rx (torn chain consumed nothing)"
);
dev.drain_pending_rx(1);
let used_idx_after: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx after second drain");
assert_eq!(
used_idx_after, 2,
"second drain must process chain 1 (used.idx 1 -> 2)"
);
let mut readback_b2 = vec![0u8; 32];
mem.read_slice(&mut readback_b2, valid_addr_b)
.expect("read chain 1 data after second drain");
assert_eq!(
readback_b2,
payload[..32],
"chain 1 must hold the first 32 bytes of the preserved payload"
);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len() - 32,
"second drain must consume only chain 1's capacity (32 bytes)"
);
}
#[test]
fn drain_port1_pending_rx_torn_after_success_still_signals() {
let mut dev = VirtioConsole::new();
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let valid_addr_a = GuestAddress(0x10000);
let valid_addr_b = GuestAddress(0x20000);
let unmapped_addr = GuestAddress(4 << 20);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
valid_addr_a.0,
32,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
valid_addr_b.0,
32,
(VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16,
2,
)),
RawDescriptor::from(SplitDescriptor::new(
unmapped_addr.0,
32,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.add_desc_chains(&descs, 0)
.expect("publish success+torn chains");
dev.set_mem(mem.clone());
wire_port1_rxq_to_mock(&mut dev, &mock);
open_port1(&mut dev);
let payload: Vec<u8> = (0..96u8).collect();
dev.ports[1].pending_rx.extend(payload.iter().copied());
let _ = dev.irq_evt.read();
dev.drain_pending_rx(1);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(
used_idx, 2,
"drain must add_used both chain 0 (len=32) and chain 1 \
(torn, len=0)"
);
assert_ne!(
dev.interrupt_status & VIRTIO_MMIO_INT_VRING,
0,
"signal_used must have been called because chain 0 \
delivered 32 bytes"
);
let irq_count = dev.irq_evt.read().expect("irq_evt was written");
assert!(
irq_count > 0,
"irq_evt counter must be non-zero after signal_used"
);
let mut readback_a = vec![0u8; 32];
mem.read_slice(&mut readback_a, valid_addr_a)
.expect("read chain 0 data");
assert_eq!(
readback_a,
payload[..32],
"chain 0 must hold the first 32 bytes of the payload"
);
assert_eq!(
dev.ports[1].pending_rx.len(),
payload.len() - 32,
"only chain 0's bytes were consumed from pending_rx"
);
let preserved: Vec<u8> = dev.ports[1].pending_rx.iter().copied().collect();
assert_eq!(
preserved,
payload[32..],
"preserved bytes must be exactly the suffix not delivered \
to chain 0 (chain 1's first descriptor's partial write \
does NOT consume from the deque)"
);
}