ryankurte / ghostfat

Virtual FAT16 implementation for embedded USB Mass Storage Device emulation
8 stars 1 forks source link

Add example / improve docs for use (particularly with RTIC) #5

Open ryankurte opened 2 years ago

ryankurte commented 2 years ago

extract / add example for use with nrf52, perhaps other platforms?

flash Control wrapper provides DynamicFile over NVMC:


use core::cell::UnsafeCell;

use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
use nrf52840_hal::nvmc::Nvmc;
use nrf52840_pac::NVMC;

const FILE_START: usize = 0x00080000;
const FILE_LEN: usize = 256 * 1024;

pub struct FlashControl {
    nvm: UnsafeCell<Nvmc<NVMC>>,
    len: usize,
}

unsafe impl Sync for FlashControl {}

impl FlashControl {
    /// Create a new flash controller from device [`NVMC`]
    pub fn new(nvmc: NVMC) -> Self {

        // Chunk of memory available to flash control
        // TODO: work out how to extract segments from linker file..?
        let section = unsafe {
            core::slice::from_raw_parts_mut(FILE_START as *const u8 as *mut u8, FILE_LEN)
        };

        Self{
            nvm: UnsafeCell::new(Nvmc::new(nvmc, section)),
            len: FILE_LEN,
        }
    }
}

/// GhostFAT dynamic file implementation for our Flash controller
impl <const BLOCK_SIZE: usize> ghostfat::DynamicFile<BLOCK_SIZE> for FlashControl {
    fn len(&self) -> usize {
        self.len
    }

    fn read_chunk(&self, index: usize, buff: &mut [u8]) -> usize {
        defmt::info!("Read file chunk: 0x{:02x} index: 0x{:08x} len: {}", index, index * BLOCK_SIZE, buff.len());

        let res = unsafe { (*self.nvm.get()).read((index * BLOCK_SIZE) as u32, buff) };

        // Read data
        if let Err(e) = res {
            defmt::error!("Failed to read index: 0x{:08x} len: {}: {:?}", index, Nvmc::<NVMC>::ERASE_SIZE, defmt::Debug2Format(&e));
            return 0;
        }

        return buff.len()
    }

    fn write_chunk(&mut self, index: usize, data: &[u8]) -> usize {
        defmt::info!("Write file index: 0x{:08x}", index);

        // Erase on writes to the first address in the block
        // TODO: this assumes chunk writes are always aligned / ordered...
        // i _think_ but am not _sure_ this is correct

        if index % Nvmc::<NVMC>::ERASE_SIZE == 0 {
            if let Err(e) = self.nvm.get_mut().erase((index * BLOCK_SIZE) as u32, Nvmc::<NVMC>::ERASE_SIZE as u32) {
                defmt::error!("Failed to erase index: 0x{:08x} len: {}: {:?}", index, Nvmc::<NVMC>::ERASE_SIZE, defmt::Debug2Format(&e));
                return 0;
            }
        }

        // Write data
        if let Err(e) = self.nvm.get_mut().write((index * BLOCK_SIZE) as u32, data) {
            defmt::error!("Failed to write index: 0x{:08x} len: {}: {:?}", index, Nvmc::<NVMC>::ERASE_SIZE, defmt::Debug2Format(&e));
            return 0;
        }

        return data.len();        
    }
}

which can then be used with rtic, usb_device, usb_scsi:

#[init(local = [
    ...
    CLOCKS: Option<Clocks<ExternalOscillator, Internal, LfOscStopped>> = None,
    USB_ALLOCATOR: Option<UsbBusAllocator<Usbd<UsbPeripheral<'static>>>> = None,
    FILES: Option<[File<'static>; 2]> = None,
    FLASH: Option<FlashControl> = None,
])]
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
    // Setup USB allocator
    *cx.local.USB_ALLOCATOR = Some(Usbd::new(UsbPeripheral::new(periph.USBD, &clocks)));
    let usb_bus = cx.local.USB_ALLOCATOR.as_ref().unwrap();

    ...
    // Setup files
    *cx.local.FLASH = Some(FlashControl::new(periph.NVMC));

    *cx.local.FILES = Some([
        File::new_ro("a.txt", b"12345\r\n"),
        File::new_dyn("b.bin", cx.local.FLASH.as_mut().unwrap()),
    ]);

    ...
    // Setup block device
    let block_dev = GhostFat::new(cx.local.FILES.as_mut().unwrap(), Default::default());
    let usb_store = Scsi::new(&usb_bus, 64, block_dev, "V", "P", "0.1");

    // TODO: start periodic (~1kHz) poll on usb_dev via task
    ...
}