Module tickv::async_ops

source ·
Expand description

TicKV can be used asynchronously. This module provides documentation and tests for using it with an async FlashController interface.

To do this first there are special error values to return from the FlashController functions. These are the ReadNotReady, WriteNotReady and EraseNotReady types.

// EXAMPLE ONLY: The `DefaultHasher` is subject to change
// and hence is not a good fit.
use std::collections::hash_map::DefaultHasher;
use core::hash::{Hash, Hasher};
use std::cell::{Cell, RefCell};
use tickv::{AsyncTicKV, MAIN_KEY};
use tickv::error_codes::ErrorCode;
use tickv::flash_controller::FlashController;

fn get_hashed_key(unhashed_key: &[u8]) -> u64 {
    let mut hash_function = DefaultHasher::new();
    unhashed_key.hash(&mut hash_function);
    hash_function.finish()
}

struct FlashCtrl {
    buf: RefCell<[[u8; 1024]; 64]>,
    async_read_region: Cell<usize>,
    async_erase_region: Cell<usize>,
}

impl FlashCtrl {
    fn new() -> Self {
        Self {
            buf: RefCell::new([[0xFF; 1024]; 64]),
            async_read_region: Cell::new(10),
            async_erase_region: Cell::new(10),
        }
    }
}

impl FlashController<1024> for FlashCtrl {
    fn read_region(
        &self,
        region_number: usize,
        buf: &mut [u8; 1024],
    ) -> Result<(), ErrorCode> {
         // We aren't ready yet, launch the async operation
         self.async_read_region.set(region_number);
         return Err(ErrorCode::ReadNotReady(region_number));

        Ok(())
    }

    fn write(&self, address: usize, buf: &[u8]) -> Result<(), ErrorCode> {
        // Save the write operation to a queue, we don't need to re-call
        for (i, d) in buf.iter().enumerate() {
            self.buf.borrow_mut()[address / 1024][(address % 1024) + i] = *d;
        }
        Ok(())
    }

    fn erase_region(&self, region_number: usize) -> Result<(), ErrorCode> {
        if self.async_erase_region.get() != region_number {
            // We aren't ready yet, launch the async operation
            self.async_erase_region.set(region_number);
            return Err(ErrorCode::EraseNotReady(region_number));
        }

        Ok(())
    }
}

// Create the TicKV instance and loop until everything is done
// NOTE in an real implementation you will want to wait on
// callbacks/interrupts and make this async.

let mut read_buf: [u8; 1024] = [0; 1024];
let mut hash_function = DefaultHasher::new();
MAIN_KEY.hash(&mut hash_function);
let tickv = AsyncTicKV::<FlashCtrl, 1024>::new(FlashCtrl::new(),
                  &mut read_buf, 0x1000);

let mut ret = tickv.initialise(hash_function.finish());
while ret.is_err() {
    // There is no actual delay here, in a real implementation wait on some event
    ret = tickv.continue_operation().0;

    match ret {
        Err(ErrorCode::ReadNotReady(reg)) => {
            tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]);
        }
        Ok(_) => break,
        Err(ErrorCode::WriteNotReady(reg)) => break,
        Err(ErrorCode::EraseNotReady(reg)) => {}
        _ => unreachable!(),
    }
}

// Then when calling the TicKV function check for the error. For example
// when appending a key:

// Add a key
static mut VALUE: [u8; 32] = [0x23; 32];
let ret = unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut VALUE, 32) };

match ret {
    Err((_buf, ErrorCode::ReadNotReady(reg))) => {
        // There is no actual delay in the test, just continue now
        tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]);
        tickv
            .continue_operation().0
            .unwrap();
    }
    Ok(_) => {}
    _ => unreachable!(),
}

This will call into the FlashController again where the FlashController implementation must return the data that is requested. If the data isn’t ready (multiple reads might occur) then the NotReady error types can still be used.

Structs§

  • The struct storing all of the TicKV information for the async implementation.