//! Virtio MMIO transport identity checks. A DTB slot is only a candidate; //! device presence begins with a volatile MagicValue register read. pub const MAGIC: u32 = 0x7472_6976; pub const QEMU_VENDOR: u32 = 0x554d_4551; pub const BLK_F_RO: u32 = 1 << 5; pub const BLK_F_FLUSH: u32 = 1 << 9; pub const MODERN_F_VERSION_1: u32 = 1; pub const MODERN_F_IOMMU_PLATFORM: u32 = 1 << 1; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { WrongMagic, UnsupportedVersion, MissingDevice, WrongDevice, WrongVendor, MissingRequiredFeature, NegotiationRejected, EmptyQueue, QueueTooLarge, UnexpectedInterruptSource, ZeroCapacity, CapacityOverflow, SectorOutsideCapacity, ReadOnly, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BlockOperation { Read, Write, } /// The bounded block path only constructs read requests after RO was /// negotiated. A write request has no representable outgoing type here. pub fn read_only_request_type(operation: BlockOperation, accepted_low: u32) -> Result { if accepted_low & BLK_F_RO == 0 { return Err(Error::MissingRequiredFeature); } match operation { BlockOperation::Read => Ok(0), BlockOperation::Write => Err(Error::ReadOnly), } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BlockAbsence { pub slots: usize, pub empty: usize, pub other_devices: usize, } /// A diskless boot may still have an occupied non-block virtio bridge. The /// block queue must not be allocated unless DeviceID=2 is actually present. pub fn classify_block_absence(ids: &[u32]) -> Result { if ids.is_empty() { return Err(Error::MissingDevice); } let mut empty = 0usize; let mut other_devices = 0usize; for id in ids { match *id { 0 => empty += 1, 4 => other_devices += 1, 2 => return Err(Error::WrongDevice), _ => return Err(Error::WrongDevice), } } Ok(BlockAbsence { slots: ids.len(), empty, other_devices, }) } /// K323 checks transport identity only. An unoccupied QEMU virtio-mmio bridge /// still advertises this magic; DeviceID (K325) determines device presence. pub fn validate_magic(value: u32) -> Result<(), Error> { if value == MAGIC { Ok(()) } else { Err(Error::WrongMagic) } } /// K324 pins the QEMU virt-11.1 transport to its observed legacy MMIO v1. /// Modern v2 uses a different queue layout and must not silently enter this path. pub fn validate_version(value: u32) -> Result<(), Error> { if value == 1 { Ok(()) } else { Err(Error::UnsupportedVersion) } } /// K325: DeviceID=0 denotes an unoccupied bridge; 2 is virtio-blk. A /// non-block device must not be handed to the block driver. pub fn require_block_device(value: u32) -> Result<(), Error> { match value { 0 => Err(Error::MissingDevice), 2 => Ok(()), _ => Err(Error::WrongDevice), } } /// K421 keeps a virtio-net bridge (DeviceID=1) out of the block path. pub fn require_net_device(value: u32) -> Result<(), Error> { match value { 0 => Err(Error::MissingDevice), 1 => Ok(()), _ => Err(Error::WrongDevice), } } #[cfg(test)] mod net_identity_tests { use super::*; #[test] fn k421_net_id_is_never_a_block_device() { assert_eq!(require_net_device(1), Ok(())); assert_eq!(require_block_device(1), Err(Error::WrongDevice)); assert_eq!(require_net_device(0), Err(Error::MissingDevice)); assert_eq!(require_net_device(2), Err(Error::WrongDevice)); assert_eq!(require_net_device(4), Err(Error::WrongDevice)); assert_eq!(require_net_device(u32::MAX), Err(Error::WrongDevice)); } } /// K326 pins the test fixture to QEMU's little-endian VendorID register. /// It is provenance for these tests, not an allowlist for a future driver. pub fn validate_fixture_vendor(value: u32) -> Result<(), Error> { if value == QEMU_VENDOR { Ok(()) } else { Err(Error::WrongVendor) } } /// K327: the first read-only block path understands only RO. Retain offered /// bits for diagnostics, but never pass unimplemented features to the device. pub fn select_read_only_features(offered: u32) -> u32 { offered & BLK_F_RO } /// K328's isolated modern-v2 fixture needs both read-only block semantics and /// VIRTIO_F_VERSION_1. IOMMU_PLATFORM is required when offered by this fixture. pub fn select_modern_read_only_features(low: u32, high: u32) -> Result<(u32, u32), Error> { if low & BLK_F_RO == 0 || high & MODERN_F_VERSION_1 == 0 { return Err(Error::MissingRequiredFeature); } Ok(( BLK_F_RO, high & (MODERN_F_VERSION_1 | MODERN_F_IOMMU_PLATFORM), )) } /// K381 is intentionally unavailable in ordinary builds. It admits only a /// writable, modern, IOMMU-free fixture; K383 will add the first actual write /// request to a separate disposable disk, not to the production RO path. #[cfg(any( feature = "virtio-write-gate-test", feature = "virtio-metadata-order-test", feature = "p12-journal-test" ))] pub fn select_experimental_write_features(low: u32, high: u32) -> Result<(u32, u32), Error> { if low & BLK_F_RO != 0 || high & MODERN_F_VERSION_1 == 0 || high & MODERN_F_IOMMU_PLATFORM != 0 { return Err(Error::MissingRequiredFeature); } Ok((0, MODERN_F_VERSION_1)) } /// K387 probes FLUSH only in its disposable write experiment. A successful /// feature readback is not a completed FLUSH request or a durability promise. #[cfg(any( feature = "virtio-flush-gate-test", feature = "virtio-metadata-order-test", feature = "p12-journal-test" ))] pub fn select_experimental_flush_features(low: u32, high: u32) -> Result<(u32, u32), Error> { select_experimental_write_features(low, high)?; if low & BLK_F_FLUSH == 0 { return Err(Error::MissingRequiredFeature); } Ok((BLK_F_FLUSH, MODERN_F_VERSION_1)) } /// K329: status can reach DRIVER_OK only after the device retains the full /// ACK|DRIVER|FEATURES_OK prefix. The returned value is deferred until queues /// exist; the K329 probe intentionally never writes it to the device. pub fn deferred_driver_ready_status(status: u32) -> Result { if status == 0x0b { Ok(0x0f) } else { Err(Error::NegotiationRejected) } } /// K330 only validates the device's advertised queue maximum; queue memory /// ownership and size selection belong to later moves. pub fn validate_queue_max(max: u32) -> Result { if max == 0 { Err(Error::EmptyQueue) } else if max > 1024 { Err(Error::QueueTooLarge) } else { Ok(max) } } /// Only a used-buffer notification may be acknowledged by the block queue. /// Configuration-change bit 1, if present, belongs to a separate handler. pub fn used_interrupt_ack_mask(status: u32) -> Result { if status & !0b11 != 0 || status & 1 == 0 { Err(Error::UnexpectedInterruptSource) } else { Ok(1) } } /// Virtio-blk capacity is a count of 512-byte sectors, irrespective of the /// optional physical block-size feature (which this read-only probe declines). pub fn capacity_bytes(sectors: u64) -> Result { if sectors == 0 { return Err(Error::ZeroCapacity); } sectors.checked_mul(512).ok_or(Error::CapacityOverflow) } pub fn sector_byte_offset(sector: u64, capacity_sectors: u64) -> Result { capacity_bytes(capacity_sectors)?; if sector >= capacity_sectors { return Err(Error::SectorOutsideCapacity); } sector.checked_mul(512).ok_or(Error::CapacityOverflow) } /// An empty read is a successful no-op: no descriptor chain or QueueNotify. /// A non-empty read must fit entirely inside the end-exclusive capacity. pub fn checked_read_span( first_sector: u64, sector_count: u64, capacity_sectors: u64, ) -> Result, Error> { capacity_bytes(capacity_sectors)?; if sector_count == 0 { return Ok(None); } let end = first_sector .checked_add(sector_count) .ok_or(Error::SectorOutsideCapacity)?; if end > capacity_sectors { return Err(Error::SectorOutsideCapacity); } let start_byte = sector_byte_offset(first_sector, capacity_sectors)?; let byte_count = sector_count .checked_mul(512) .ok_or(Error::CapacityOverflow)?; Ok(Some((start_byte, byte_count))) } #[cfg(test)] mod tests { use super::*; #[test] fn k338_used_interrupt_ack_does_not_clear_config_source() { assert_eq!(used_interrupt_ack_mask(1), Ok(1)); assert_eq!(used_interrupt_ack_mask(3), Ok(1)); for status in [0, 2, 4, 5, u32::MAX] { assert_eq!( used_interrupt_ack_mask(status), Err(Error::UnexpectedInterruptSource) ); } } #[test] fn k341_capacity_rejects_zero_and_byte_count_overflow() { assert_eq!(capacity_bytes(2048), Ok(1_048_576)); assert_eq!(capacity_bytes(0), Err(Error::ZeroCapacity)); assert_eq!(capacity_bytes(u64::MAX), Err(Error::CapacityOverflow)); } #[test] fn k342_sector_offsets_use_512_bytes_and_reject_end_exclusive_boundary() { assert_eq!(sector_byte_offset(0, 2048), Ok(0)); assert_eq!(sector_byte_offset(1, 2048), Ok(512)); assert_eq!(sector_byte_offset(2047, 2048), Ok(1_048_064)); assert_eq!( sector_byte_offset(2048, 2048), Err(Error::SectorOutsideCapacity) ); assert_eq!(sector_byte_offset(0, 0), Err(Error::ZeroCapacity)); } #[test] fn k347_last_valid_sector_ends_exactly_at_capacity() { for sectors in [1, 2, 2048, 65_536] { let bytes = capacity_bytes(sectors).unwrap(); assert_eq!(sector_byte_offset(sectors - 1, sectors), Ok(bytes - 512)); assert_eq!( sector_byte_offset(sectors, sectors), Err(Error::SectorOutsideCapacity) ); } } #[test] fn k348_outside_lba_rejects_without_admitting_a_queue_request() { // Admission is intentionally downstream from the capacity preflight. let mut accepted = 0u16; for lba in [2048, 2049, u64::MAX] { if sector_byte_offset(lba, 2048).is_ok() { accepted += 1; } assert_eq!(accepted, 0); } assert_eq!(sector_byte_offset(2047, 2048), Ok(1_048_064)); } #[test] fn k349_zero_sector_read_is_an_explicit_no_op() { assert_eq!(checked_read_span(0, 0, 2048), Ok(None)); assert_eq!(checked_read_span(2048, 0, 2048), Ok(None)); assert_eq!(checked_read_span(u64::MAX, 0, 2048), Ok(None)); assert_eq!(checked_read_span(0, 0, 0), Err(Error::ZeroCapacity)); assert_eq!( checked_read_span(2048, 1, 2048), Err(Error::SectorOutsideCapacity) ); assert_eq!( checked_read_span(2047, 2, 2048), Err(Error::SectorOutsideCapacity) ); assert_eq!( checked_read_span(u64::MAX, 2, 2048), Err(Error::SectorOutsideCapacity) ); } #[test] fn k350_two_sector_span_checks_full_byte_count_and_end_boundary() { assert_eq!(checked_read_span(2, 2, 2048), Ok(Some((1024, 1024)))); assert_eq!( checked_read_span(2046, 2, 2048), Ok(Some((1_047_552, 1024))) ); assert_eq!( checked_read_span(2047, 2, 2048), Err(Error::SectorOutsideCapacity) ); assert_eq!( checked_read_span(u64::MAX, 2, 2048), Err(Error::SectorOutsideCapacity) ); } #[test] fn k323_accepts_only_little_endian_virtio_magic() { assert_eq!(validate_magic(MAGIC), Ok(())); assert_eq!(validate_magic(MAGIC.swap_bytes()), Err(Error::WrongMagic)); assert_eq!(validate_magic(0x7472_6977), Err(Error::WrongMagic)); } #[test] fn k323_zero_magic_is_rejected_and_never_proves_absence() { assert_eq!(validate_magic(0), Err(Error::WrongMagic)); } #[test] fn k324_accepts_only_observed_legacy_mmio_version_one() { assert_eq!(validate_version(1), Ok(())); for bad in [0, 2, 3, u32::MAX] { assert_eq!(validate_version(bad), Err(Error::UnsupportedVersion)); } } #[test] fn k324_magic_success_does_not_bypass_version_rejection() { assert_eq!(validate_magic(MAGIC), Ok(())); assert_eq!(validate_version(2), Err(Error::UnsupportedVersion)); } #[test] fn k325_selects_only_block_device_id_two() { assert_eq!(require_block_device(2), Ok(())); for other in [1, 3, 4, u32::MAX] { assert_eq!(require_block_device(other), Err(Error::WrongDevice)); } } #[test] fn k325_empty_bridge_is_not_a_block_device() { assert_eq!(require_block_device(0), Err(Error::MissingDevice)); assert_eq!(validate_magic(MAGIC), Ok(())); assert_eq!(validate_version(1), Ok(())); } #[test] fn k358_disk_absence_allows_empty_bridges_and_rng_but_rejects_block() { assert_eq!( classify_block_absence(&[0, 4, 0]), Ok(BlockAbsence { slots: 3, empty: 2, other_devices: 1 }) ); assert_eq!( classify_block_absence(&[0, 0]), Ok(BlockAbsence { slots: 2, empty: 2, other_devices: 0 }) ); assert_eq!(classify_block_absence(&[]), Err(Error::MissingDevice)); assert_eq!(classify_block_absence(&[0, 2, 4]), Err(Error::WrongDevice)); assert_eq!(classify_block_absence(&[0, 1]), Err(Error::WrongDevice)); } #[test] fn k359_read_only_policy_never_emits_a_write_request_type() { assert_eq!( read_only_request_type(BlockOperation::Read, BLK_F_RO), Ok(0) ); assert_eq!( read_only_request_type(BlockOperation::Write, BLK_F_RO), Err(Error::ReadOnly) ); assert_eq!( read_only_request_type(BlockOperation::Read, 0), Err(Error::MissingRequiredFeature) ); assert_eq!( read_only_request_type(BlockOperation::Write, 0), Err(Error::MissingRequiredFeature) ); assert_eq!( select_modern_read_only_features(0, MODERN_F_VERSION_1), Err(Error::MissingRequiredFeature) ); } #[test] fn k381_default_build_keeps_the_read_only_contract() { assert_eq!( read_only_request_type(BlockOperation::Write, BLK_F_RO), Err(Error::ReadOnly) ); assert_eq!( select_modern_read_only_features(0, MODERN_F_VERSION_1), Err(Error::MissingRequiredFeature) ); assert_eq!( select_modern_read_only_features(BLK_F_RO, MODERN_F_VERSION_1), Ok((BLK_F_RO, MODERN_F_VERSION_1)) ); } #[cfg(feature = "virtio-flush-reopen-test")] #[test] fn k389_reopen_checker_requires_a_read_only_modern_backend() { assert_eq!( select_modern_read_only_features(BLK_F_RO, MODERN_F_VERSION_1), Ok((BLK_F_RO, MODERN_F_VERSION_1)) ); assert_eq!( select_modern_read_only_features(0, MODERN_F_VERSION_1), Err(Error::MissingRequiredFeature) ); assert_eq!( select_modern_read_only_features(BLK_F_RO, 0), Err(Error::MissingRequiredFeature) ); } #[cfg(feature = "virtio-write-gate-test")] #[test] fn k381_experimental_selector_admits_only_writable_modern_non_iommu_fixture() { assert_eq!( select_experimental_write_features(0, MODERN_F_VERSION_1), Ok((0, 1)) ); assert_eq!( select_experimental_write_features(1 << 9, MODERN_F_VERSION_1), Ok((0, 1)) ); for (low, high) in [ (BLK_F_RO, MODERN_F_VERSION_1), (0, 0), (0, MODERN_F_VERSION_1 | MODERN_F_IOMMU_PLATFORM), ] { assert_eq!( select_experimental_write_features(low, high), Err(Error::MissingRequiredFeature) ); } } #[cfg(feature = "virtio-flush-gate-test")] #[test] fn k387_flush_selector_requires_bit_nine_without_weakening_write_gate() { assert_eq!(BLK_F_FLUSH, 0x200); assert_eq!( select_experimental_flush_features(BLK_F_FLUSH, MODERN_F_VERSION_1), Ok((BLK_F_FLUSH, MODERN_F_VERSION_1)) ); assert_eq!( select_experimental_flush_features(BLK_F_FLUSH | (1 << 6), MODERN_F_VERSION_1), Ok((BLK_F_FLUSH, MODERN_F_VERSION_1)) ); for (low, high) in [ (0, MODERN_F_VERSION_1), (BLK_F_RO | BLK_F_FLUSH, MODERN_F_VERSION_1), (BLK_F_FLUSH, 0), (BLK_F_FLUSH, MODERN_F_VERSION_1 | MODERN_F_IOMMU_PLATFORM), ] { assert_eq!( select_experimental_flush_features(low, high), Err(Error::MissingRequiredFeature) ); } assert_eq!( select_experimental_write_features(BLK_F_FLUSH, MODERN_F_VERSION_1), Ok((0, MODERN_F_VERSION_1)) ); } #[test] fn k326_qemu_fixture_vendor_has_exact_register_value() { assert_eq!(validate_fixture_vendor(QEMU_VENDOR), Ok(())); assert_eq!( validate_fixture_vendor(QEMU_VENDOR.swap_bytes()), Err(Error::WrongVendor) ); } #[test] fn k326_missing_or_mutated_vendor_is_not_the_recorded_fixture() { for wrong in [0, QEMU_VENDOR ^ 1, u32::MAX] { assert_eq!(validate_fixture_vendor(wrong), Err(Error::WrongVendor)); } } #[test] fn k327_unknown_bits_never_enter_read_only_feature_subset() { assert_eq!(select_read_only_features(u32::MAX), BLK_F_RO); assert_eq!( select_read_only_features(BLK_F_RO | (1 << 9) | (1 << 28)), BLK_F_RO ); assert_eq!(select_read_only_features(0), 0); } #[test] fn k327_read_only_bit_is_only_selected_when_device_offers_it() { assert_eq!(select_read_only_features(BLK_F_RO), BLK_F_RO); assert_eq!(select_read_only_features(1 << 6), 0); } #[test] fn k328_modern_subset_requires_ro_and_version_one_bit() { assert_eq!( select_modern_read_only_features(u32::MAX, u32::MAX), Ok((BLK_F_RO, 3)) ); assert_eq!( select_modern_read_only_features(BLK_F_RO, 1), Ok((BLK_F_RO, 1)) ); assert_eq!( select_modern_read_only_features(BLK_F_RO, 3), Ok((BLK_F_RO, 3)) ); assert_eq!( select_modern_read_only_features(0, 1), Err(Error::MissingRequiredFeature) ); assert_eq!( select_modern_read_only_features(BLK_F_RO, 0), Err(Error::MissingRequiredFeature) ); } #[test] fn k329_driver_ready_requires_exact_accepted_prefix() { assert_eq!(deferred_driver_ready_status(0x0b), Ok(0x0f)); for rejected in [0, 1, 2, 3, 7, 8, 0x0a, 0x0f, 0x8b] { assert_eq!( deferred_driver_ready_status(rejected), Err(Error::NegotiationRejected) ); } } #[test] fn k330_queue_max_rejects_zero_and_unbounded_values() { assert_eq!(validate_queue_max(0), Err(Error::EmptyQueue)); assert_eq!(validate_queue_max(1), Ok(1)); assert_eq!(validate_queue_max(256), Ok(256)); assert_eq!(validate_queue_max(1024), Ok(1024)); assert_eq!(validate_queue_max(1025), Err(Error::QueueTooLarge)); assert_eq!(validate_queue_max(u32::MAX), Err(Error::QueueTooLarge)); } }