mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
add bulk erase command to accelerate PDDB init
This commit is contained in:
parent
89f5b49c96
commit
3a0dfe5c3b
3 changed files with 182 additions and 3 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue