Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  memtest.rs   Sprache: Rust

 

use {
    crate::prelude::*,
    rand::random,
    serde::{Deserialize, Deserializer, Serialize, Serializer},
    std::{error, fmt},
};

// TODO: Intend to convert this module to a standalone `no_std` crate

pub type TestResult<O> = Result<Outcome, Error<<O as TestObserver>::Error>>;

#[derive(Debug, Serialize, Deserialize)]
#[must_use]
pub enum Outcome {
    Pass,
    Fail(Failure),
}

#[derive(Serialize, Deserialize)]
pub enum 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,
    },
}

pub trait TestObserver {
    type Error: error::Error;
    fn init(&mut self, expected_iter: u64);
    fn check(&mut self) -> Result<(), Self::Error>;
}

#[derive(Debug, Serialize, Deserialize)]
pub enum Error<E> {
    Observer(E),
    #[serde(
        serialize_with = "serialize_error_other",
        deserialize_with = "deserialize_error_other"
    )]
    Other(anyhow::Error),
}

macro_rules! test_kinds {{
    $($variant: ident),* $(,)?
} => {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
    pub enum TestKind {
        $($variant,)*
    }

    impl TestKind {
        pub const ALL: &'static [Self] = &[
            $(Self::$variant),*
        ];
    }

    impl std::str::FromStr for TestKind {
        type Err = ParseTestKindError;
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            match s {
            $(
                stringify!($variant) => Ok(Self::$variant),
            )*
                _ => Err(ParseTestKindError),
            }
        }
    }
}}

test_kinds! {
    OwnAddressBasic,
    OwnAddressRepeat,
    RandomVal,
    Xor,
    Sub,
    Mul,
    Div,
    Or,
    And,
    SeqInc,
    SolidBits,
    Checkerboard,
    BlockSeq,
    MovInvFixedBlock,
    MovInvFixedBit,
    MovInvFixedRandom,
    MovInvWalk,
    BlockMove,
    MovInvRandom,
    Modulo20,
}

#[derive(Debug, PartialEq, Eq)]
pub struct ParseTestKindError;

impl TestKind {
    pub fn run<O: TestObserver>(&self, memory: &>mut [usize], observer: O) -> TestResult<O> {
        use two_region::{
            run_two_region_test_algorithm, And, BlockSeq, Checkerboard, Div, Mul, Or, RandomVal,
            SeqInc, SolidBits, Sub, Xor,
        };

        match self {
            Self::OwnAddressBasic => {
                run_test_algorithm(OwnAddressBasic::default(), memory, observer)
            }
            Self::OwnAddressRepeat => {
                run_test_algorithm(OwnAddressRepeat::default(), memory, observer)
            }
            Self::RandomVal => {
                run_two_region_test_algorithm(RandomVal::default(), memory, observer)
            }
            Self::Xor => run_two_region_test_algorithm(Xor::default(), memory, observer),
            Self::Sub => run_two_region_test_algorithm(Sub::default(), memory, observer),
            Self::Mul => run_two_region_test_algorithm(Mul::default(), memory, observer),
            Self::Div => run_two_region_test_algorithm(Div::default(), memory, observer),
            Self::Or => run_two_region_test_algorithm(Or::default(), memory, observer),
            Self::And => run_two_region_test_algorithm(And::default(), memory, observer),
            Self::SeqInc => run_two_region_test_algorithm(SeqInc::default(), memory, observer),
            Self::SolidBits => {
                run_two_region_test_algorithm(SolidBits::default(), memory, observer)
            }
            Self::Checkerboard => {
                run_two_region_test_algorithm(Checkerboard::default(), memory, observer)
            }
            Self::BlockSeq => run_two_region_test_algorithm(BlockSeq::default(), memory, observer),
            Self::MovInvFixedBlock => {
                run_test_algorithm(mov_inv::FixedBlock::default(), memory, observer)
            }
            Self::MovInvFixedBit => {
                run_test_algorithm(mov_inv::FixedBit::default(), memory, observer)
            }
            Self::MovInvFixedRandom => {
                run_test_algorithm(mov_inv::FixedRandom::default(), memory, observer)
            }
            Self::MovInvWalk => run_test_algorithm(mov_inv::Walk::default(), memory, observer),
            Self::BlockMove => run_block_move(memory, observer),
            Self::MovInvRandom => run_test_algorithm(mov_inv::Random::default(), memory, observer),
            Self::Modulo20 => run_modulo_20(memory, observer),
        }
    }
}

