Skip to content

Commit

Permalink
Abstract writing to flash on Nordic (#572)
Browse files Browse the repository at this point in the history
This follows #568 towards dynamic loading and unloading of applets. The
process of writing to flash can be shared between platform update and
applet install.
  • Loading branch information
ia0 authored Aug 6, 2024
1 parent a4dcce0 commit 38d8bab
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 95 deletions.
90 changes: 8 additions & 82 deletions crates/runner-nordic/src/board/platform/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ use header::{Header, Side};
use wasefire_board_api::platform::update::Api;
use wasefire_board_api::Supported;
use wasefire_error::{Code, Error};
use wasefire_store::{Storage as _, StorageIndex};
use wasefire_sync::TakeCell;

use crate::storage::Storage;
use crate::storage::{Storage, StorageWriter};

pub enum Impl {}

Expand All @@ -38,25 +37,18 @@ impl Api for Impl {
}

fn initialize(dry_run: bool) -> Result<(), Error> {
STATE.with(|state| {
state.reset(dry_run);
Ok(())
})
STATE.with(|state| state.start(dry_run))
}

fn process(mut chunk: &[u8]) -> Result<(), Error> {
STATE.with(|state| {
while !chunk.is_empty() {
state.write(&mut chunk)?;
}
Ok(())
})
fn process(chunk: &[u8]) -> Result<(), Error> {
STATE.with(|state| state.write(chunk))
}

fn finalize() -> Result<(), Error> {
STATE.with(|state| {
let dry_run = state.dry_run()?;
state.flush()?;
match state.dry_run {
match dry_run {
true => Ok(()),
false => super::reboot(),
}
Expand All @@ -65,76 +57,10 @@ impl Api for Impl {
}

pub fn init(storage: Storage) {
STATE.put(Update::new(storage));
STATE.put(StorageWriter::new(storage));
}

static STATE: TakeCell<Update> = TakeCell::new(None);

struct Update {
storage: Storage,
dry_run: bool,
// offset + buffer.len() <= storage.len()
offset: usize,
buffer: Vec<u8>,
}

impl Update {
fn new(storage: Storage) -> Self {
Update { storage, dry_run: false, offset: 0, buffer: Vec::new() }
}

fn reset(&mut self, dry_run: bool) {
self.dry_run = dry_run;
self.offset = 0;
self.buffer.clear();
}

fn write(&mut self, chunk: &mut &[u8]) -> Result<(), Error> {
let page_size = self.storage.page_size();
let word_size = self.storage.word_size();
let page = self.offset / page_size;
let byte = self.offset % page_size;
let index = StorageIndex { page, byte };
let (value, rest) = if self.buffer.is_empty() {
let length = core::cmp::min(chunk.len(), page_size - byte);
let length = length / word_size * word_size;
chunk.split_at(length)
} else {
assert!(self.buffer.len() < word_size);
let length = core::cmp::min(chunk.len(), word_size - self.buffer.len());
self.buffer.extend_from_slice(&chunk[.. length]);
(&self.buffer[..], &chunk[length ..])
};
if value.is_empty() {
self.buffer = chunk.to_vec();
*chunk = &[];
return Ok(());
}
if !self.dry_run {
if byte == 0 {
self.storage.erase_page(page).map_err(|_| Error::world(0))?;
}
self.storage.write_slice(index, value).map_err(|_| Error::world(0))?;
}
*chunk = rest;
self.offset += value.len();
Ok(())
}

fn flush(&mut self) -> Result<(), Error> {
if self.buffer.is_empty() {
return Ok(());
}
let word_size = self.storage.word_size();
assert!(self.buffer.len() < word_size);
self.buffer.resize(word_size, 0xff);
let chunk = core::mem::take(&mut self.buffer);
let mut chunk = &chunk[..];
self.write(&mut chunk)?;
assert!(chunk.is_empty());
Ok(())
}
}
static STATE: TakeCell<StorageWriter> = TakeCell::new(None);

fn push_header(metadata: &mut Vec<u8>, header: Header) {
match header.side() {
Expand Down
123 changes: 110 additions & 13 deletions crates/runner-nordic/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@

use alloc::borrow::Cow;
use alloc::vec;
use alloc::vec::Vec;
use core::ptr::addr_of_mut;

use embedded_storage::nor_flash::{
ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash,
};
use nrf52840_hal::nvmc::Nvmc;
use nrf52840_hal::pac::NVMC;
use wasefire_error::{Code, Error};
use wasefire_store::{self as store, StorageError, StorageIndex, StorageResult};
use wasefire_sync::{AtomicBool, Ordering, TakeCell};

const WORD_SIZE: usize = <Nvmc<NVMC>>::WRITE_SIZE;
const PAGE_SIZE: usize = <Nvmc<NVMC>>::ERASE_SIZE;

static DRIVER: TakeCell<NVMC> = TakeCell::new(None);
Expand Down Expand Up @@ -90,20 +93,106 @@ impl Storage {
unsafe { &*self.ptr }
}

pub fn ptr(&self) -> *const u8 {
self.ptr as *const u8
}

pub fn len(&self) -> usize {
self.ptr.len()
}
}

pub const ERASE_SIZE: usize = PAGE_SIZE;
pub fn erase(&self, offset: usize, length: usize) -> StorageResult<()> {
let mut helper = Helper::new(self);
helper.nvmc().erase(offset as u32, (offset + length) as u32).map_err(convert)
pub struct StorageWriter {
storage: Storage,
// None if not running.
dry_run: Option<bool>,
// offset % WORD_SIZE == 0
offset: usize,
// buffer.len() < WORD_SIZE
buffer: Vec<u8>,
}

impl StorageWriter {
pub fn new(storage: Storage) -> Self {
StorageWriter { storage, dry_run: None, offset: 0, buffer: Vec::with_capacity(WORD_SIZE) }
}

pub const WRITE_SIZE: usize = <Nvmc<NVMC>>::WRITE_SIZE;
pub fn write(&self, offset: usize, data: &[u8]) -> StorageResult<()> {
let mut helper = Helper::new(self);
helper.nvmc().write(offset as u32, data).map_err(convert)
pub fn dry_run(&self) -> Result<bool, Error> {
self.dry_run.ok_or(Error::user(Code::InvalidState))
}

pub fn start(&mut self, dry_run: bool) -> Result<(), Error> {
if self.dry_run.is_some() {
self.offset = 0;
self.buffer.clear();
}
self.dry_run = Some(dry_run);
assert_eq!(self.offset, 0);
assert!(self.buffer.is_empty());
if !self.dry_run()? {
// Erase the storage.
for offset in (0 .. self.storage.len()).step_by(PAGE_SIZE) {
let content = unsafe { self.storage.get() };
// Only erase a page if needed.
if content[offset ..][.. PAGE_SIZE].iter().all(|x| *x == 0xff) {
continue;
}
let from = offset as u32;
let to = from + PAGE_SIZE as u32;
Helper::new(&self.storage).nvmc().erase(from, to).map_err(into_wasefire_error)?;
}
}
Ok(())
}

pub fn write(&mut self, mut chunk: &[u8]) -> Result<(), Error> {
if !self.buffer.is_empty() {
assert!(self.buffer.len() < WORD_SIZE);
let length = core::cmp::min(chunk.len(), WORD_SIZE - self.buffer.len());
self.buffer.extend_from_slice(&chunk[.. length]);
chunk = &chunk[length ..];
if self.buffer.len() < WORD_SIZE {
return Ok(());
}
self.write_buffer()?;
}
assert!(self.buffer.is_empty());
let length = chunk.len() / WORD_SIZE * WORD_SIZE;
self.aligned_write(&chunk[.. length])?;
self.buffer.extend_from_slice(&chunk[length ..]);
Ok(())
}

fn write_buffer(&mut self) -> Result<(), Error> {
assert_eq!(self.buffer.len(), WORD_SIZE);
let data = core::mem::take(&mut self.buffer);
self.aligned_write(&data)?;
self.buffer = data;
self.buffer.clear();
Ok(())
}

fn aligned_write(&mut self, data: &[u8]) -> Result<(), Error> {
assert_eq!(data.len() % WORD_SIZE, 0);
if !self.dry_run()? {
Helper::new(&self.storage)
.nvmc()
.write(self.offset as u32, data)
.map_err(into_wasefire_error)?;
}
self.offset += data.len();
Ok(())
}

pub fn flush(&mut self) -> Result<(), Error> {
if !self.buffer.is_empty() {
assert!(self.buffer.len() < WORD_SIZE);
self.buffer.resize(WORD_SIZE, 0xff);
self.write_buffer()?;
}
self.dry_run = None;
self.offset = 0;
Ok(())
}
}

Expand Down Expand Up @@ -133,7 +222,7 @@ impl<'a> Drop for Helper<'a> {

impl store::Storage for Storage {
fn word_size(&self) -> usize {
<Nvmc<NVMC>>::WRITE_SIZE
WORD_SIZE
}

fn page_size(&self) -> usize {
Expand All @@ -156,32 +245,40 @@ impl store::Storage for Storage {
let offset = offset(self, length, index)?;
let mut result = vec![0; length];
let mut helper = Helper::new(self);
helper.nvmc().read(offset, &mut result).map_err(convert)?;
helper.nvmc().read(offset, &mut result).map_err(into_storage_error)?;
Ok(Cow::Owned(result))
}

fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> {
let offset = offset(self, value.len(), index)?;
let mut helper = Helper::new(self);
helper.nvmc().write(offset, value).map_err(convert)
helper.nvmc().write(offset, value).map_err(into_storage_error)
}

fn erase_page(&mut self, page: usize) -> StorageResult<()> {
let from = offset(self, PAGE_SIZE, StorageIndex { page, byte: 0 })?;
let to = from + PAGE_SIZE as u32;
let mut helper = Helper::new(self);
helper.nvmc().erase(from, to).map_err(convert)
helper.nvmc().erase(from, to).map_err(into_storage_error)
}
}

fn offset(storage: &Storage, length: usize, index: StorageIndex) -> StorageResult<u32> {
Ok(index.range(length, storage)?.start as u32)
}

fn convert(e: <Nvmc<NVMC> as ErrorType>::Error) -> StorageError {
fn into_storage_error(e: <Nvmc<NVMC> as ErrorType>::Error) -> StorageError {
match e.kind() {
NorFlashErrorKind::NotAligned => StorageError::NotAligned,
NorFlashErrorKind::OutOfBounds => StorageError::OutOfBounds,
_ => StorageError::CustomError,
}
}

fn into_wasefire_error(e: <Nvmc<NVMC> as ErrorType>::Error) -> Error {
match e.kind() {
NorFlashErrorKind::NotAligned => Error::user(Code::InvalidAlign),
NorFlashErrorKind::OutOfBounds => Error::user(Code::OutOfBounds),
_ => Error::world(0),
}
}

0 comments on commit 38d8bab

Please sign in to comment.