#[derive(Serialize, Deserialize)] pubenum Failure { /// Failure due to the actual value read being different from the expected value
UnexpectedValue {
address: usize,
expected: usize,
actual: usize,
}, /// Failure due to the two memory locations being compared returning two different values /// This is used by tests where memory is split in two and values are written in pairs
MismatchedValues {
address1: usize,
value1: usize,
address2: usize,
value2: usize,
},
}
/// A pass can be done forward (default) or reversed #[derive(Debug, Default)] enum PassDirection { #[default]
Forward,
Reverse,
}
type PassFn<T> = fn(&mut T, direction: &mut PassDirection) -> IterFn<T>; type IterFn<T> = fn(&mut T, &mut usize) -> Result<(), Failure>;
trait TestAlgorithm: fmt::Debug { /// The number of runs the algorithm needs. Most tests can just accept the default of '1'
fn num_runs(&self) -> u64 { 1
} /// Initialize the state for the run given by `run_idx`. Most tests don't need to do anything.
fn start_run(&mutself, _run_idx: u64) {} /// Return a list of functions defining the behavior of the passes /// /// Each function receives a mutable reference to `direction`, which will always be set to /// the default of `Forward`. If a pass wants the direction reversed, it can set it to /// `Reverse`. /// /// Each function must return the iteration function for the pass. This is the function that /// will be run for each memory address. /// /// The iteration function itself takes a mutable reference to each memory address and /// returns either `Ok(())` or `Err(Failure)`.
fn passes(&self) -> Vec<PassFn<Self>>;
}
#[tracing::instrument(skip(memory, observer))]
fn run_test_algorithm<T: TestAlgorithm, O: TestObserver>( mut test: T,
memory: &mut [usize], mut observer: O,
) -> TestResult<O> { let expected_iter = u64::try_from(memory.len())
.ok()
.and_then(|count| count.checked_mul(test.num_runs()))
.and_then(|count| count.checked_mul(u64::try_from(test.passes().len()).ok()?))
.context("Total number of iterations overflowed")?;
observer.init(expected_iter);
for i in 0..test.num_runs() {
test.start_run(i);
for pass_fn in test.passes() { letmut direction = PassDirection::default(); let iter_fn = pass_fn(&mut test, &mut direction);
let mem_iter: Box<dyn Iterator<Item = &mut usize>> = match direction {
PassDirection::Forward => Box::new(memory.iter_mut()),
PassDirection::Reverse => Box::new(memory.iter_mut().rev()),
};
/// Write the address of each memory location to itself, then read back the value and check that it /// matches the expected address. #[derive(Debug, Default)] struct OwnAddressBasic {}
/// Write the address of each memory location (or its complement) to itself, then read back the /// value and check that it matches the expected address. /// This procedure is repeated 16 times. #[derive(Debug, Default)] struct OwnAddressRepeat {
complement: bool,
run_idx: u64,
}
type TwoRegionWriteFn<T> = fn(&mut T, &mut usize, &mut usize);
/// A two region test has to passes of memory for every run. The test splits memory into /// two halves. The first pass iterates through memory and writes some memory pattern to each /// pair of locations. The second pass reads through and compare the two halves. trait TwoRegionTestAlgorithm: fmt::Debug + Default { /// The number of runs the algorithm needs. Most tests can just accept the default of '1'
fn num_runs(&self) -> u64 { 1
} /// Initialize the state for the run given by `run_idx`. Most tests don't need to do anything.
fn start_run(&mutself, _run_idx: u64) {}
/// Returns whether a memory reset is needed at the start of each run /// If true, the algorithm will reset all bits to 1.
fn reset_before_run(&self) -> bool;
/// Returns the function that is called for every iteration of the first pass to write to /// memory.
fn write_fn(&self) -> TwoRegionWriteFn<Self>;
}
#[tracing::instrument(skip(memory, observer))] pub(super) fn run_two_region_test_algorithm<T: TwoRegionTestAlgorithm, O: TestObserver>( mut test: T,
memory: &mut [usize], mut observer: O,
) -> TestResult<O> { if test.reset_before_run() {
mem_reset(memory);
} let (first_half, second_half) = split_slice_in_half(memory)?; let expected_iter = u64::try_from(first_half.len())
.ok()
.and_then(|count| count.checked_mul(2))
.and_then(|count| count.checked_mul(test.num_runs()))
.context("Total number of iterations overflowed")?;
observer.init(expected_iter);
for i in 0..test.num_runs() {
test.start_run(i);
let write_fn = test.write_fn();
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
observer.check().map_err(Error::Observer)?;
write_fn(&mut test, first_ref, second_ref);
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each /// pair, write a random value. After all locations are written, read and compare the two halves. #[derive(Debug, Default)] pub(super) struct RandomVal {} impl TwoRegionTestAlgorithm for RandomVal {
fn reset_before_run(&self) -> bool { false
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
|_state, first_ref, second_ref| { let val = random();
write_volatile_safe(first_ref, val);
write_volatile_safe(second_ref, val);
}
}
}
let val = read_volatile_safe(first_ref); let new_val = $transform_fn(val, mixing_val);
write_volatile_safe(first_ref, new_val);
let val = read_volatile_safe(second_ref); let new_val = $transform_fn(val, mixing_val);
write_volatile_safe(second_ref, new_val);
}
};
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through /// memory locations in pairs. For each pair, write the XOR result of a random value and the value /// read from the location. After all locations are written, read and compare the two halves. #[derive(Debug, Default)] pub(super) struct Xor {} impl TwoRegionTestAlgorithm for Xor {
fn reset_before_run(&self) -> bool { true
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
two_region_write_fn_with_transform_fn!(std::ops::BitXor::bitxor)
}
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through /// memory locations in pairs. For each pair, write the result of subtracting a random value from /// the value read from the location. After all locations are written, read and compare the two /// halves. #[derive(Debug, Default)] pub(super) struct Sub {} impl TwoRegionTestAlgorithm for Sub {
fn reset_before_run(&self) -> bool { true
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
two_region_write_fn_with_transform_fn!(usize::wrapping_sub)
}
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through /// memory locations in pairs. For each pair, write the result of multiplying a random value with /// the value read from the location. After all locations are written, read and compare the two /// halves. #[derive(Debug, Default)] pub(super) struct Mul {} impl TwoRegionTestAlgorithm for Mul {
fn reset_before_run(&self) -> bool { true
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
two_region_write_fn_with_transform_fn!(usize::wrapping_mul)
}
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through /// memory locations in pairs. For each pair, write the result of dividing the value read from the /// location with a random value. After all locations are written, read and compare the two halves. #[derive(Debug, Default)] pub(super) struct Div {} impl TwoRegionTestAlgorithm for Div {
fn reset_before_run(&self) -> bool { true
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
two_region_write_fn_with_transform_fn!(
|n: usize, d: usize| n.wrapping_div(usize::max(d, 1))
)
}
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through /// memory locations in pairs. For each pair, write the OR result of a random value and the value /// read from the location. After all locations are written, read and compare the two halves. #[derive(Debug, Default)] pub(super) struct Or {} impl TwoRegionTestAlgorithm for Or {
fn reset_before_run(&self) -> bool { true
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
two_region_write_fn_with_transform_fn!(std::ops::BitOr::bitor)
}
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through /// memory locations in pairs. For each pair, write the AND result of a random value and the value /// read from the location. After all locations are written, read and compare the two halves. #[derive(Debug, Default)] pub(super) struct And {} impl TwoRegionTestAlgorithm for And {
fn reset_before_run(&self) -> bool { true
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
two_region_write_fn_with_transform_fn!(std::ops::BitAnd::bitand)
}
}
/// Split given memory into two halves and iterate through memory locations in pairs. Generate a /// random value at the start. For each pair, write the result of adding the random value and the /// index of iteration. After all locations are written, read and compare the two halves. #[derive(Debug, Default)] pub(super) struct SeqInc {
val: usize,
} impl TwoRegionTestAlgorithm for SeqInc {
fn reset_before_run(&self) -> bool { false
}
fn write_fn(&self) -> TwoRegionWriteFn<Self> {
|state, first_ref, second_ref| {
state.val = state.val.wrapping_add(1);
write_volatile_safe(first_ref, state.val);
write_volatile_safe(second_ref, state.val);
}
}
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each /// pair, write to all bits as either 1s or 0s, alternating after each memory location pair. /// After all locations are written, read and compare the two halves. /// This procedure is repeated 64 times. #[derive(Debug)] pub(super) struct SolidBits {
solid_bits: usize,
val: usize,
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each pair, /// write to a pattern of alternating 1s and 0s (in bytes it is either 0x55 or 0xaa, and alternating /// after each memory location pair). After all locations are written, read and compare the two /// halves. /// This procedure is repeated 64 times. #[derive(Debug)] pub(super) struct Checkerboard {
checker_board: usize,
val: usize,
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each pair, /// write to all bytes with the value i. After all locations are written, read and compare the two /// halves. /// This procedure is repeated 256 times, with i corresponding to the iteration number 0-255. #[derive(Debug, Default)] pub(super) struct BlockSeq {
val: usize,
}
/// These tests adapt the moving inversion algorithm implemented by [memtest86+](https://github.com/ /// memtest86plus/memtest86plus). As described in the the [Memtest86+ Test Algorithm Section](https://github.com/ /// memtest86plus/memtest86plus?tab=readme-ov-file#memtest86-test-algorithms), /// /// "The moving inversion tests work as follows: /// 1. Fill memory with a pattern /// 2. Starting at the lowest address /// i. check that the pattern has not changed /// ii. write the pattern's complement /// iii. increment the address /// iv. repeat 2.1 to 2.3 /// 3. Starting at the highest address /// i. check that the pattern has not changed /// ii. write the pattern's complement /// iii. decrement the address /// iv. repeat 3.1 to 3.3 " pub(super) trait MovInvAlgorithm: fmt::Debug + Default {
fn num_runs(&self) -> u64;
/// This test runs the moving inversion algorithm with fixed patterns of all bits as 1s or 0s. impl MovInvAlgorithm for FixedBlock {
fn num_runs(&self) -> u64 { 2
}
/// This test runs the moving inversion algorithm with fixed 8-bit patterns where 1 bit is 1/0 and the /// other 7 bits are 0/1s. The procedure is repeated 8 times with the pattern rotated by 1 bit each /// time to test all bits in a byte. #[derive(Debug)] pub(super) struct FixedBit {
run_idx: u64,
pattern: usize,
}
/// This test runs the moving inversion algorithm with a random fixed pattern. #[derive(Debug)] pub(super) struct FixedRandom {
run_idx: u64,
pattern: usize,
}
/// This test runs the moving inversion algorithm with a "walking" bit pattern. The algorithm starts /// with 0x1 (or the compliment of 0x1) and "walks" the bit by shifting left for every new memory /// location. /// The procedure is repeated with offsets 0-31 or 0-63 depending on the size of `usize` to test all /// bits in a memory location. impl MovInvAlgorithm for Walk {
fn num_runs(&self) -> u64 {
u64::from(usize::BITS) * 2
}
fn reset_rng(&mutself) { self.rng = SmallRng::seed_from_u64(self.seed);
}
}
} /// This test adapts the block move test algorithm implemented by [memtest86+](https://github.com/ /// memtest86plus/memtest86plus). For a detailed explanation of the algorithm, please refer to /// the description available at the [Memtest86+ Test Algorithm Section](https://github.com/ /// memtest86plus/memtest86plus?tab=readme-ov-file#memtest86-test-algorithms) /// /// The test aims to stress test the memory by moving blocks of memory, such as with the `movs` instruction. /// It first initializes the memory with an irregular shifting pattern. Then it performs 3 memory block moves. /// 1. Copy the first half of the memory region to the second half /// 2. Copy the second half of the memory region back to the first half, offset by 8 locations. /// ie. Copy the second half - the last 8 locations, to the first half's original location + 8 /// 3. Copy the second half's last 8 locations to the first half's first 8 locations /// Finally, the test verifies that the second half has the values of the original first half, and /// the first half's values are right rotated by 8 locations /// /// Note that the original implementation in Memtest86+ only verifies the values by comparing /// neighbouring pairs of memory location. Instead of that, this implementation verifies by /// recalculating the expected pattern in each location. /// /// Also note that unlike the other memory tests, `Observer::check()` is only called in the initial /// loop for initalizing memory and the final loop for verifying values. It is not called during the /// memory block moves, as it intefere with the stress testing of memory. Unfortunately, this means /// that `Observer::check()` will not be called for a siginficant duration of the test run time. #[tracing::instrument(skip_all)] pub fn run_block_move<O: TestObserver>(memory: &mut [usize], mut observer: O) -> TestResult<O> { const CHUNK_SIZE: usize = 16; const OFFSET: usize = 8; if memory.len() < CHUNK_SIZE {
Err(anyhow!("Insufficient memory length for Block Move Test"))?;
}
let expected_iter = u64::try_from(memory.len())
.ok()
.and_then(|count| count.checked_mul(2))
.context("Total number of iterations overflowed")?;
observer.init(expected_iter);
// Set up initial pattern in memory letmut pattern = 1;
for chunk in memory.chunks_exact_mut(CHUNK_SIZE) {
for (i, mem_ref) in chunk.iter_mut().enumerate() {
observer.check().map_err(Error::Observer)?;
let val = val_to_write(pattern, i);
write_volatile_safe(mem_ref, val);
}
pattern = pattern.rotate_left(1);
}
// Move blocks of memory around let (first_half, second_half) = split_slice_in_half(memory)?; let half_len = first_half.len();
volatile_copy_slice(second_half, first_half);
volatile_copy_slice(
&mut first_half[OFFSET..],
&second_half[..(half_len - OFFSET)],
);
volatile_copy_slice(
&mut first_half[..OFFSET],
&second_half[(half_len - OFFSET)..],
);
// Verify that values stored after block move are as expected, by traversing both halves at the // same time (with first half rotated by OFFSET), as they have the same expected values letmut pattern = 1;
for (chunk1, chunk2) in [&first_half[OFFSET..], &first_half[..OFFSET]]
.concat()
.chunks(CHUNK_SIZE)
.zip(second_half.chunks(CHUNK_SIZE))
{
for (i, (mem_ref1, mem_ref2)) in chunk1.iter().zip(chunk2.iter()).enumerate() { let expected = val_to_write(pattern, i);
for mem_ref in [mem_ref1, mem_ref2] {
observer.check().map_err(Error::Observer)?;
// TODO: In Memtest86+, block move is achieved with the `movs` assembly instruction
fn volatile_copy_slice<T: Copy>(dst: &mut [T], src: &[T]) {
assert_eq!(
dst.len(),
src.len(), "length of dst and src should be equal"
);
for (dst_ref, src_ref) in dst.iter_mut().zip(src.iter()) { let val = read_volatile_safe(src_ref);
write_volatile_safe(dst_ref, val);
}
}
/// This test uses the Modulo-20 algorithm implemented by [memtest86+](https://github.com/ /// memtest86plus/memtest86plus), which is designed to avoid effects of caching and buffering. /// /// The test generates a random value, then write the value to every 20th memory location. /// Afterwards write the complement of the value to all other locations one or more times (twice in /// this case). Then verify that the values stored in every 20th location is unchanged. /// /// The procedure is repeated with offsets 0-19 to test all memory locations. #[tracing::instrument(skip_all)] pub fn run_modulo_20<O: TestObserver>(memory: &mut [usize], mut observer: O) -> TestResult<O> { const STEP: usize = 20;
(memory.len() > STEP)
.then_some(())
.context("Insufficient memory length for two-regions memtest")?; let expected_iter = u64::try_from(memory.len())
.ok()
.and_then(|count| count.checked_mul((STEP * 2).try_into().unwrap()))
.context("Total number of iterations overflowed")?;
observer.init(expected_iter);
let pattern = random();
for offset in 0..STEP {
for mem_ref in memory.iter_mut().skip(offset).step_by(STEP) {
observer.check().map_err(Error::Observer)?;
write_volatile_safe(mem_ref, pattern);
}
for _ in 0..2 {
for (i, mem_ref) in memory.iter_mut().enumerate() { if i % STEP == offset { continue;
}
observer.check().map_err(Error::Observer)?;
write_volatile_safe(mem_ref, !pattern);
}
}
for mem_ref in memory.iter().skip(offset).step_by(STEP) {
observer.check().map_err(Error::Observer)?;
fn mem_reset(memory: &mut [usize]) {
for mem_ref in memory.iter_mut() {
write_volatile_safe(mem_ref, !0);
}
}
fn address_from_ref(r: &usize) -> usize {
std::ptr::from_ref(r) as usize
}
/// Return a usize where all bytes are set to to value of `byte`
fn usize_filled_from_byte(byte: u8) -> usize { letmut val = 0; unsafe { std::ptr::write_bytes(&mut val, byte, 1) }
val
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.