diff --git a/services/spinor/src/api.rs b/services/spinor/src/api.rs index 20b50b382..642b585a4 100644 --- a/services/spinor/src/api.rs +++ b/services/spinor/src/api.rs @@ -3,8 +3,10 @@ pub(crate) const SERVER_NAME_SPINOR: &str = "_SPINOR Hardware Interface Serv #[cfg(any(target_os = "none", target_os = "xous"))] pub const SPINOR_SIZE_BYTES: u32 = 128 * 1024 * 1024; // physical size of the device, used for hardware sanity checks on requests #[cfg_attr(not(any(target_os = "none", target_os = "xous")), allow(dead_code))] -pub const SPINOR_ERASE_SIZE: u32 = 0x1000; // this is the smallest sector size. 64k sectors also exist, but this implementation does not use them. +pub const SPINOR_ERASE_SIZE: u32 = 0x1000; // this is the smallest sector size. // note: logical lengths of regions are in xous::definitions +#[allow(dead_code)] +pub const SPINOR_BULK_ERASE_SIZE: u32 = 0x1_0000; // this is the bulk erase size. #[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)] pub(crate) enum Opcode { @@ -18,6 +20,8 @@ pub(crate) enum Opcode { ClearStagingWriteProtect, /// program a region. Erase is accomplished by calling WriteRegion with all 0xFF's as data. WriteRegion, + /// bulk erase a region. Has fewer safety checks, used for accelerating the bulk clear of the PDDB for init. + BulkErase, /// allow the susres manager to prevent new ops from happening during a suspend AcquireSuspendLock, @@ -56,6 +60,17 @@ pub(crate) struct WriteRegion { pub data: [u8; 4096], } +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Clone, Copy)] +pub(crate) struct BulkErase { + /// the exclusive access ID + pub id: [u32; 4], + /// start address for the erase; address 0 is start of FLASH. WARNING: This will get rounded down to the nearest 64kiB boundary. + pub start: u32, + /// length of data to erase. WARNING: This will always get rounded up to the nearest 64kiB. + pub len: u32, + /// return code + pub result: Option, +} #[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Clone, Copy, num_derive::FromPrimitive, num_derive::ToPrimitive)] pub enum SpinorError { diff --git a/services/spinor/src/lib.rs b/services/spinor/src/lib.rs index ef944b59d..52e5ac2d0 100644 --- a/services/spinor/src/lib.rs +++ b/services/spinor/src/lib.rs @@ -57,6 +57,9 @@ impl Spinor { /// it must be called very early in the boot process, while we are still in the fully trusted code zone /// This registers that server as the only server which is authorized to request a patch to the SOC gateware region. /// later on, anyone is welcome to try to call it; it will have no effect. + /// + /// Note to self: because we don't have the SOC updater written, this token is curretnly occupied by keys.rs in + /// the shellchat command. Later on, we will want to move this to the final server, once it is written. pub fn register_soc_token(&self) -> Result<(), xous::Error> { send_message(self.conn, Message::new_scalar(Opcode::RegisterSocToken.to_usize().unwrap(), @@ -123,6 +126,83 @@ impl Spinor { Ok(()) } + #[cfg(not(test))] + fn send_bulk_erase(&self, be: &BulkErase) -> Result<(), SpinorError> { + let mut buf = Buffer::into_buf(*be).or(Err(SpinorError::IpcError))?; + buf.lend_mut(self.conn, Opcode::BulkErase.to_u32().unwrap()).or(Err(SpinorError::IpcError))?; + + match buf.to_original::() { + Ok(wr) => { + if let Some(res) = wr.result { + match res { + SpinorError::NoError => Ok(()), + _ => Err(res) + } + } else { + Err(SpinorError::ImplementationError) + } + } + _ => Err(SpinorError::ImplementationError) + } + } + + #[cfg(test)] + fn send_bulk_erase(&self, be: &BulkErase) -> Result<(), SpinorError> { + let mut i = 0; + for addr in be.start..be.start + be.len { + EMU_FLASH.lock().unwrap()[addr as usize] = 0xFF; + i += 1; + } + Ok(()) + } + + /// `bulk_erase` is a function to be used fairly rarely, as it will erase just about anything and everything and it + /// requires a 64k-alignment for the start and len arguments. It's a bit too coarse a hammer to be used in many + /// functions, and confers little performance benefit when erasing fewer than a couple megabytes of data. The main + /// intended use of this is for PDDB-region bulk erase, which is about 100MiB and this should reduce the erase time + /// by about 20-30% instead of using the `patch` function to patch a blank sector. The current implementation enforces + /// the bounds of `bulk_erase` to be within the PDDB region with a check on the server side. + /// + /// Also note that the `start` address is given as an offset from the start of FLASH, and not as an absolute memory address. + pub fn bulk_erase(&self, start: u32, len: u32) -> Result<(), SpinorError> { + if (start & (SPINOR_BULK_ERASE_SIZE - 1)) != 0 { + return Err(SpinorError::AlignmentError); + } + if (len & (SPINOR_BULK_ERASE_SIZE - 1)) != 0 { + return Err(SpinorError::AlignmentError); + } + // acquire a write lock on the unit + #[cfg(not(test))] + { + let response = send_message(self.conn, + Message::new_blocking_scalar(Opcode::AcquireExclusive.to_usize().unwrap(), + self.token[0] as usize, + self.token[1] as usize, + self.token[2] as usize, + self.token[3] as usize, + ) + ).expect("couldn't send AcquireExclusive message to Spinor hardware!"); + if let xous::Result::Scalar1(result) = response { + if result == 0 { + return Err(SpinorError::BusyTryAgain) + } + } + } + let be = BulkErase { + id: self.token, + start, + len, + result: None + }; + let ret = self.send_bulk_erase(&be); + // release the write lock before exiting + #[cfg(not(test))] + let _ = send_message(self.conn, + Message::new_blocking_scalar(Opcode::ReleaseExclusive.to_usize().unwrap(), 0, 0, 0, 0) + ).expect("couldn't send ReleaseExclusive message"); + ret + } + /// `patch` is an extremely low-level function that can patch data on FLASH. Access control must be enforced by higher level /// abstractions. However, there is a single sanity check on the server side that prevents rogue writes to the SoC gateware area. /// Aside from that, it's the wild west! Think of this as `dd` into a raw disk device node, and not a filesystem-abstracted `write` @@ -162,7 +242,7 @@ impl Spinor { self.token[2] as usize, self.token[3] as usize, ) - ).expect("couldn't send AcquireExclusive message to Sha2 hardware!"); + ).expect("couldn't send AcquireExclusive message to Spinor hardware!"); if let xous::Result::Scalar1(result) = response { if result == 0 { return Err(SpinorError::BusyTryAgain) diff --git a/services/spinor/src/main.rs b/services/spinor/src/main.rs index 14faef9cf..4c2010a4a 100644 --- a/services/spinor/src/main.rs +++ b/services/spinor/src/main.rs @@ -20,6 +20,7 @@ mod implementation { #[derive(Debug)] enum FlashOp { EraseSector(u32), // 4k sector + EraseBlock(u32), // 64k block WritePages(u32, [u8; 4096], usize), // page address, data, len ReadId, } @@ -63,6 +64,38 @@ mod implementation { } flash_rdsr(&mut spinor.csr, 0); // dummy read to clear the "read lock" bit }, + Some(FlashOp::EraseBlock(block_address)) => { + // enable writes: set wren mode + loop { + flash_wren(&mut spinor.csr); + let status = flash_rdsr(&mut spinor.csr, 1); + if status & 0x02 != 0 { + break; + } + } + // issue erase command + flash_be4b(&mut spinor.csr, block_address); + // wait for WIP bit to drop + loop { + let status = flash_rdsr(&mut spinor.csr, 1); + if status & 0x01 == 0 { + break; + } + } + // get the success code for return + result = flash_rdscur(&mut spinor.csr); + // disable writes: send wrdi + if flash_rdsr(&mut spinor.csr, 1) & 0x02 != 0 { + loop { + flash_wrdi(&mut spinor.csr); + let status = flash_rdsr(&mut spinor.csr, 1); + if status & 0x02 == 0 { + break; + } + } + } + flash_rdsr(&mut spinor.csr, 0); // dummy read to clear the "read lock" bit + }, Some(FlashOp::WritePages(start_addr, data, len)) => { // assumption: data being written to is already erased (set to 0xFF) assert!(len <= 4096, "data len is too large"); @@ -276,7 +309,6 @@ mod implementation { while csr.rf(utra::spinor::STATUS_WIP) != 0 {} } - #[allow(dead_code)] fn flash_be4b(csr: &mut utralib::CSR, block_address: u32) { csr.wfo(utra::spinor::CMD_ARG_CMD_ARG, block_address); csr.wo(utra::spinor::COMMAND, @@ -453,6 +485,27 @@ mod implementation { } } + pub(crate) fn bulk_erase(&mut self, be: &mut BulkErase) -> SpinorError { + if (be.start & (SPINOR_BULK_ERASE_SIZE - 1)) != 0 { + log::warn!("Bulk erase start address is not block-aligned. Aborting."); + return SpinorError::AlignmentError; + } + if (be.len & (SPINOR_BULK_ERASE_SIZE - 1)) != 0 { + log::warn!("Bulk erase end address is not block-aligned. Aborting."); + return SpinorError::AlignmentError; + } + for block in (be.start..be.start + be.len).step_by(SPINOR_BULK_ERASE_SIZE as usize) { + self.cur_op = Some(FlashOp::EraseBlock(block)); + log::trace!("bulk erase: {:x?}", block); + let erase_result = self.call_spinor_context_blocking(); + if erase_result & 0x40 != 0 { + log::error!("E_FAIL set, erase failed: result 0x{:02x}, block addr 0x{:08x}", erase_result, block); + return SpinorError::EraseFailed; + } + } + return SpinorError::NoError + } + pub fn suspend(&mut self) { self.susres.suspend(); } @@ -483,6 +536,9 @@ mod implementation { pub(crate) fn write_region(&mut self, _wr: &mut WriteRegion) -> SpinorError { SpinorError::ImplementationError } + pub(crate) fn bulk_erase(&mut self, _be: &mut BulkErase) -> SpinorError { + SpinorError::ImplementationError + } } } @@ -681,6 +737,34 @@ fn xmain() -> ! { } buffer.replace(wr).expect("couldn't return response code to WriteRegion"); }, + Some(Opcode::BulkErase) => { + let mut buffer = unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + let mut wr = buffer.to_original::().unwrap(); + // bounds check to within the PDDB region for bulk erases. Please use standard patching for other regions. + let authorized = + if (wr.start >= xous::PDDB_LOC) && ((wr.start + wr.len) <= (xous::PDDB_LOC + xous::PDDB_LEN)) { + true + } else { + false + }; + if authorized { + match client_id { + Some(id) => { + if wr.id == id { + wr.result = Some(spinor.bulk_erase(&mut wr)); // note: this must reject out-of-bound length requests for security reasons + } else { + wr.result = Some(SpinorError::IdMismatch); + } + }, + _ => { + wr.result = Some(SpinorError::NoId); + } + } + } else { + wr.result = Some(SpinorError::AccessDenied); + } + buffer.replace(wr).expect("couldn't return response code to WriteRegion"); + } Some(Opcode::EccError) => msg_scalar_unpack!(msg, address, _overflow, _, _, { // just some stand-in code -- should probably do something more clever, e.g. a rolling log // plus some error handling callback. But this is in the distant future once we have enough