/// 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(&mut self, _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() {
            let mut 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()),
            };

            for mem_ref in mem_iter {
                observer.check().map_err(Error::Observer)?;
                if let Err(f) = iter_fn(&mut test, mem_ref) {
                    return Ok(Outcome::Fail(f));
                }
            }
        }
    }
    Ok(Outcome::Pass)
}

fn check_expected_value(address: usize, expected: usize, actual: usize) -> Result<(), Failure> {
    if actual != expected {
        info!("Test failed at 0x{address:x}");
        Err(Failure::UnexpectedValue {
            address,
            expected,
            actual,
        })
    } else {
        Ok(())
    }
}

/// 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 {}

impl TestAlgorithm for OwnAddressBasic {
    fn passes(&self) -> Vec<PassFn<Self>> {
        vec![Self::pass_0, Self::pass_1]
    }
}

impl OwnAddressBasic {
    fn pass_0(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
        |_state, mem_ref| {
            write_volatile_safe(mem_ref, address_from_ref(mem_ref));
            Ok(())
        }
    }

    fn pass_1(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
        |_state, mem_ref| {
            check_expected_value(
                address_from_ref(mem_ref),
                address_from_ref(mem_ref),
                read_volatile_safe(mem_ref),
            )
        }
    }
}

/// 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,
}

impl TestAlgorithm for OwnAddressRepeat {
    fn num_runs(&self) -> u64 {
        16
    }
    fn start_run(&mut self, run_idx: u64) {
        self.run_idx = run_idx;
    }
    fn passes(&self) -> Vec<PassFn<Self>> {
        vec![Self::pass_0, Self::pass_1]
    }
}

impl OwnAddressRepeat {
    fn pass_0(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
        self.init_complement();

        |state, mem_ref| {
            write_volatile_safe(mem_ref, state.val_to_write(mem_ref));

            state.complement = !state.complement;
            Ok(())
        }
    }

    fn pass_1(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
        self.init_complement();

        |state, mem_ref| {
            check_expected_value(
                address_from_ref(mem_ref),
                state.val_to_write(mem_ref),
                read_volatile_safe(mem_ref),
            )?;

            state.complement = !state.complement;
            Ok(())
        }
    }

    fn init_complement(&mut self) {
        self.complement = self.run_idx % 2 != 0;
    }

    fn val_to_write(&self, mem_ref: &usize) -> usize {
        if self.complement {
            !address_from_ref(mem_ref)
        } else {
            address_from_ref(mem_ref)
        }
    }
}

