Skip to content

Commit

Permalink
Move away from ROM APIs for flash writing
Browse files Browse the repository at this point in the history
We have a driver to access the flash controller directly, use it. Remove
the old APIs so nobody is tempted to use them.
  • Loading branch information
labbott committed Aug 30, 2023
1 parent 580b0fc commit 375a1f7
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 443 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions drv/lpc55-flash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pub const BYTES_PER_FLASH_WORD: usize = 16;
/// Number of bytes per flash page.
pub const BYTES_PER_FLASH_PAGE: usize = 512;

/// Flash words per flash page
pub const WORDS_PER_FLASH_PAGE: usize =
BYTES_PER_FLASH_PAGE / BYTES_PER_FLASH_WORD;

/// Flash driver handle. Wraps a pointer to the flash register set and provides
/// encapsulated operations.
///
Expand Down Expand Up @@ -336,6 +340,67 @@ impl<'a> Flash<'a> {
w
});
}

pub fn write_page(
&mut self,
addr: u32,
flash_page: &[u8; BYTES_PER_FLASH_PAGE],
wait: fn() -> (),
) -> Result<(), FlashTimeout> {
let start_word = addr / BYTES_PER_FLASH_WORD as u32;
self.start_erase_range(
start_word..=start_word + ((WORDS_PER_FLASH_PAGE as u32) - 1),
);
self.wait_for_erase_or_program(wait)?;

for (i, row) in
flash_page.chunks_exact(BYTES_PER_FLASH_WORD).enumerate()
{
let row: &[u8; BYTES_PER_FLASH_WORD] = row.try_into().unwrap();

self.start_write_row(i as u32, row);
while !self.poll_write_result() {}
}

self.start_program(start_word);
self.wait_for_erase_or_program(wait)?;
Ok(())
}

fn wait_for_erase_or_program(
&mut self,
wait: fn() -> (),
) -> Result<(), FlashTimeout> {
loop {
if let Some(result) = self.poll_erase_or_program_result() {
return result;
}

self.enable_interrupt_sources();
wait();
self.disable_interrupt_sources();
}
}

pub fn is_page_range_programmed(&mut self, addr: u32, len: u32) -> bool {
for i in (addr..addr + len).step_by(BYTES_PER_FLASH_PAGE) {
let word = i / (BYTES_PER_FLASH_WORD as u32);
// the blank check is inclusive so we need to end before
// the last word
let end_word = (word + WORDS_PER_FLASH_PAGE as u32) - 1;
self.start_blank_check(word..=end_word);
loop {
if let Some(s) = self.poll_blank_check_result() {
match s {
ProgramState::Blank => return false,
_ => break,
}
}
}
}

true
}
}

/// Raw status bits from the flash controller.
Expand Down
96 changes: 16 additions & 80 deletions drv/lpc55-update-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

use core::convert::Infallible;
use core::mem::MaybeUninit;
use drv_lpc55_flash::{BYTES_PER_FLASH_PAGE, BYTES_PER_FLASH_WORD};
use drv_lpc55_flash::BYTES_PER_FLASH_PAGE;
use drv_lpc55_update_api::{
RawCabooseError, RotBootInfo, SlotId, SwitchDuration, UpdateTarget,
};
Expand Down Expand Up @@ -403,11 +403,13 @@ impl idl::InOrderUpdateImpl for ServerImpl<'_> {
// Note that the page write machinery uses page numbers. This
// should probably change. But, for now, we must divide our word
// number by 32.
do_raw_page_write(
&mut self.flash,
CFPA_SCRATCH_FLASH_WORD / 32,
&cfpa_bytes,
)?;
self.flash
.write_page(
CFPA_SCRATCH_FLASH_WORD,
&cfpa_bytes,
wait_for_flash_interrupt,
)
.map_err(|_| UpdateError::FlashError)?;
}
}

Expand Down Expand Up @@ -713,82 +715,16 @@ fn do_block_write(
None => return Err(UpdateError::OutOfBounds),
};

// write_addr is a byte address; convert it back to a page number, but this
// time, an absolute page number in the flash device.
let page_num = write_addr / BYTES_PER_FLASH_PAGE as u32;

do_raw_page_write(flash, page_num, flash_page)
}

