add bulk erase command to accelerate PDDB init

This commit is contained in:
bunnie 2022-01-04 21:57:47 +08:00
parent 89f5b49c96
commit 3a0dfe5c3b
3 changed files with 182 additions and 3 deletions

View file

@ -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<SpinorError>,
}
#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Clone, Copy, num_derive::FromPrimitive, num_derive::ToPrimitive)]
pub enum SpinorError {

View file

@ -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::<BulkErase, _>() {
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)

View file

@ -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<u32>, 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::<BulkErase, _>().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