mod two_region {
    use {
        super::{
            address_from_ref, mem_reset, read_volatile_safe, split_slice_in_half,
            usize_filled_from_byte, write_volatile_safe, Error, Failure, Outcome, TestObserver,
            TestResult,
        },
        crate::prelude::*,
        rand::random,
        std::fmt,
    };

    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(&mut self, _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);
            }

            for (first_ref, second_ref) in first_half.iter().zip(second_half.iter()) {
                observer.check().map_err(Error::Observer)?;
                if let Err(f) = check_matching_values(
                    address_from_ref(first_ref),
                    read_volatile_safe(first_ref),
                    address_from_ref(second_ref),
                    read_volatile_safe(second_ref),
                ) {
                    return Ok(Outcome::Fail(f));
                }
            }
        }

        Ok(Outcome::Pass)
    }

    fn check_matching_values(
        address1: usize,
        value1: usize,
        address2: usize,
        value2: usize,
    ) -> Result<(), Failure> {
        if value1 != value2 {
            info!("Test failed at 0x{address1:x} compared to 0x{address2:x}");
            Err(Failure::MismatchedValues {
                address1,
                value1,
                address2,
                value2,
            })
        } else {
            Ok(())
        }
    }

    /// 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);
            }
        }
    }

    macro_rules! two_region_write_fn_with_transform_fn {
        ($transform_fn:expr) => {
            |_state, first_ref, second_ref| {
                let mixing_val: usize = random();

                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,
    }

    impl Default for SolidBits {
        fn default() -> Self {
            SolidBits {
                solid_bits: !0,
                val: usize::default(),
            }
        }
    }

    impl TwoRegionTestAlgorithm for SolidBits {
        fn num_runs(&self) -> u64 {
            64
        }

        fn start_run(&mut self, _run_idx: u64) {
            self.solid_bits = !self.solid_bits;
            self.val = self.solid_bits;
        }

        fn reset_before_run(&self) -> bool {
            false
        }

        fn write_fn(&self) -> TwoRegionWriteFn<Self> {
            |state, first_ref, second_ref| {
                state.val = !state.val;
                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 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,
    }

    impl Default for Checkerboard {
        fn default() -> Self {
            Checkerboard {
                checker_board: usize_filled_from_byte(0xaa),
                val: usize::default(),
            }
        }
    }

    impl TwoRegionTestAlgorithm for Checkerboard {
        fn num_runs(&self) -> u64 {
            64
        }

        fn start_run(&mut self, _run_idx: u64) {
            self.checker_board = !self.checker_board;
            self.val = self.checker_board;
        }

        fn reset_before_run(&self) -> bool {
            false
        }

        fn write_fn(&self) -> TwoRegionWriteFn<Self> {
            |state, first_ref, second_ref| {
                state.val = !state.val;
                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 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,
    }

    impl TwoRegionTestAlgorithm for BlockSeq {
        fn num_runs(&self) -> u64 {
            256
        }

        fn start_run(&mut self, run_idx: u64) {
            self.val = usize_filled_from_byte(run_idx.try_into().unwrap());
        }

        fn reset_before_run(&self) -> bool {
            false
        }

        fn write_fn(&self) -> TwoRegionWriteFn<Self> {
            |state, first_ref, second_ref| {
                write_volatile_safe(first_ref, state.val);
                write_volatile_safe(second_ref, state.val);
            }
        }
    }
}

mod mov_inv {
    use {
        super::{
            address_from_ref, check_expected_value, read_volatile_safe, usize_filled_from_byte,
            write_volatile_safe, IterFn, PassDirection, PassFn, TestAlgorithm,
        },
        rand::{random, rngs::SmallRng, Rng, SeedableRng},
        std::{
            fmt,
            time::{SystemTime, UNIX_EPOCH},
        },
    };

    /// 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;

        fn start_run(&mut self, _run_idx: u64);

        fn start_pass(&mut self, _pass_idx: u64) {}

        fn generate_pattern(&mut self) -> usize;

        fn backtrack_pattern(&mut self) -> usize;

        fn passes(&self) -> Vec<PassFn<Self>> {
            vec![Self::pass_0, Self::pass_1, Self::pass_2]
        }

        fn pass_0(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
            self.start_pass(0);
            |state, mem_ref| {
                let pattern = state.generate_pattern();
                write_volatile_safe(mem_ref, pattern);
                Ok(())
            }
        }

        fn pass_1(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
            self.start_pass(1);

            |state, mem_ref| {
                let pattern = state.generate_pattern();
                check_expected_value(
                    address_from_ref(mem_ref),
                    pattern,
                    read_volatile_safe(mem_ref),
                )?;

                write_volatile_safe(mem_ref, !pattern);
                Ok(())
            }
        }

        fn pass_2(&mut self, direction: &mut PassDirection) -> IterFn<Self> {
            *direction = PassDirection::Reverse;

            self.start_pass(2);

            |state, mem_ref| {
                let pattern = !state.backtrack_pattern();
                check_expected_value(
                    address_from_ref(mem_ref),
                    pattern,
                    read_volatile_safe(mem_ref),
                )?;

                write_volatile_safe(mem_ref, !pattern);
                Ok(())
            }
        }
    }

    impl<T: MovInvAlgorithm> TestAlgorithm for T {
        fn num_runs(&self) -> u64 {
            T::num_runs(self)
        }
        fn start_run(&mut self, run_idx: u64) {
            T::start_run(self, run_idx)
        }
        fn passes(&self) -> Vec<PassFn<Self>> {
            T::passes(self)
        }
    }

    #[derive(Debug, Default)]
    pub(super) struct FixedBlock {
        run_idx: 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
        }

        fn start_run(&mut self, run_idx: u64) {
            self.run_idx = run_idx;
        }

        fn generate_pattern(&mut self) -> usize {
            self.pattern()
        }

        fn backtrack_pattern(&mut self) -> usize {
            self.pattern()
        }
    }

    impl FixedBlock {
        fn pattern(&mut self) -> usize {
            if self.run_idx == 0 {
                0
            } else {
                !0
            }
        }
    }

    /// 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,
    }

    impl Default for FixedBit {
        fn default() -> Self {
            FixedBit {
                run_idx: u64::default(),
                pattern: usize_filled_from_byte(0x01),
            }
        }
    }

    impl MovInvAlgorithm for FixedBit {
        fn num_runs(&self) -> u64 {
            16
        }

        fn start_run(&mut self, run_idx: u64) {
            self.run_idx = run_idx;
            if run_idx % 2 == 0 {
                self.pattern = self.pattern.rotate_right(1);
            }
        }

        fn generate_pattern(&mut self) -> usize {
            self.pattern()
        }

        fn backtrack_pattern(&mut self) -> usize {
            self.pattern()
        }
    }

    impl FixedBit {
        fn pattern(&mut self) -> usize {
            if self.run_idx % 2 == 0 {
                self.pattern
            } else {
                !self.pattern
            }
        }
    }

    /// This test runs the moving inversion algorithm with a random fixed pattern.
    #[derive(Debug)]
    pub(super) struct FixedRandom {
        run_idx: u64,
        pattern: usize,
    }

    impl Default for FixedRandom {
        fn default() -> Self {
            FixedRandom {
                run_idx: u64::default(),
                pattern: random(),
            }
        }
    }

    impl MovInvAlgorithm for FixedRandom {
        fn num_runs(&self) -> u64 {
            2
        }

        fn start_run(&mut self, run_idx: u64) {
            self.run_idx = run_idx;
        }

        fn generate_pattern(&mut self) -> usize {
            self.pattern()
        }

        fn backtrack_pattern(&mut self) -> usize {
            self.pattern()
        }
    }

    impl FixedRandom {
        fn pattern(&mut self) -> usize {
            if self.run_idx % 2 == 0 {
                self.pattern
            } else {
                !self.pattern
            }
        }
    }

    #[derive(Debug, Default)]
    pub(super) struct Walk {
        run_idx: u64,
        starting_pattern: usize,
        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 start_run(&mut self, run_idx: u64) {
            self.run_idx = run_idx;
            self.starting_pattern = 1 << (run_idx / 2);
        }

        fn start_pass(&mut self, pass_idx: u64) {
            // For pass_idx == 2, pattern is not reset
            if pass_idx == 0 || pass_idx == 1 {
                self.pattern = self.starting_pattern;
            }
        }

        fn generate_pattern(&mut self) -> usize {
            let pattern = if self.run_idx % 2 == 0 {
                self.pattern
            } else {
                !self.pattern
            };
            self.pattern = self.pattern.rotate_left(1);
            pattern
        }

        fn backtrack_pattern(&mut self) -> usize {
            self.pattern = self.pattern.rotate_right(1);
            if self.run_idx % 2 == 0 {
                self.pattern
            } else {
                !self.pattern
            }
        }
    }

    // Despite its name MovInvRandom, this implementation (which follows the Memtest86+
    // implementation) is not similar to other moving inversion tests. Due to its nature of using
    // random numbers for each address, pass 2 does not traverse the memory in reverse.
    // TODO: Consider using a reversible RNG to match other moving inversion tests
    // See https://stackoverflow.com/questions/31513168/finding-inverse-operation-to-george-marsaglias-xorshift-rng
    // and https://stackoverflow.com/questions/31521910/simplify-the-inverse-of-z-x-x-y-function/31522122#31522122
    // for XORshift, which is the RNG used in Memtest86+
    #[derive(Debug)]
    pub(super) struct Random {
        seed: u64,
        rng: SmallRng,
    }

    impl Default for Random {
        fn default() -> Self {
            let seed = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_millis() as u64;
            Random {
                seed,
                rng: SmallRng::seed_from_u64(seed),
            }
        }
    }

    impl TestAlgorithm for Random {
        fn passes(&self) -> Vec<PassFn<Self>> {
            vec![Self::pass_0, Self::pass_1, Self::pass_2]
        }
    }

    impl Random {
        fn pass_0(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
            self.reset_rng();

            |state, mem_ref| {
                let pattern = state.rng.gen();
                write_volatile_safe(mem_ref, pattern);

                Ok(())
            }
        }

        fn pass_1(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
            self.reset_rng();

            |state, mem_ref| {
                let pattern = state.rng.gen();
                check_expected_value(
                    address_from_ref(mem_ref),
                    pattern,
                    read_volatile_safe(mem_ref),
                )?;

                write_volatile_safe(mem_ref, !pattern);
                Ok(())
            }
        }

        fn pass_2(&mut self, _direction: &mut PassDirection) -> IterFn<Self> {
            self.reset_rng();

            |state, mem_ref| {
                let pattern = !state.rng.gen::<usize>();
                check_expected_value(
                    address_from_ref(mem_ref),
                    pattern,
                    read_volatile_safe(mem_ref),
                )?;

                write_volatile_safe(mem_ref, !pattern);
                Ok(())
            }
        }

        fn reset_rng(&mut self) {
            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);

    let val_to_write = |pattern: usize, idx| match idx {
        4 | 5 | 10 | 11 | 14 | 15 => !pattern,
        _ => pattern,
    };

    // Set up initial pattern in memory
    let mut 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
    let mut 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)?;

                if let Err(f) = check_expected_value(
                    address_from_ref(mem_ref),
                    expected,
                    read_volatile_safe(mem_ref),
                ) {
                    return Ok(Outcome::Fail(f));
                }
            }
        }
        pattern = pattern.rotate_left(1);
    }

    Ok(Outcome::Pass)
}

