pub(crate) const PAGE_CHUNK: u64 = 4096;
const _: () = assert!(PAGE_CHUNK.is_power_of_two());
pub(crate) fn chunked_kva_io<T, F>(
translate: T,
target_kva: u64,
len: usize,
mut chunk_fn: F,
) -> bool
where
T: Fn(u64) -> Option<u64>,
F: FnMut(u64, u64, usize),
{
let mut consumed: u64 = 0;
let total = len as u64;
while consumed < total {
let kva = target_kva + consumed;
let Some(pa) = translate(kva) else {
return false;
};
let page_end = (kva & !(PAGE_CHUNK - 1)) + PAGE_CHUNK;
let chunk_len = (page_end - kva).min(total - consumed) as usize;
chunk_fn(pa, consumed, chunk_len);
consumed += chunk_len as u64;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
fn run_with_capture<T>(
translate: T,
target_kva: u64,
len: usize,
) -> (bool, Vec<(u64, u64, usize)>)
where
T: Fn(u64) -> Option<u64>,
{
let captured = RefCell::new(Vec::new());
let ok = chunked_kva_io(translate, target_kva, len, |pa, src_off, chunk_len| {
captured.borrow_mut().push((pa, src_off, chunk_len));
});
(ok, captured.into_inner())
}
#[test]
fn chunked_kva_io_single_page_range_one_translate_one_chunk() {
let translate_calls = RefCell::new(0u32);
let (ok, chunks) = run_with_capture(
|kva| {
*translate_calls.borrow_mut() += 1;
Some(kva ^ 0xFFFF_FFFF_F000_0000) },
0x1000, 512, );
assert!(ok);
assert_eq!(*translate_calls.borrow(), 1);
assert_eq!(chunks.len(), 1);
let (_pa, src_off, chunk_len) = chunks[0];
assert_eq!(src_off, 0);
assert_eq!(chunk_len, 512);
}
#[test]
fn chunked_kva_io_multi_page_range_n_translate_n_chunks() {
let translate_calls = RefCell::new(0u32);
let (ok, chunks) = run_with_capture(
|kva| {
*translate_calls.borrow_mut() += 1;
Some(kva.wrapping_sub(0x1_0000)) },
0x1000, (PAGE_CHUNK as usize) * 2 + PAGE_CHUNK as usize / 2, );
assert!(ok);
assert_eq!(*translate_calls.borrow(), 3);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0].2, PAGE_CHUNK as usize);
assert_eq!(chunks[1].2, PAGE_CHUNK as usize);
assert_eq!(chunks[2].2, PAGE_CHUNK as usize / 2);
assert_eq!(chunks[0].1, 0);
assert_eq!(chunks[1].1, PAGE_CHUNK);
assert_eq!(chunks[2].1, PAGE_CHUNK * 2);
}
#[test]
fn chunked_kva_io_unaligned_start_kva_chunks_to_first_page_boundary() {
let offset_in_page: u64 = 0x100;
let total: u64 = PAGE_CHUNK + 0x200; let (ok, chunks) = run_with_capture(
|_kva| Some(0xdead_0000),
PAGE_CHUNK + offset_in_page, total as usize,
);
assert!(ok);
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0].2 as u64, PAGE_CHUNK - offset_in_page);
assert_eq!(chunks[1].2 as u64, total - (PAGE_CHUNK - offset_in_page));
}
#[test]
fn chunked_kva_io_translate_fails_mid_range_returns_false_short_chunks() {
let translate_calls = RefCell::new(0u32);
let (ok, chunks) = run_with_capture(
|_kva| {
let mut n = translate_calls.borrow_mut();
*n += 1;
if *n == 1 { Some(0x1000) } else { None }
},
0x1000,
(PAGE_CHUNK as usize) * 2,
);
assert!(!ok);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0].1, 0);
assert_eq!(chunks[0].2, PAGE_CHUNK as usize);
}
#[test]
fn chunked_kva_io_zero_length_returns_true_no_chunk_calls() {
let translate_calls = RefCell::new(0u32);
let (ok, chunks) = run_with_capture(
|_kva| {
*translate_calls.borrow_mut() += 1;
Some(0)
},
0xdead_beef,
0,
);
assert!(ok);
assert_eq!(*translate_calls.borrow(), 0);
assert!(chunks.is_empty());
}
#[test]
fn chunked_kva_io_translator_always_none_returns_false_no_chunks() {
let (ok, chunks) = run_with_capture(|_kva| None, 0x1000, 4096);
assert!(!ok);
assert!(chunks.is_empty());
}
#[test]
fn chunked_kva_io_chunk_at_last_byte_of_page_correctly_splits() {
let (ok, chunks) = run_with_capture(|_kva| Some(0), 0x1000, PAGE_CHUNK as usize + 1);
assert!(ok);
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0].2, PAGE_CHUNK as usize);
assert_eq!(chunks[1].2, 1);
}
}