From 5526bc410078f0bcaac9581cd3df927f708fb6e2 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 21 May 2021 04:35:01 +0800 Subject: [PATCH] add hardware support for sha512/256 --- services/engine-sha512/src/lib.rs | 311 +++++++++++++++++++---------- services/engine-sha512/src/main.rs | 1 + services/shellchat/src/cmds/sha.rs | 2 +- 3 files changed, 212 insertions(+), 102 deletions(-) diff --git a/services/engine-sha512/src/lib.rs b/services/engine-sha512/src/lib.rs index 2892e61be..14eea2aa6 100644 --- a/services/engine-sha512/src/lib.rs +++ b/services/engine-sha512/src/lib.rs @@ -76,15 +76,117 @@ impl Engine512 { } } +// a macro for common communications libraries for SHA2 hardware interfacing +// you can't reference fields in a trait. looks like a macro is the accepted way +// of not having to repeat this code over and over again. +macro_rules! sha512_comms { + () => { + pub(crate) fn ensure_conn(&self) -> u32 { + if HW_CONN.load(Ordering::Relaxed) == 0 { + let xns = xous_names::XousNames::new().unwrap(); + HW_CONN.store(xns.request_connection_blocking(api::SERVER_NAME_SHA512).expect("Can't connect to Sha512 server"), Ordering::Relaxed); + let trng = trng::Trng::new(&xns).expect("Can't connect to TRNG server"); + let id1 = trng.get_u64().unwrap(); + let id2 = trng.get_u32().unwrap(); + TOKEN[0].store((id1 >> 32) as u32, Ordering::Relaxed); + TOKEN[1].store(id1 as u32, Ordering::Relaxed); + TOKEN[2].store(id2, Ordering::Relaxed); + } + HW_CONN.load(Ordering::Relaxed) + } + pub fn is_idle(&self) -> Result { + let response = send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::IsIdle.to_usize().unwrap(), 0, 0, 0, 0) + ).expect("Couldn't make IsIdle query"); + if let xous::Result::Scalar1(result) = response { + if result != 0 { + Ok(true) + } else { + Ok(false) + } + } else { + Err(xous::Error::InternalError) + } + } + pub fn acquire_suspend_lock(&self) -> Result { + let response = send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::AcquireSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) + ).expect("Couldn't issue AcquireSuspendLock message"); + if let xous::Result::Scalar1(result) = response { + if result != 0 { + Ok(true) + } else { + Ok(false) + } + } else { + Err(xous::Error::InternalError) + } + } + pub fn abort_suspend(&self) -> Result<(), xous::Error> { + // we ignore the result and just turn it into () once we get anything back, as abort_suspend "can't fail" + send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::AbortSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) + ).map(|_| ()) + } + pub(crate) fn try_acquire_hw(&mut self, config: Sha2Config) { + if !self.in_progress && (self.strategy != FallbackStrategy::SoftwareOnly) { + loop { + let conn = self.ensure_conn(); // also ensures the ID + let response = send_message(conn, + Message::new_blocking_scalar(Opcode::AcquireExclusive.to_usize().unwrap(), + TOKEN[0].load(Ordering::Relaxed) as usize, + TOKEN[1].load(Ordering::Relaxed) as usize, + TOKEN[2].load(Ordering::Relaxed) as usize, + config.to_usize().unwrap(), + ) + ).expect("couldn't send AcquireExclusive message to Sha2 hardware!"); + if let xous::Result::Scalar1(result) = response { + if result != 0 { + self.use_soft = false; + self.in_progress = true; + break; + } else { + if self.strategy == FallbackStrategy::HardwareThenSoftware { + self.use_soft = true; + self.in_progress = true; + break; + } else { + // this is hardware-exclusive mode, we block until we can get the hardware + xous::yield_slice(); + } + } + } else { + log::error!("AcquireExclusive had an unexpected error: {:?}", response); + panic!("Internal error in AcquireExclusive"); + } + } + } + } + pub(crate) fn reset_hw(&mut self) { + send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::Reset.to_usize().unwrap(), + TOKEN[0].load(Ordering::Relaxed) as usize, + TOKEN[1].load(Ordering::Relaxed) as usize, + TOKEN[2].load(Ordering::Relaxed) as usize, + 0, + )).expect("couldn't send reset to hardware"); + // reset internal flags + self.length = 0; + self.in_progress = false; + self.use_soft = true; + } + }; +} + /// The SHA-512 hash algorithm with the SHA-512 initial hash value. #[derive(Clone)] pub struct Sha512 { + /// software fallback engine + engine: Engine512, /// whether or not this current hasher instance will use software or hardware acceleration use_soft: bool, /// specifies the strategy for fallback in case multiple hashes are initiated simultaneously strategy: FallbackStrategy, - /// software fallback engine - engine: Engine512, /// track if a hash is in progress in_progress: bool, /// track the length of the message processed so far @@ -106,53 +208,8 @@ impl Sha512 { length: 0, } } - fn ensure_conn(&self) -> u32 { - if HW_CONN.load(Ordering::Relaxed) == 0 { - let xns = xous_names::XousNames::new().unwrap(); - HW_CONN.store(xns.request_connection_blocking(api::SERVER_NAME_SHA512).expect("Can't connect to Sha512 server"), Ordering::Relaxed); - let trng = trng::Trng::new(&xns).expect("Can't connect to TRNG server"); - let id1 = trng.get_u64().unwrap(); - let id2 = trng.get_u32().unwrap(); - TOKEN[0].store((id1 >> 32) as u32, Ordering::Relaxed); - TOKEN[1].store(id1 as u32, Ordering::Relaxed); - TOKEN[2].store(id2, Ordering::Relaxed); - } - HW_CONN.load(Ordering::Relaxed) - } - pub fn is_idle(&self) -> Result { - let response = send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::IsIdle.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("Couldn't make IsIdle query"); - if let xous::Result::Scalar1(result) = response { - if result != 0 { - Ok(true) - } else { - Ok(false) - } - } else { - Err(xous::Error::InternalError) - } - } - pub fn acquire_suspend_lock(&self) -> Result { - let response = send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::AcquireSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("Couldn't issue AcquireSuspendLock message"); - if let xous::Result::Scalar1(result) = response { - if result != 0 { - Ok(true) - } else { - Ok(false) - } - } else { - Err(xous::Error::InternalError) - } - } - pub fn abort_suspend(&self) -> Result<(), xous::Error> { - // we ignore the result and just turn it into () once we get anything back, as abort_suspend "can't fail" - send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::AbortSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) - ).map(|_| ()) - } + // make all the boilerplate comms code shared between all sizes of digest + sha512_comms!(); } impl Default for Sha512 { @@ -167,6 +224,9 @@ impl Drop for Sha512 { // normally, we would de-allocate a connection but because the Digest API assumes that // all instances are fungible we can't do that, as the connection needs to be persistent // between invocations of the object. + if !self.use_soft { + self.reset_hw(); + } } } @@ -176,38 +236,7 @@ impl BlockInput for Sha512 { impl Update for Sha512 { fn update(&mut self, input: impl AsRef<[u8]>) { - if !self.in_progress && (self.strategy != FallbackStrategy::SoftwareOnly) { - loop { - let conn = self.ensure_conn(); // also ensures the ID - let response = send_message(conn, - Message::new_blocking_scalar(Opcode::AcquireExclusive.to_usize().unwrap(), - TOKEN[0].load(Ordering::Relaxed) as usize, - TOKEN[1].load(Ordering::Relaxed) as usize, - TOKEN[2].load(Ordering::Relaxed) as usize, - Sha2Config::Sha512.to_usize().unwrap(), - ) - ).expect("couldn't send AcquireExclusive message to Sha2 hardware!"); - if let xous::Result::Scalar1(result) = response { - if result != 0 { - self.use_soft = false; - self.in_progress = true; - break; - } else { - if self.strategy == FallbackStrategy::HardwareThenSoftware { - self.use_soft = true; - self.in_progress = true; - break; - } else { - // this is hardware-exclusive mode, we block until we can get the hardware - xous::yield_slice(); - } - } - } else { - log::error!("AcquireExclusive had an unexpected error: {:?}", response); - panic!("Internal error in AcquireExclusive"); - } - } - } + self.try_acquire_hw(Sha2Config::Sha512); // split the incoming blocks to page size and send to the engine if self.use_soft { self.engine.update(input.as_ref()); @@ -282,17 +311,7 @@ impl Reset for Sha512 { if self.use_soft { self.engine.reset(&H512); } else { - send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::Reset.to_usize().unwrap(), - TOKEN[0].load(Ordering::Relaxed) as usize, - TOKEN[1].load(Ordering::Relaxed) as usize, - TOKEN[2].load(Ordering::Relaxed) as usize, - 0, - )).expect("couldn't send reset to hardware"); - // reset internal flags - self.length = 0; - self.in_progress = false; - self.use_soft = true; + self.reset_hw(); } } } @@ -307,13 +326,46 @@ impl Reset for Sha512 { #[derive(Clone)] pub struct Sha512Trunc256 { engine: Engine512, + /// whether or not this current hasher instance will use software or hardware acceleration + use_soft: bool, + /// specifies the strategy for fallback in case multiple hashes are initiated simultaneously + strategy: FallbackStrategy, + /// track if a hash is in progress + in_progress: bool, + /// track the length of the message processed so far + length: u64, +} +impl Sha512Trunc256 { + // use this function instead of default for more control over configuration of the hardware engine + pub fn new(maybe_strategy: Option) -> Self { + let strategy = if let Some(strat) = maybe_strategy { + strat + } else { + FallbackStrategy::HardwareThenSoftware + }; + Sha512Trunc256 { + use_soft: true, + strategy, + engine: Engine512::new(&H512_TRUNC_256), + in_progress: false, + length: 0, + } + } + // make all the boilerplate comms code shared between all sizes of digest + sha512_comms!(); +} + +impl Drop for Sha512Trunc256 { + fn drop(&mut self) { + if !self.use_soft { + self.reset_hw(); + } + } } impl Default for Sha512Trunc256 { fn default() -> Self { - Sha512Trunc256 { - engine: Engine512::new(&H512_TRUNC_256), - } + Sha512Trunc256::new(Some(FallbackStrategy::HardwareThenSoftware)) } } @@ -323,7 +375,26 @@ impl BlockInput for Sha512Trunc256 { impl Update for Sha512Trunc256 { fn update(&mut self, input: impl AsRef<[u8]>) { - self.engine.update(input.as_ref()); + self.try_acquire_hw(Sha2Config::Sha512Trunc256); + if self.use_soft { + self.engine.update(input.as_ref()); + } else { + for chunk in input.as_ref().chunks(3968) { + // one SHA512 block (128 bytes) short of 4096 to give space for struct overhead in page remap handling + let mut update = Sha2Update { + id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], + buffer: [0; 3968], + len: 0, + }; + self.length += (chunk.len() as u64) * 8; // we need to keep track of length in bits + for (&src, dest) in chunk.iter().zip(&mut update.buffer) { + *dest = src; + } + update.len = chunk.len() as u16; + let buf = Buffer::into_buf(update).expect("couldn't map chunk into IPC buffer"); + buf.lend(self.ensure_conn(), Opcode::Update.to_u32().unwrap()).expect("hardware rejected our hash chunk!"); + } + } } } @@ -331,17 +402,55 @@ impl FixedOutputDirty for Sha512Trunc256 { type OutputSize = U32; fn finalize_into_dirty(&mut self, out: &mut digest::Output) { - self.engine.finish(); - let s = &self.engine.state[..4]; - for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { - chunk.copy_from_slice(&v.to_be_bytes()); + if self.use_soft { + self.engine.finish(); + let s = &self.engine.state[..4]; + for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + } else { + let result = Sha2Finalize { + id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], + result: Sha2Result::Uninitialized, + length_in_bits: None, + }; + let mut buf = Buffer::into_buf(result).expect("couldn't map memory for the return buffer"); + buf.lend_mut(self.ensure_conn(), Opcode::Finalize.to_u32().unwrap()).expect("couldn't finalize"); + + let returned: Sha2Finalize = buf.to_original().expect("couldn't decode return buffer"); + match returned.result { + Sha2Result::Sha512Trunc256Result(s) => { + if self.length != returned.length_in_bits.expect("hardware did not return a length field!") { + panic!("Sha512 hardware did not hash as many bits as we had expected!") + } + for (dest, &src) in out.chunks_exact_mut(1).zip(s.iter()) { + dest.copy_from_slice(&[src]) + } + } + Sha2Result::Sha512Result(_) => { + panic!("Sha512 hardware returned the wrong type of buffer!"); + } + Sha2Result::SuspendError => { + panic!("Hardware was suspended during Sha512 operation, result is invalid."); + } + Sha2Result::Uninitialized => { + panic!("Hardware didn't copy Sha512 hash result to the return buffer."); + } + Sha2Result::IdMismatch => { + panic!("Hardware is not currently processing our block, finalize call has no meaning."); + } + } } } } impl Reset for Sha512Trunc256 { fn reset(&mut self) { - self.engine.reset(&H512_TRUNC_256); + if self.use_soft { + self.engine.reset(&H512_TRUNC_256); + } else { + self.reset_hw(); + } } } diff --git a/services/engine-sha512/src/main.rs b/services/engine-sha512/src/main.rs index 9aec2f125..8a3e7998e 100644 --- a/services/engine-sha512/src/main.rs +++ b/services/engine-sha512/src/main.rs @@ -257,6 +257,7 @@ fn xmain() -> ! { Some(Opcode::AcquireExclusive) => msg_blocking_scalar_unpack!(msg, id0, id1, id2, flags, { if client_id.is_none() && !SUSPEND_PENDING.load(Ordering::Relaxed) { client_id = Some([id0 as u32, id1 as u32, id2 as u32]); + //log::trace!("giving {:x?} an exclusive lock", client_id); mode = Some(FromPrimitive::from_usize(flags).unwrap()); SUSPEND_FAILURE.store(false, Ordering::Relaxed); HASH_IN_PROGRESS.store(true, Ordering::Relaxed); diff --git a/services/shellchat/src/cmds/sha.rs b/services/shellchat/src/cmds/sha.rs index 47950b1f8..e5d18f8ed 100644 --- a/services/shellchat/src/cmds/sha.rs +++ b/services/shellchat/src/cmds/sha.rs @@ -179,7 +179,7 @@ impl<'a> ShellCmdApi<'a> for Sha { ]; let mut pass: bool = true; - let mut hasher = engine_sha512::Sha512Trunc256::new(); + let mut hasher = engine_sha512::Sha512Trunc256::new(Some(FallbackStrategy::WaitForHardware)); hasher.update(K_DATA); let digest = hasher.finalize();