// 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)?;

            if let Err(f) = check_expected_value(
                address_from_ref(mem_ref),
                pattern,
                read_volatile_safe(mem_ref),
            ) {
                return Ok(Outcome::Fail(f));
            }
        }
    }

    Ok(Outcome::Pass)
}

fn read_volatile_safe<T: Copy>(src: &T) -> T {
    unsafe { std::ptr::read_volatile(src) }
}

fn write_volatile_safe<T: Copy>(dst: &mut T, src: T) {
    unsafe { std::ptr::write_volatile(dst, src) }
}

fn split_slice_in_half(slice: &mut [usize]) -> anyhow::Result<(&an style='color:red'>mut [usize], &mut [usize])> {
    let mut it = slice.chunks_exact_mut(slice.len() / 2);
    let (Some(first), Some(second)) = (it.next(), it.next()) else {
        bail!("Insufficient memory length for two-regions memtest");
    };
    Ok((first, second))
}

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 {
    let mut val = 0;
    unsafe { std::ptr::write_bytes(&mut val, byte, 1) }
    val
}

impl fmt::Debug for Failure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::UnexpectedValue {
                address,
                expected,
                actual,
            } => f
                .debug_struct("UnexpectedValue")
                .field("address", &format_args!("0x{address:x}"))
                .field("expected", &format_args!("0x{expected:x}"))
                .field("actual", &format_args!("0x{actual:x}"))
                .finish(),
            Self::MismatchedValues {
                address1,
                value1,
                address2,
                value2,
            } => f
                .debug_struct("MismatchedValues")
                .field("address1", &format_args!("0x{address1:x}"))
                .field("value1", &format_args!("0x{value1:x}"))
                .field("address2", &format_args!("0x{address2:x}"))
                .field("value2", &format_args!("0x{value2:x}"))
                .finish(),
        }
    }
}

