diff --git a/drv/spartan7-loader/cosmo-seq/README.md b/drv/spartan7-loader/cosmo-seq/README.md index 98a5c35b14..29ab3729d0 100644 --- a/drv/spartan7-loader/cosmo-seq/README.md +++ b/drv/spartan7-loader/cosmo-seq/README.md @@ -1,3 +1 @@ -FPGA images and collateral are generated from: -[this sha](https://github.com/oxidecomputer/quartz/commit/bdc5fb31e1905a1b66c19647fe2d156dd1b97b7b) -[release](https://api.github.com/repos/oxidecomputer/quartz/releases/242278756) \ No newline at end of file +FPGA images and collateral are generated from some hw guy's local build diff --git a/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 b/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 index 271f0fd82b..bca26f96e4 100644 Binary files a/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 and b/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 differ diff --git a/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json b/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json index 0e3eb61297..251e8083eb 100644 --- a/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json +++ b/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json @@ -15,7 +15,7 @@ }, { "type": "addrmap", - "addr_span_bytes": 44, + "addr_span_bytes": 48, "inst_name": "spi_nor", "orig_type_name": "spi_nor_regs", "addr_offset": 256, diff --git a/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json b/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json index ae86d6aff9..f656c18a6f 100644 --- a/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json +++ b/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json @@ -1,6 +1,6 @@ { "type": "addrmap", - "addr_span_bytes": 44, + "addr_span_bytes": 48, "inst_name": "spi_nor_regs", "addr_offset": 0, "children": [ @@ -311,6 +311,26 @@ "desc": "Length of the APOB flash region" } ] + }, + { + "type": "reg", + "inst_name": "ApobFlashOffset", + "addr_offset": 44, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "offset", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Offset of the APOB flash slot" + } + ] } ] } \ No newline at end of file diff --git a/lib/host-sp-messages/src/lib.rs b/lib/host-sp-messages/src/lib.rs index fa56fa0bb5..4bb69fba7b 100644 --- a/lib/host-sp-messages/src/lib.rs +++ b/lib/host-sp-messages/src/lib.rs @@ -123,6 +123,15 @@ pub enum HostToSp { // We use a raw `u8` here for the same reason as in `KeyLookup` above. key: u8, }, + // ApobWrite is followed by a binary data blob, to be written to flash + ApobWrite { + offset: u64, + }, + // ApobRead returns an ApobResult followed by trailing data + ApobRead { + offset: u64, + size: u64, + }, } /// The order of these cases is critical! We are relying on hubpack's encoding @@ -185,6 +194,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 3835dfda09..627d3ac402 100644 --- a/task/host-sp-comms/src/main.rs +++ b/task/host-sp-comms/src/main.rs @@ -145,6 +145,16 @@ enum Trace { #[count(children)] message: SpToHost, }, + ApobWriteError { + offset: u64, + #[count(children)] + err: ApobError, + }, + ApobReadError { + offset: u64, + #[count(children)] + err: ApobError, + }, } counted_ringbuf!(Trace, 20, Trace::None); @@ -171,6 +181,34 @@ enum Timers { TxPeriodicZeroByte, } +#[derive(Copy, Clone, Debug, Eq, PartialEq, counters::Count)] +enum ApobError { + OffsetOverflow { + page_offset: u64, + }, + SizeOverflow { + size: u64, + }, + NotErased { + page_offset: u32, + }, + EraseFailed { + page_offset: u32, + #[count(children)] + err: drv_hf_api::HfError, + }, + WriteFailed { + page_offset: u32, + #[count(children)] + err: drv_hf_api::HfError, + }, + ReadFailed { + page_offset: u32, + #[count(children)] + err: drv_hf_api::HfError, + }, +} + #[export_name = "main"] fn main() -> ! { let mut server = ServerImpl::claim_static_resources(); @@ -993,6 +1031,20 @@ impl ServerImpl { }), } } + HostToSp::ApobWrite { 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) + } + }) + } + HostToSp::ApobRead { offset, size } => { + // apob_read does serialization itself + self.apob_read(header.sequence, offset, size); + None + } }; if let Some(response) = response { @@ -1021,6 +1073,87 @@ 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 { + page_offset: 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, + page_offset: u32, + data: &[u8], + ) -> Result<(), ApobError> { + if (page_offset as usize).is_multiple_of(drv_hf_api::SECTOR_SIZE_BYTES) + { + hf.bonus_sector_erase(page_offset) + .map_err(|err| ApobError::EraseFailed { page_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(page_offset, &mut scratch[..data.len()]) + .map_err(|err| ApobError::ReadFailed { page_offset, err })?; + if !scratch[..data.len()].iter().all(|b| *b == 0xFF) { + return Err(ApobError::NotErased { page_offset }); + } + } + hf.bonus_page_program(page_offset, data) + .map_err(|err| ApobError::WriteFailed { page_offset, err }) + } + + /// Reads and encodes data from the bonus region of flash + fn apob_read(&mut self, sequence: u64, offset: u64, size: u64) { + self.tx_buf.try_encode_response( + sequence, + &SpToHost::ApobResult(0), + |buf| match Self::apob_read_inner(buf, &self.hf, offset, size) { + Ok(n) => Ok(n), + Err(err) => { + ringbuf_entry!(Trace::ApobReadError { offset, err }); + Err(SpToHost::ApobResult(1)) + } + }, + ); + } + + fn apob_read_inner( + buf: &mut [u8], + hf: &HostFlash, + offset: u64, + size: u64, + ) -> Result { + let size = usize::try_from(size) + .map_err(|_| ApobError::SizeOverflow { size })?; + for d in (0..size).step_by(drv_hf_api::PAGE_SIZE_BYTES) { + let chunk_size = (size - d).min(drv_hf_api::PAGE_SIZE_BYTES); + let page_offset = offset + d as u64; + let page_offset = u32::try_from(page_offset) + .map_err(|_| ApobError::OffsetOverflow { page_offset })?; + + hf.bonus_read(page_offset, &mut buf[d..][..chunk_size]) + .map_err(|err| ApobError::ReadFailed { page_offset, err })?; + } + Ok(size) + } + fn handle_sprot( &mut self, sequence: u64,