imix/test/
log_test.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Tests the log storage interface in circular mode. For testing in linear mode, see
6//! linear_log_test.rs.
7//!
8//! This testing framework creates a circular log storage interface in flash and runs a series of
9//! operations upon it. The tests check to make sure that the correct values are read and written
10//! after each operation, that errors are properly detected and handled, and that the log generally
11//! behaves as expected. The tests perform both valid and invalid operations to fully test the log's
12//! behavior.
13//!
14//! Pressing the `USER` button on the imix at any time during the test will erase the log and reset
15//! the test state. Pressing the `RESET` button will reboot the imix without erasing the log,
16//! allowing for testing logs across reboots.
17//!
18//! In order to fully test the log, the tester should try a variety of erases and reboots to ensure
19//! that the log works correctly across these operations. The tester can also modify the testing
20//! operations and parameters defined below to test logs in different configurations. Different
21//! configurations should be tested in order to exercise the log under a greater number of
22//! scenarios (e.g. saturating/not saturating log pages with data, always/not always ending
23//! operations at page boundaries, etc.).
24//!
25//! To run the test, add the following line to the imix boot sequence:
26//! ```
27//!     test::log_test::run(mux_alarm, &peripherals.flash_controller);
28//! ```
29//! and use the `USER` and `RESET` buttons to manually erase the log and reboot the imix,
30//! respectively.
31
32use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm};
33use capsules_extra::log;
34use core::cell::Cell;
35use core::ptr::addr_of_mut;
36use kernel::debug;
37use kernel::hil::flash;
38use kernel::hil::gpio::{self, Interrupt};
39use kernel::hil::log::{LogRead, LogReadClient, LogWrite, LogWriteClient};
40use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks};
41use kernel::static_init;
42use kernel::storage_volume;
43use kernel::utilities::cells::{NumericCellExt, TakeCell};
44use kernel::ErrorCode;
45use sam4l::ast::Ast;
46use sam4l::flashcalw;
47
48// Allocate 2kiB volume for log storage.
49storage_volume!(TEST_LOG, 2);
50
51pub unsafe fn run(
52    mux_alarm: &'static MuxAlarm<'static, Ast>,
53    flash_controller: &'static sam4l::flashcalw::FLASHCALW,
54) {
55    // Set up flash controller.
56    flash_controller.configure();
57    let pagebuffer = static_init!(flashcalw::Sam4lPage, flashcalw::Sam4lPage::default());
58
59    // Create actual log storage abstraction on top of flash.
60    let log = static_init!(
61        Log,
62        log::Log::new(&TEST_LOG, flash_controller, pagebuffer, true)
63    );
64    kernel::deferred_call::DeferredCallClient::register(log);
65    flash::HasClient::set_client(flash_controller, log);
66
67    let alarm = static_init!(
68        VirtualMuxAlarm<'static, Ast>,
69        VirtualMuxAlarm::new(mux_alarm)
70    );
71    alarm.setup();
72
73    // Create and run test for log storage.
74    let test = static_init!(
75        LogTest<VirtualMuxAlarm<'static, Ast>>,
76        LogTest::new(log, &mut *addr_of_mut!(BUFFER), alarm, &TEST_OPS)
77    );
78    log.set_read_client(test);
79    log.set_append_client(test);
80    test.alarm.set_alarm_client(test);
81
82    // Create user button.
83    let button_pin = sam4l::gpio::GPIOPin::new(sam4l::gpio::Pin::PC24);
84    button_pin.enable_interrupts(gpio::InterruptEdge::RisingEdge);
85    button_pin.set_client(test);
86
87    test.run();
88}
89
90static TEST_OPS: [TestOp; 24] = [
91    // Read back any existing entries.
92    TestOp::BadRead,
93    TestOp::Read,
94    // Write multiple pages, but don't fill log.
95    TestOp::BadWrite,
96    TestOp::Write,
97    TestOp::Read,
98    TestOp::BadWrite,
99    TestOp::Write,
100    TestOp::Read,
101    // Seek to beginning and re-verify entire log.
102    TestOp::SeekBeginning,
103    TestOp::Read,
104    // Write multiple pages, over-filling log and overwriting oldest entries.
105    TestOp::SeekBeginning,
106    TestOp::Write,
107    // Read offset should be incremented since it was invalidated by previous write.
108    TestOp::BadRead,
109    TestOp::Read,
110    // Write multiple pages and sync. Read offset should be invalidated due to sync clobbering
111    // previous read offset.
112    TestOp::Write,
113    TestOp::Sync,
114    TestOp::Read,
115    // Try bad seeks, should fail and not change read entry ID.
116    TestOp::Write,
117    TestOp::BadSeek(0),
118    TestOp::BadSeek(usize::MAX),
119    TestOp::Read,
120    // Try bad write, nothing should change.
121    TestOp::BadWrite,
122    TestOp::Read,
123    // Sync log before finishing test so that all changes persist for next test iteration.
124    TestOp::Sync,
125];
126
127// Buffer for reading from and writing to in the log tests.
128static mut BUFFER: [u8; 8] = [0; 8];
129// Length of buffer to actually use.
130const BUFFER_LEN: usize = 8;
131// Amount to shift value before adding to magic in order to fit in buffer.
132const VALUE_SHIFT: usize = 8 * (8 - BUFFER_LEN);
133// Dummy buffer for testing bad writes.
134static mut DUMMY_BUFFER: [u8; 520] = [0; 520];
135// Time to wait in between log operations.
136const WAIT_MS: u32 = 2;
137// Magic number to write to log storage (+ offset).
138const MAGIC: u64 = 0x0102030405060708;
139// Number of entries to write per write operation.
140const ENTRIES_PER_WRITE: u64 = 120;
141
142// Test's current state.
143#[derive(Clone, Copy, PartialEq)]
144enum TestState {
145    Operate, // Running through test operations.
146    Erase,   // Erasing log and restarting test.
147    CleanUp, // Cleaning up test after all operations complete.
148}
149
150// A single operation within the test.
151#[derive(Clone, Copy, PartialEq)]
152enum TestOp {
153    Read,
154    BadRead,
155    Write,
156    BadWrite,
157    Sync,
158    SeekBeginning,
159    BadSeek(usize),
160}
161
162type Log = log::Log<'static, flashcalw::FLASHCALW>;
163struct LogTest<A: 'static + Alarm<'static>> {
164    log: &'static Log,
165    buffer: TakeCell<'static, [u8]>,
166    alarm: &'static A,
167    state: Cell<TestState>,
168    ops: &'static [TestOp],
169    op_index: Cell<usize>,
170    op_start: Cell<bool>,
171    read_val: Cell<u64>,
172    write_val: Cell<u64>,
173}
174
175impl<A: 'static + Alarm<'static>> LogTest<A> {
176    fn new(
177        log: &'static Log,
178        buffer: &'static mut [u8],
179        alarm: &'static A,
180        ops: &'static [TestOp],
181    ) -> LogTest<A> {
182        // Recover test state.
183        let read_val = entry_id_to_test_value(log.next_read_entry_id());
184        let write_val = entry_id_to_test_value(log.log_end());
185
186        debug!(
187            "Log recovered from flash (Start and end entry IDs: {:?} to {:?}; read and write values: {} and {})",
188            log.next_read_entry_id(),
189            log.log_end(),
190            read_val,
191            write_val
192        );
193
194        LogTest {
195            log,
196            buffer: TakeCell::new(buffer),
197            alarm,
198            state: Cell::new(TestState::Operate),
199            ops,
200            op_index: Cell::new(0),
201            op_start: Cell::new(true),
202            read_val: Cell::new(read_val),
203            write_val: Cell::new(write_val),
204        }
205    }
206
207    fn run(&self) {
208        match self.state.get() {
209            TestState::Operate => {
210                let op_index = self.op_index.get();
211                if op_index == self.ops.len() {
212                    self.state.set(TestState::CleanUp);
213                    let _ = self.log.seek(self.log.log_start());
214                    return;
215                }
216
217                match self.ops[op_index] {
218                    TestOp::Read => self.read(),
219                    TestOp::BadRead => self.bad_read(),
220                    TestOp::Write => self.write(),
221                    TestOp::BadWrite => self.bad_write(),
222                    TestOp::Sync => self.sync(),
223                    TestOp::SeekBeginning => self.seek_beginning(),
224                    TestOp::BadSeek(entry_id) => self.bad_seek(entry_id),
225                }
226            }
227            TestState::Erase => self.erase(),
228            TestState::CleanUp => {
229                debug!(
230                    "Log Storage test succeeded! (Final log start and end entry IDs: {:?} to {:?})",
231                    self.log.next_read_entry_id(),
232                    self.log.log_end()
233                );
234            }
235        }
236    }
237
238    fn next_op(&self) {
239        self.op_index.increment();
240        self.op_start.set(true);
241    }
242
243    fn erase(&self) {
244        match self.log.erase() {
245            Ok(()) => (),
246            Err(ErrorCode::BUSY) => {
247                self.wait();
248            }
249            _ => panic!("Could not erase log storage!"),
250        }
251    }
252
253    fn read(&self) {
254        // Update read value if clobbered by previous operation.
255        if self.op_start.get() {
256            let next_read_val = entry_id_to_test_value(self.log.next_read_entry_id());
257            if self.read_val.get() < next_read_val {
258                debug!(
259                    "Increasing read value from {} to {} due to clobbering (read entry ID is {:?})!",
260                    self.read_val.get(),
261                    next_read_val,
262                    self.log.next_read_entry_id()
263                );
264                self.read_val.set(next_read_val);
265            }
266        }
267
268        self.buffer.take().map_or_else(
269            || panic!("NO BUFFER"),
270            move |buffer| {
271                // Clear buffer first to make debugging more sane.
272                buffer.clone_from_slice(&0u64.to_be_bytes());
273
274                if let Err((error, original_buffer)) = self.log.read(buffer, BUFFER_LEN) {
275                    self.buffer.replace(original_buffer);
276                    match error {
277                        ErrorCode::FAIL => {
278                            // No more entries, start writing again.
279                            debug!(
280                                "READ DONE: READ OFFSET: {:?} / WRITE OFFSET: {:?}",
281                                self.log.next_read_entry_id(),
282                                self.log.log_end()
283                            );
284                            self.next_op();
285                            self.run();
286                        }
287                        ErrorCode::BUSY => {
288                            debug!("Flash busy, waiting before reattempting read");
289                            self.wait();
290                        }
291                        _ => panic!("READ #{} FAILED: {:?}", self.read_val.get(), error),
292                    }
293                }
294            },
295        );
296    }
297
298    fn bad_read(&self) {
299        // Ensure failure if buffer is smaller than provided max read length.
300        self.buffer
301            .take()
302            .map(
303                move |buffer| match self.log.read(buffer, buffer.len() + 1) {
304                    Ok(()) => panic!("Read with too-large max read length succeeded unexpectedly!"),
305                    Err((error, original_buffer)) => {
306                        self.buffer.replace(original_buffer);
307                        assert_eq!(error, ErrorCode::INVAL);
308                    }
309                },
310            )
311            .unwrap();
312
313        // Ensure failure if buffer is too small to hold entry.
314        self.buffer
315            .take()
316            .map(move |buffer| match self.log.read(buffer, BUFFER_LEN - 1) {
317                Ok(()) => panic!("Read with too-small buffer succeeded unexpectedly!"),
318                Err((error, original_buffer)) => {
319                    self.buffer.replace(original_buffer);
320                    if self.read_val.get() == self.write_val.get() {
321                        assert_eq!(error, ErrorCode::FAIL);
322                    } else {
323                        assert_eq!(error, ErrorCode::FAIL);
324                    }
325                }
326            })
327            .unwrap();
328
329        self.next_op();
330        self.run();
331    }
332
333    fn write(&self) {
334        self.buffer
335            .take()
336            .map(move |buffer| {
337                buffer.clone_from_slice(
338                    &(MAGIC + (self.write_val.get() << VALUE_SHIFT)).to_be_bytes(),
339                );
340                if let Err((error, original_buffer)) = self.log.append(buffer, BUFFER_LEN) {
341                    self.buffer.replace(original_buffer);
342
343                    match error {
344                        ErrorCode::BUSY => self.wait(),
345                        _ => panic!("WRITE FAILED: {:?}", error),
346                    }
347                }
348            })
349            .unwrap();
350    }
351
352    fn bad_write(&self) {
353        let original_offset = self.log.log_end();
354
355        // Ensure failure if entry length is 0.
356        self.buffer
357            .take()
358            .map(move |buffer| match self.log.append(buffer, 0) {
359                Ok(()) => panic!("Appending entry of size 0 succeeded unexpectedly!"),
360                Err((error, original_buffer)) => {
361                    self.buffer.replace(original_buffer);
362                    assert_eq!(error, ErrorCode::INVAL);
363                }
364            })
365            .unwrap();
366
367        // Ensure failure if proposed entry length is greater than buffer length.
368        self.buffer
369            .take()
370            .map(
371                move |buffer| match self.log.append(buffer, buffer.len() + 1) {
372                    Ok(()) => panic!("Appending with too-small buffer succeeded unexpectedly!"),
373                    Err((error, original_buffer)) => {
374                        self.buffer.replace(original_buffer);
375                        assert_eq!(error, ErrorCode::INVAL);
376                    }
377                },
378            )
379            .unwrap();
380
381        // Ensure failure if entry is too large to fit within a single flash page.
382        unsafe {
383            let dummy_buffer = &mut *addr_of_mut!(DUMMY_BUFFER);
384            let len = dummy_buffer.len();
385            match self.log.append(dummy_buffer, len) {
386                Ok(()) => panic!("Appending with too-small buffer succeeded unexpectedly!"),
387                Err((ecode, _original_buffer)) => assert_eq!(ecode, ErrorCode::SIZE),
388            }
389        }
390
391        // Make sure that append offset was not changed by failed writes.
392        assert_eq!(original_offset, self.log.log_end());
393        self.next_op();
394        self.run();
395    }
396
397    fn sync(&self) {
398        match self.log.sync() {
399            Ok(()) => (),
400            error => panic!("Sync failed: {:?}", error),
401        }
402    }
403
404    fn seek_beginning(&self) {
405        let entry_id = self.log.log_start();
406        match self.log.seek(entry_id) {
407            Ok(()) => debug!("Seeking to {:?}...", entry_id),
408            error => panic!("Seek failed: {:?}", error),
409        }
410    }
411
412    fn bad_seek(&self, entry_id: usize) {
413        // Make sure seek fails with INVAL.
414        let original_offset = self.log.next_read_entry_id();
415        match self.log.seek(entry_id) {
416            Err(ErrorCode::INVAL) => (),
417            Ok(()) => panic!(
418                "Seek to invalid entry ID {:?} succeeded unexpectedly!",
419                entry_id
420            ),
421            error => panic!(
422                "Seek to invalid entry ID {:?} failed with unexpected error {:?}!",
423                entry_id, error
424            ),
425        }
426
427        // Make sure that read offset was not changed by failed seek.
428        assert_eq!(original_offset, self.log.next_read_entry_id());
429        self.next_op();
430        self.run();
431    }
432
433    fn wait(&self) {
434        let delay = self.alarm.ticks_from_ms(WAIT_MS);
435        let now = self.alarm.now();
436        self.alarm.set_alarm(now, delay);
437    }
438}
439
440impl<A: Alarm<'static>> LogReadClient for LogTest<A> {
441    fn read_done(&self, buffer: &'static mut [u8], length: usize, error: Result<(), ErrorCode>) {
442        match error {
443            Ok(()) => {
444                // Verify correct number of bytes were read.
445                if length != BUFFER_LEN {
446                    panic!(
447                        "{} bytes read, expected {} on read number {} (offset {:?}). Value read was {:?}",
448                        length,
449                        BUFFER_LEN,
450                        self.read_val.get(),
451                        self.log.next_read_entry_id(),
452                        &buffer[0..length],
453                    );
454                }
455
456                // Verify correct value was read.
457                let expected = (MAGIC + (self.read_val.get() << VALUE_SHIFT)).to_be_bytes();
458                for i in 0..BUFFER_LEN {
459                    if buffer[i] != expected[i] {
460                        panic!(
461                            "Expected {:?}, read {:?} on read number {} (offset {:?})",
462                            &expected[0..BUFFER_LEN],
463                            &buffer[0..BUFFER_LEN],
464                            self.read_val.get(),
465                            self.log.next_read_entry_id(),
466                        );
467                    }
468                }
469
470                self.buffer.replace(buffer);
471                self.read_val.set(self.read_val.get() + 1);
472                self.op_start.set(false);
473                self.wait();
474            }
475            _ => {
476                panic!("Read failed unexpectedly!");
477            }
478        }
479    }
480
481    fn seek_done(&self, error: Result<(), ErrorCode>) {
482        if error == Ok(()) {
483            debug!("Seeked");
484            self.read_val
485                .set(entry_id_to_test_value(self.log.next_read_entry_id()));
486        } else {
487            panic!("Seek failed: {:?}", error);
488        }
489
490        if self.state.get() == TestState::Operate {
491            self.next_op();
492        }
493        self.run();
494    }
495}
496
497impl<A: Alarm<'static>> LogWriteClient for LogTest<A> {
498    fn append_done(
499        &self,
500        buffer: &'static mut [u8],
501        length: usize,
502        records_lost: bool,
503        error: Result<(), ErrorCode>,
504    ) {
505        self.buffer.replace(buffer);
506        self.op_start.set(false);
507
508        match error {
509            Ok(()) => {
510                if length != BUFFER_LEN {
511                    panic!(
512                        "Appended {} bytes, expected {} (write #{}, offset {:?})!",
513                        length,
514                        BUFFER_LEN,
515                        self.write_val.get(),
516                        self.log.log_end()
517                    );
518                }
519                let expected_records_lost =
520                    self.write_val.get() > entry_id_to_test_value(TEST_LOG.len());
521                if records_lost && records_lost != expected_records_lost {
522                    panic!("Append callback states records_lost = {}, expected {} (write #{}, offset {:?})!",
523                           records_lost,
524                           expected_records_lost,
525                           self.write_val.get(),
526                           self.log.log_end()
527                    );
528                }
529
530                // Stop writing after `ENTRIES_PER_WRITE` entries have been written.
531                if (self.write_val.get() + 1) % ENTRIES_PER_WRITE == 0 {
532                    debug!(
533                        "WRITE DONE: READ OFFSET: {:?} / WRITE OFFSET: {:?}",
534                        self.log.next_read_entry_id(),
535                        self.log.log_end()
536                    );
537                    self.next_op();
538                }
539
540                self.write_val.set(self.write_val.get() + 1);
541            }
542            Err(ErrorCode::FAIL) => {
543                assert_eq!(length, 0);
544                assert!(!records_lost);
545                debug!("Append failed due to flash error, retrying...");
546            }
547            error => panic!("UNEXPECTED APPEND FAILURE: {:?}", error),
548        }
549
550        self.wait();
551    }
552
553    fn sync_done(&self, error: Result<(), ErrorCode>) {
554        if error == Ok(()) {
555            debug!(
556                "SYNC DONE: READ OFFSET: {:?} / WRITE OFFSET: {:?}",
557                self.log.next_read_entry_id(),
558                self.log.log_end()
559            );
560        } else {
561            panic!("Sync failed: {:?}", error);
562        }
563
564        self.next_op();
565        self.run();
566    }
567
568    fn erase_done(&self, error: Result<(), ErrorCode>) {
569        match error {
570            Ok(()) => {
571                // Reset test state.
572                self.op_index.set(0);
573                self.op_start.set(true);
574                self.read_val.set(0);
575                self.write_val.set(0);
576
577                // Make sure that flash has been erased.
578                for i in 0..TEST_LOG.len() {
579                    if TEST_LOG[i] != 0xFF {
580                        panic!(
581                            "Log not properly erased, read {} at byte {}. SUMMARY: {:?}",
582                            TEST_LOG[i],
583                            i,
584                            &TEST_LOG[i..i + 8]
585                        );
586                    }
587                }
588
589                // Make sure that a read on an empty log fails normally.
590                self.buffer.take().map(move |buffer| {
591                    if let Err((error, original_buffer)) = self.log.read(buffer, BUFFER_LEN) {
592                        self.buffer.replace(original_buffer);
593                        match error {
594                            ErrorCode::FAIL => (),
595                            ErrorCode::BUSY => {
596                                self.wait();
597                            }
598                            _ => panic!("Read on empty log did not fail as expected: {:?}", error),
599                        }
600                    } else {
601                        panic!("Read on empty log succeeded! (it shouldn't)");
602                    }
603                });
604
605                // Move to next operation.
606                debug!("Log Storage erased");
607                self.state.set(TestState::Operate);
608                self.run();
609            }
610            Err(ErrorCode::BUSY) => {
611                // Flash busy, try again.
612                self.wait();
613            }
614            _ => {
615                panic!("Erase failed: {:?}", error);
616            }
617        }
618    }
619}
620
621impl<A: Alarm<'static>> AlarmClient for LogTest<A> {
622    fn alarm(&self) {
623        self.run();
624    }
625}
626
627impl<A: Alarm<'static>> gpio::Client for LogTest<A> {
628    fn fired(&self) {
629        // Erase log.
630        self.state.set(TestState::Erase);
631        self.erase();
632    }
633}
634
635fn entry_id_to_test_value(entry_id: usize) -> u64 {
636    // Page and entry header sizes for log storage.
637    const PAGE_SIZE: usize = 512;
638
639    let pages_written = entry_id / PAGE_SIZE;
640    let entry_size = log::ENTRY_HEADER_SIZE + BUFFER_LEN;
641    let entries_per_page = (PAGE_SIZE - log::PAGE_HEADER_SIZE) / entry_size;
642    let entries_last_page = if entry_id % PAGE_SIZE >= log::PAGE_HEADER_SIZE {
643        (entry_id % PAGE_SIZE - log::PAGE_HEADER_SIZE) / entry_size
644    } else {
645        0
646    };
647    (pages_written * entries_per_page + entries_last_page) as u64
648}