diff --git a/CHANGELOG.md b/CHANGELOG.md index 7416664a80f..0ade25ba437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ and this project adheres to ### Fixed +- [#5222](https://github.com/firecracker-microvm/firecracker/pull/5222): Fixed + network and rng devices locking up on hosts with non 4K pages. + ## [1.12.0] ### Added diff --git a/docs/kernel-policy.md b/docs/kernel-policy.md index 5f079f0d04b..d67496d8435 100644 --- a/docs/kernel-policy.md +++ b/docs/kernel-policy.md @@ -9,29 +9,29 @@ We are continuously validating the currently supported Firecracker releases (as per [Firecracker’s release policy](../docs/RELEASE_POLICY.md)) using a combination of all supported host and guest kernel versions in the table below. -While other versions and other kernel configs might work, they are not -periodically validated in our test suite, and using them might result in -unexpected behaviour. Starting with release `v1.0` each major and minor release -will specify the supported kernel versions. - Once a kernel version is officially added, it is supported for a **minimum of 2 years**. At least 2 major guest and host versions will be supported at any time. When support is added for a third kernel version, the oldest will be deprecated and removed in a following release, after its minimum end of support date. +**Note** While other versions and other kernel configs might work, they are not +periodically validated in our test suite, and using them might result in +unexpected behaviour. Starting with release `v1.0` each major and minor release +will specify the supported kernel versions. + ### Host Kernel -| Host kernel | Min. version | Min. end of support | -| ----------: | -----------: | ------------------: | -| v5.10 | v1.0.0 | 2024-01-31 | -| v6.1 | v1.5.0 | 2025-10-12 | +| Page size | Host kernel | Min. version | Min. end of support | +| --------: | ----------: | -----------: | ------------------: | +| 4K | v5.10 | v1.0.0 | 2024-01-31 | +| 4K | v6.1 | v1.5.0 | 2025-10-12 | ### Guest Kernel -| Guest kernel | Min. version | Min. end of support | -| -----------: | -----------: | ------------------: | -| v5.10 | v1.0.0 | 2024-01-31 | -| v6.1 | v1.9.0 | 2026-09-02 | +| Page size | Guest kernel | Min. version | Min. end of support | +| --------: | -----------: | -----------: | ------------------: | +| 4K | v5.10 | v1.0.0 | 2024-01-31 | +| 4K | v6.1 | v1.9.0 | 2026-09-02 | The guest kernel configs used in our validation pipelines can be found [here](../resources/guest_configs/) while a breakdown of the relevant guest diff --git a/src/vmm/src/devices/virtio/iov_deque.rs b/src/vmm/src/devices/virtio/iov_deque.rs index 55587d83843..fc57f9cecdf 100644 --- a/src/vmm/src/devices/virtio/iov_deque.rs +++ b/src/vmm/src/devices/virtio/iov_deque.rs @@ -69,7 +69,9 @@ pub enum IovDequeError { // Like that, the elements stored in the buffer are always laid out in contiguous virtual memory, // so making a slice out of them does not require any copies. // -// The `L` const generic determines the maximum number of `iovec` elements the queue should hold. +// The `L` const generic determines the maximum number of `iovec` elements the queue should hold +// at any point in time. The actual capacity of the queue may differ and will depend on the host +// page size. // // ```Rust // pub struct iovec { @@ -83,6 +85,7 @@ pub struct IovDeque { pub iov: *mut libc::iovec, pub start: u16, pub len: u16, + pub capacity: u16, } // SAFETY: This is `Send`. We hold sole ownership of the underlying buffer. @@ -158,6 +161,14 @@ impl IovDeque { /// Create a new [`IovDeque`] that can hold memory described by a single VirtIO queue. pub fn new() -> Result { let pages_bytes = Self::pages_bytes(); + let capacity = pages_bytes / std::mem::size_of::(); + let capacity: u16 = capacity.try_into().unwrap(); + assert!( + L <= capacity, + "Actual capacity {} is smaller than requested capacity {}", + capacity, + L + ); let memfd = Self::create_memfd(pages_bytes)?; let raw_memfd = memfd.as_file().as_raw_fd(); @@ -201,6 +212,7 @@ impl IovDeque { iov: buffer.cast(), start: 0, len: 0, + capacity, }) } @@ -258,8 +270,8 @@ impl IovDeque { self.start += nr_iovecs; self.len -= nr_iovecs; - if self.start >= L { - self.start -= L; + if self.capacity <= self.start { + self.start -= self.capacity; } } @@ -532,4 +544,46 @@ mod tests { assert_eq!(iov.iov_len, 2 * copy[i].iov_len); } } + + #[test] + fn test_size_less_than_capacity() { + // Usually we have a queue size of 256 which is a perfect fit + // for 4K pages. But with 16K or bigger pages the `perfect fit` + // is not perfect anymore. Need to ensure the wraparound logic + // remains valid in such cases. + const L: u16 = 16; + let mut deque = super::IovDeque::::new().unwrap(); + assert!(deque.as_mut_slice().is_empty()); + + // Number of times need to fill/empty the queue to reach the + // wraparound point. + let fills = deque.capacity / L; + + // Almost reach the wraparound. + for _ in 0..(fills - 1) { + for _ in 0..L { + deque.push_back(make_iovec(0, 100)); + } + deque.pop_front(L); + } + // 1 element away from the wraparound + for _ in 0..(L - 1) { + deque.push_back(make_iovec(0, 100)); + } + deque.pop_front(L - 1); + + // Start filling the 'second' page + // First element will be put at the end of the + // first page, while the rest will be in `second` + // page. + for _ in 0..L { + deque.push_back(make_iovec(1, 100)); + } + + // Pop one element to trigger the wraparound. + deque.pop_front(1); + // Now the slice should be pointing to the memory of the `first` page + // which should have the same content as the `second` page. + assert_eq!(deque.as_slice(), vec![make_iovec(1, 100); L as usize - 1]); + } } diff --git a/src/vmm/src/devices/virtio/iovec.rs b/src/vmm/src/devices/virtio/iovec.rs index 3865cc7ecf2..51352699660 100644 --- a/src/vmm/src/devices/virtio/iovec.rs +++ b/src/vmm/src/devices/virtio/iovec.rs @@ -927,6 +927,7 @@ mod verification { iov: mem.cast(), start: kani::any_where(|&start| start < FIRECRACKER_MAX_QUEUE_SIZE), len: 0, + capacity: FIRECRACKER_MAX_QUEUE_SIZE, } }