impl fmt::Display for ParseTestKindError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl error::Error for ParseTestKindError {}

impl fmt::Display for Outcome {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Outcome: {:?}", self)
    }
}

impl<E: fmt::Debug> fmt::Display for Error<E> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Error: {:?}", self)
    }
}

impl<E: error::Error + 'static> error::Error for Error<E> {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Error::Observer(err) => Some(err),
            Error::Other(err) => Some(err.as_ref()),
        }
    }
}

impl<E> From<anyhow::Error> for Error<E> {
    fn from(err: anyhow::Error) -> Error<E> {
        Error::Other(err)
    }
}

fn serialize_error_other<S>(error: &anyhow::Error, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_str(&format!("{:?}", error))
}

fn deserialize_error_other<'de, D>(deserializer: D) -> Result<anyhow::Error, D::Error>
where
    D: Deserializer<'de>,
{
    let str = String::deserialize(deserializer)?;
    Ok(anyhow!(str))
}

#[cfg(test)]
mod test {
    use {
        super::{Outcome, TestKind, TestObserver},
        std::convert::Infallible,
    };

    #[derive(Debug)]
    struct IterationCounter {
        expected_iter: Option<u64>,
        completed_iter: u64,
    }

    impl TestObserver for &mut IterationCounter {
        type Error = Infallible;

        fn init(&mut self, expected_iter: u64) {
            assert!(
                self.expected_iter.is_none(),
                "init() should only be called once per test"
            );

            self.expected_iter = Some(expected_iter);
        }

        #[inline(always)]
        fn check(&mut self) -> Result<(), Self::Error> {
            self.completed_iter += 1;
            Ok(())
        }
    }

    impl IterationCounter {
        fn new() -> IterationCounter {
            IterationCounter {
                expected_iter: None,
                completed_iter: 0,
            }
        }

        fn assert_count(self) {
            let expected_iter = self.expected_iter.expect("init() should be called");
            assert_eq!(expected_iter, self.completed_iter);
        }
    }

    #[test]
    fn test_memtest_expected_iter() {
        let mut memory = vec![0; 512];
        for test_kind in TestKind::ALL {
            let mut counter = IterationCounter::new();
            assert!(
                matches!(test_kind.run(&mut memory, &mut counter), Ok(Outcome::Pass),),
                "{:?} should pass",
                test_kind
            );
            counter.assert_count();
        }
    }
}

Messung V0.5 in Prozent
C=92 H=97 G=94

¤ Dauer der Verarbeitung: 0.12 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1127926
#Domains=2039723