/// Performs an erase-write sequence to a single page within the raw flash
/// device. This function is capable of writing outside of any image slot, which
/// is important for doing CFPA updates. If you're writing to an image slot, use
/// `do_block_write`.
fn do_raw_page_write(
flash: &mut drv_lpc55_flash::Flash<'_>,
page_num: u32,
flash_page: &[u8; BYTES_PER_FLASH_PAGE],
) -> Result<(), UpdateError> {
// We regularly need the number of flash words per flash page below, and
// specifically as a u32, so:
static_assertions::const_assert_eq!(
BYTES_PER_FLASH_PAGE % BYTES_PER_FLASH_WORD,
0
);
const WORDS_PER_PAGE: u32 =
(BYTES_PER_FLASH_PAGE / BYTES_PER_FLASH_WORD) as u32;

// The hardware operates in terms of word numbers, never page numbers.
// Convert the page number to the number of the first word in that page.
// (This is equivalent to multiplying by 32 but named constants are nice.)
let word_num = page_num * WORDS_PER_PAGE;

// Step one: erase the page. Note that this range is INCLUSIVE. The hardware
// will happily erase multiple pages if you let it. We don't want that here.
flash.start_erase_range(word_num..=word_num + (WORDS_PER_PAGE - 1));
wait_for_erase_or_program(flash)?;

// Step two: Transfer each 16-byte flash word (page row) into the write
// registers in the flash controller.
for (i, row) in flash_page.chunks_exact(BYTES_PER_FLASH_WORD).enumerate() {
// TODO: this will be unnecessary if array_chunks stabilizes
let row: &[u8; BYTES_PER_FLASH_WORD] = row.try_into().unwrap_lite();

flash.start_write_row(i as u32, row);
while !flash.poll_write_result() {
// spin - supposed to be very quick in hardware.
}
}

// Step three: program the whole page into non-volatile storage by naming
// the first word in the target page. (Any word in the page will do,
// actually, but we've conveniently got the first word available.)
flash.start_program(word_num);
wait_for_erase_or_program(flash)?;

Ok(())
flash
.write_page(write_addr, flash_page, wait_for_flash_interrupt)
.map_err(|_| UpdateError::FlashError)
}

/// Utility function that does an interrupt-driven poll and sleep while the
/// flash controller finishes a write or erase.
fn wait_for_erase_or_program(
flash: &mut drv_lpc55_flash::Flash<'_>,
) -> Result<(), UpdateError> {
loop {
if let Some(result) = flash.poll_erase_or_program_result() {
return result.map_err(|_| UpdateError::FlashError);
}

flash.enable_interrupt_sources();
sys_irq_control(notifications::FLASH_IRQ_MASK, true);
// RECV from the kernel cannot produce an error, so ignore it.
let _ = sys_recv_closed(
&mut [],
notifications::FLASH_IRQ_MASK,
TaskId::KERNEL,
);
flash.disable_interrupt_sources();
}
fn wait_for_flash_interrupt() {
sys_irq_control(notifications::FLASH_IRQ_MASK, true);
// RECV from the kernel cannot produce an error, so ignore it.
let _ =
sys_recv_closed(&mut [], notifications::FLASH_IRQ_MASK, TaskId::KERNEL);
}

fn same_image(which: UpdateTarget) -> bool {
Expand Down
10 changes: 5 additions & 5 deletions drv/update-api/hiffy-rot-flash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

# NOTE: Make sure you are not runnning this on an ancient version of bash on Mac OSX

set -x
set -ex

image=$1
image_type=$2

block_size=512
binsize=`ls -la $image | cut -w -f 5`
binsize=`ls -la $image | cut -d ' ' -f 5`
numblocks=$(("${binsize}" / "${block_size}"))
total_before_last_block=$((numblocks * "${block_size}"))
lastblocksize=$(("${binsize}" - "${total_before_last_block}"))
Expand Down Expand Up @@ -63,10 +63,10 @@ write_blocks() {
humility -a "${ARCHIVE}" hiffy -c Update.prep_image_update -a image_type="${image_type}"

# Begin by invalidating the header which resides at offset 0x130 (in block zero).
write_blocks 0 0 /dev/zero
write_blocks 1 "${loopend}" "${image}"
#write_blocks 0 0 /dev/zero
write_blocks 0 "${loopend}" "${image}"
humility -a "${ARCHIVE}" hiffy -c Update.write_one_block -a block_num="${numblocks}" -i <(dd if="${image}" bs=1 count="${lastblocksize}" skip="${total_before_last_block}")
# Lastly, write the correct block zero.
write_blocks 0 0 "${image}"
#write_blocks 0 0 "${image}"

humility -a "${ARCHIVE}" hiffy -c Update.finish_image_update
Loading

0 comments on commit 375a1f7

Please sign in to comment.