diff --git a/drv/cosmo-hf/src/main.rs b/drv/cosmo-hf/src/main.rs index 4b06280947..1a077fc11b 100644 --- a/drv/cosmo-hf/src/main.rs +++ b/drv/cosmo-hf/src/main.rs @@ -36,25 +36,9 @@ enum Trace { counted_ringbuf!(Trace, 32, Trace::None); -/// Size in bytes of a single page of data (i.e., the max length of slice we -/// accept for `page_program()` and `read_memory()`). -/// -/// This value is really a property of the flash we're talking to and not this -/// driver, but it's correct for all our current parts. If that changes, this -/// will need to change to something more flexible. -pub const PAGE_SIZE_BYTES: usize = 256; - -/// Size in bytes of a single sector of data (i.e., the size of the data erased -/// by a call to `sector_erase()`). -/// -/// This value is really a property of the flash we're talking to and not this -/// driver, but it's correct for all our current parts. If that changes, this -/// will need to change to something more flexible. -/// -/// **Note:** the datasheet refers to a "sector" as a 4K block, but also -/// supports 64K block erases, so we call the latter a sector to match the -/// behavior of the Gimlet host flash driver. -pub const SECTOR_SIZE_BYTES: u32 = 65_536; +// Re-export constants from the generic host flash API +pub use drv_hf_api::PAGE_SIZE_BYTES; +pub const SECTOR_SIZE_BYTES: u32 = drv_hf_api::SECTOR_SIZE_BYTES as u32; /// Total flash size is 128 MiB pub const FLASH_SIZE_BYTES: u32 = 128 * 1024 * 1024; diff --git a/drv/spartan7-loader/grapefruit/README.md b/drv/spartan7-loader/grapefruit/README.md index 0328b9a141..534eb05c4a 100644 --- a/drv/spartan7-loader/grapefruit/README.md +++ b/drv/spartan7-loader/grapefruit/README.md @@ -1,2 +1,2 @@ -generated from [this commit](https://github.com/oxidecomputer/quartz/commit/e31772323d13f15802554ae3daa5061f9c9290cc) -([this CI run](https://github.com/oxidecomputer/quartz/actions/runs/13228105732/job/36921564266)) +generated from [this commit](https://github.com/oxidecomputer/quartz/commit/cf9dfbe5d7b4bfd4bf2c0dac4f3ffe852ff8d20e) +([this CI run](https://github.com/oxidecomputer/quartz/actions/runs/13502683270/job/37724885653)) diff --git a/drv/spartan7-loader/grapefruit/grapefruit.bz2 b/drv/spartan7-loader/grapefruit/grapefruit.bz2 index a5f9ffff2e..a8476667c6 100644 Binary files a/drv/spartan7-loader/grapefruit/grapefruit.bz2 and b/drv/spartan7-loader/grapefruit/grapefruit.bz2 differ diff --git a/lib/host-sp-messages/src/lib.rs b/lib/host-sp-messages/src/lib.rs index 5981290bb1..a93a0a72b3 100644 --- a/lib/host-sp-messages/src/lib.rs +++ b/lib/host-sp-messages/src/lib.rs @@ -123,6 +123,10 @@ pub enum HostToSp { // We use a raw `u8` here for the same reason as in `KeyLookup` above. key: u8, }, + // APOB is followed by a binary data blob, which should be written to flash + APOB { + offset: u64, + }, } /// The order of these cases is critical! We are relying on hubpack's encoding @@ -185,6 +189,7 @@ pub enum SpToHost { name: [u8; 32], }, KeySetResult(#[count(children)] KeySetResult), + APOBResult(u8), } #[derive(Debug, Clone, Copy, PartialEq, Eq, num_derive::FromPrimitive)] diff --git a/task/host-sp-comms/src/main.rs b/task/host-sp-comms/src/main.rs index 70c7729568..150a049540 100644 --- a/task/host-sp-comms/src/main.rs +++ b/task/host-sp-comms/src/main.rs @@ -134,6 +134,11 @@ enum Trace { #[count(children)] message: SpToHost, }, + APOBWriteError { + offset: u64, + #[count(children)] + err: APOBError, + }, } counted_ringbuf!(Trace, 20, Trace::None); @@ -160,6 +165,31 @@ enum Timers { TxPeriodicZeroByte, } +#[derive(Copy, Clone, Debug, Eq, PartialEq, counters::Count)] +enum APOBError { + OffsetOverflow { + offset: u64, + }, + NotErased { + offset: u32, + }, + EraseFailed { + offset: u32, + #[count(children)] + err: drv_hf_api::HfError, + }, + WriteFailed { + offset: u32, + #[count(children)] + err: drv_hf_api::HfError, + }, + ReadFailed { + offset: u32, + #[count(children)] + err: drv_hf_api::HfError, + }, +} + #[export_name = "main"] fn main() -> ! { let mut server = ServerImpl::claim_static_resources(); @@ -967,6 +997,15 @@ impl ServerImpl { }), } } + HostToSp::APOB { offset } => { + Some(match Self::apob_write(&self.hf, offset, data) { + Ok(()) => SpToHost::APOBResult(0), + Err(err) => { + ringbuf_entry!(Trace::APOBWriteError { offset, err }); + SpToHost::APOBResult(1) + } + }) + } }; if let Some(response) = response { @@ -995,6 +1034,51 @@ impl ServerImpl { Ok(()) } + /// Write data to the bonus region of flash + /// + /// This does not take `&self` because we need to force a split borrow + fn apob_write( + hf: &HostFlash, + mut offset: u64, + data: &[u8], + ) -> Result<(), APOBError> { + for chunk in data.chunks(drv_hf_api::PAGE_SIZE_BYTES) { + Self::apob_write_page( + hf, + offset + .try_into() + .map_err(|_| APOBError::OffsetOverflow { offset })?, + chunk, + )?; + offset += chunk.len() as u64; + } + Ok(()) + } + + /// Write a single page of data to the bonus region of flash + /// + /// This does not take `&self` because we need to force a split borrow + fn apob_write_page( + hf: &HostFlash, + offset: u32, + data: &[u8], + ) -> Result<(), APOBError> { + if offset as usize % drv_hf_api::SECTOR_SIZE_BYTES == 0 { + hf.bonus_sector_erase(offset) + .map_err(|err| APOBError::EraseFailed { offset, err })?; + } else { + // Read back the page and confirm that it's all empty + let mut scratch = [0u8; drv_hf_api::PAGE_SIZE_BYTES]; + hf.bonus_read(offset, &mut scratch[..data.len()]) + .map_err(|err| APOBError::ReadFailed { offset, err })?; + if !scratch[..data.len()].iter().all(|b| *b == 0xFF) { + return Err(APOBError::NotErased { offset }); + } + } + hf.bonus_page_program(offset, data) + .map_err(|err| APOBError::WriteFailed { offset, err }) + } + fn handle_sprot( &mut self, sequence: u64,