Skip to content

Commit 464e168

Browse files
authored
[sp-sim] Simulate host flash hashing (#8584)
1 parent 24d6575 commit 464e168

File tree

4 files changed

+104
-7
lines changed

4 files changed

+104
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sp-sim/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ omicron-common.workspace = true
2222
oxide-tokio-rt.workspace = true
2323
serde.workspace = true
2424
serde_cbor.workspace = true
25+
sha2.workspace = true
2526
sha3.workspace = true
2627
slog.workspace = true
2728
slog-dtrace.workspace = true

sp-sim/src/gimlet.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,18 +1596,15 @@ impl SpHandler for Handler {
15961596
_addr: u32,
15971597
_buf: &mut [u8],
15981598
) -> Result<(), SpError> {
1599-
// TODO we should simulate this; will need it for OS update testing.
16001599
Err(SpError::RequestUnsupportedForSp)
16011600
}
16021601

1603-
fn start_host_flash_hash(&mut self, _slot: u16) -> Result<(), SpError> {
1604-
// TODO we should simulate this; will need it for OS update testing.
1605-
Err(SpError::RequestUnsupportedForSp)
1602+
fn start_host_flash_hash(&mut self, slot: u16) -> Result<(), SpError> {
1603+
self.update_state.start_host_flash_hash(slot)
16061604
}
16071605

1608-
fn get_host_flash_hash(&mut self, _slot: u16) -> Result<[u8; 32], SpError> {
1609-
// TODO we should simulate this; will need it for OS update testing.
1610-
Err(SpError::RequestUnsupportedForSp)
1606+
fn get_host_flash_hash(&mut self, slot: u16) -> Result<[u8; 32], SpError> {
1607+
self.update_state.get_host_flash_hash(slot)
16111608
}
16121609
}
16131610

sp-sim/src/update.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use std::collections::BTreeMap;
66
use std::io::Cursor;
77
use std::mem;
8+
use std::time::Duration;
9+
use std::time::Instant;
810

911
use crate::SIM_GIMLET_BOARD;
1012
use crate::SIM_ROT_BOARD;
@@ -13,6 +15,7 @@ use crate::SIM_SIDECAR_BOARD;
1315
use crate::helpers::rot_slot_id_from_u16;
1416
use crate::helpers::rot_slot_id_to_u16;
1517
use gateway_messages::Fwid;
18+
use gateway_messages::HfError;
1619
use gateway_messages::RotSlotId;
1720
use gateway_messages::RotStateV3;
1821
use gateway_messages::SpComponent;
@@ -21,9 +24,14 @@ use gateway_messages::UpdateChunk;
2124
use gateway_messages::UpdateId;
2225
use gateway_messages::UpdateInProgressStatus;
2326
use hubtools::RawHubrisImage;
27+
use sha2::Sha256;
2428
use sha3::Digest;
2529
use sha3::Sha3_256;
2630

31+
// How long do we take to hash host flash? Real SPs take a handful of seconds;
32+
// we'll pick something similar.
33+
const TIME_TO_HASH_HOST_PHASE_1: Duration = Duration::from_secs(5);
34+
2735
pub(crate) struct SimSpUpdate {
2836
/// tracks the state of any ongoing simulated update
2937
///
@@ -38,6 +46,8 @@ pub(crate) struct SimSpUpdate {
3846
/// data from the last completed phase1 update for each slot (exposed for
3947
/// testing)
4048
last_host_phase1_update_data: BTreeMap<u16, Box<[u8]>>,
49+
/// state of hashing each of the host phase1 slots
50+
phase1_hash_state: BTreeMap<u16, HostFlashHashState>,
4151

4252
/// records whether a change to the stage0 "active slot" has been requested
4353
pending_stage0_update: bool,
@@ -177,6 +187,7 @@ impl SimSpUpdate {
177187
last_sp_update_data: None,
178188
last_rot_update_data: None,
179189
last_host_phase1_update_data: BTreeMap::new(),
190+
phase1_hash_state: BTreeMap::new(),
180191

181192
pending_stage0_update: false,
182193

@@ -303,6 +314,13 @@ impl SimSpUpdate {
303314
std::io::Write::write_all(data, chunk_data)
304315
.map_err(|_| SpError::UpdateIsTooLarge)?;
305316

317+
// If we're writing to the host flash, invalidate the cached
318+
// hash of this slot.
319+
if *component == SpComponent::HOST_CPU_BOOT_FLASH {
320+
self.phase1_hash_state
321+
.insert(*slot, HostFlashHashState::HashInvalidated);
322+
}
323+
306324
if data.position() == data.get_ref().len() as u64 {
307325
let mut stolen = Cursor::new(Box::default());
308326
mem::swap(data, &mut stolen);
@@ -452,6 +470,78 @@ impl SimSpUpdate {
452470
self.last_host_phase1_update_data.get(&slot).cloned()
453471
}
454472

473+
pub(crate) fn start_host_flash_hash(
474+
&mut self,
475+
slot: u16,
476+
) -> Result<(), SpError> {
477+
match self
478+
.phase1_hash_state
479+
.entry(slot)
480+
.or_insert(HostFlashHashState::NeverHashed)
481+
{
482+
// No current hash; record our start time so we can emulate hashing
483+
// taking a few seconds.
484+
state @ (HostFlashHashState::NeverHashed
485+
| HostFlashHashState::HashInvalidated) => {
486+
*state = HostFlashHashState::HashStarted(Instant::now());
487+
Ok(())
488+
}
489+
// Already hashed; this is a no-op.
490+
HostFlashHashState::Hashed(_) => Ok(()),
491+
// Still hashing; check and see if it's done. This is either an
492+
// error (if we're still hashing) or a no-op (if we're done).
493+
HostFlashHashState::HashStarted(started) => {
494+
let started = *started;
495+
self.finalize_host_flash_hash_if_sufficient_time_elapsed(
496+
slot, started,
497+
)?;
498+
Ok(())
499+
}
500+
}
501+
}
502+
503+
pub(crate) fn get_host_flash_hash(
504+
&mut self,
505+
slot: u16,
506+
) -> Result<[u8; 32], SpError> {
507+
match self
508+
.phase1_hash_state
509+
.entry(slot)
510+
.or_insert(HostFlashHashState::NeverHashed)
511+
{
512+
HostFlashHashState::NeverHashed => {
513+
Err(SpError::Hf(HfError::HashUncalculated))
514+
}
515+
HostFlashHashState::HashStarted(started) => {
516+
let started = *started;
517+
self.finalize_host_flash_hash_if_sufficient_time_elapsed(
518+
slot, started,
519+
)
520+
}
521+
HostFlashHashState::Hashed(hash) => Ok(*hash),
522+
HostFlashHashState::HashInvalidated => {
523+
Err(SpError::Hf(HfError::RecalculateHash))
524+
}
525+
}
526+
}
527+
528+
fn finalize_host_flash_hash_if_sufficient_time_elapsed(
529+
&mut self,
530+
slot: u16,
531+
started: Instant,
532+
) -> Result<[u8; 32], SpError> {
533+
if started.elapsed() < TIME_TO_HASH_HOST_PHASE_1 {
534+
return Err(SpError::Hf(HfError::HashInProgress));
535+
}
536+
537+
let data = self.last_host_phase1_update_data(slot);
538+
let data = data.as_deref().unwrap_or(&[]);
539+
let hash = Sha256::digest(&data).into();
540+
self.phase1_hash_state.insert(slot, HostFlashHashState::Hashed(hash));
541+
542+
Ok(hash)
543+
}
544+
455545
pub(crate) fn get_component_caboose_value(
456546
&mut self,
457547
component: SpComponent,
@@ -663,3 +753,11 @@ fn fake_fwid_compute(data: &[u8]) -> Fwid {
663753
digest.update(data);
664754
Fwid::Sha3_256(digest.finalize().into())
665755
}
756+
757+
#[derive(Debug, Clone, Copy)]
758+
enum HostFlashHashState {
759+
NeverHashed,
760+
HashStarted(Instant),
761+
Hashed([u8; 32]),
762+
HashInvalidated,
763+
}

0 commit comments

Comments
 (0)