Skip to content

Conversation

mkeeter
Copy link
Collaborator

@mkeeter mkeeter commented Feb 7, 2025

hf.bonus_sector_erase(offset)
.map_err(|err| APOBError::EraseFailed { offset, err })?;
} else {
// Read back the page and confirm that it's all empty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this fail if there's a blip in the IPCC path and the host resends an APOB request? (I'm not sure what the expectations are for the offsets the host is providing.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has changed a bunch since February; messages should now all be idempotent.

@mkeeter mkeeter marked this pull request as draft February 7, 2025 17:08
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some mostly kind of annoying nitpicks.

Comment on lines 1079 to 1083
fn apob_write(
hf: &HostFlash,
mut offset: u64,
data: &[u8],
) -> Result<(), ApobError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we need something in the broader API to discover what the erase size granularity is or at least make sure that we're sending stuff that is page size aligned. This gets to what @jgallagher gets at below. But if the host sent things that wasn't page aligned then we'd erase the entire page because our API is not doing a read-modify-write.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has all changed now – we ensure that the to-be-written page is erased when the state machine starts, and writes are idempotent.

@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch 3 times, most recently from 14165d3 to b0acdc3 Compare September 26, 2025 15:37
@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch from f7e8314 to 4a07f2b Compare October 1, 2025 14:00
@mkeeter mkeeter marked this pull request as ready for review October 1, 2025 14:13
@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch from ded1fa1 to ad93477 Compare October 1, 2025 14:27
Copy link
Collaborator

@labbott labbott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will probably do another pass later

checksum: 0, // dummy value
};
out.checksum = out.expected_checksum();
assert!(out.is_valid());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert becomes panic, is that the behavior we want here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so; the only way this should panic is if someone has broken the code in a dramatic way (e.g. editing the implementation of is_valid so that previously valid data is no longer valid).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is also copied from HfRawPersistentData, so I didn't think about it too much!

Comment on lines +265 to +267
/// Either 0 or 1; directly translatable to [`ApobSlot`]
pub slot_select: u32,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of places where we have unreachable because of using the u32, maaaybe an enum would be cleaner and reduce a few checks? Or is the issue this needs to match exactly what the host is expecting?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't seen at all by the host, but we're reading / writing this object directly to disk, so we need zerocopy-friendly types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, zerocopy::TryFromBytes can be derived for enums (though I'm not sure how annoying that would be to actually use here)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be usable, but the docs seem to imply that you shouldn't it when round-tripping through bytes (?!).

I've opened google/zerocopy#2722 to ask for clarification

fn fail(err: drv_hf_api::HfError) {
let mut buffer = [0; hf::idl::INCOMING_SIZE];
let mut server = hf::idl::FailServer::new(err);
let mut server = hf::FailServer(err);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, do we lose the idl generation of this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah – I switched to finer-grained error types for a few methods, which means that the IDL generator can't make a FailServer (which assumes a single error type).

pub(crate) fn write(
&mut self,
drv: &mut FlashDriver,
offset: u64,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the u64 everywhere? It seems like everything gets converted/checked against u32 anyway

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial u64 is sent by the host, but I pushed the u32 conversion upstream into host_sp_comms.

@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch 2 times, most recently from 49afe3d to 695c7d5 Compare October 2, 2025 14:30
@citrus-it
Copy link
Contributor

@mkeeter - here are the IPCC messages which are expected to be seen before the host is finished with APOB. Anything else incoming from the host should trigger the lockdown.

humility: ring buffer task_host_sp_comms::__RINGBUF in host_sp_comms:
   TOTAL VARIANT
      99 Request(ApobRead)
      96 Request(ApobData)
       2 Request(KeyLookup)
       1 Request(GetBootStorageUnit)
       1 Request(GetIdentity)
       1 Request(GetStatus)
       1 Request(AckSpStart)
       1 Request(ApobBegin)
       1 Request(ApobCommit)

@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch from efc7039 to 8d35455 Compare October 2, 2025 20:35
@mkeeter
Copy link
Collaborator Author

mkeeter commented Oct 2, 2025

@citrus-it Great, thanks! I've pushed this list to Hubris and to RFD 593.

@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch from 8d35455 to 58e761d Compare October 2, 2025 20:40
@mkeeter mkeeter force-pushed the mkeeter/ipcc-apob branch from 58e761d to f883776 Compare October 3, 2025 15:15
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the latest changes, this looks good to me! I'd be happy to approve this now, but I felt like some of the // XXX: should this lock the state machine? comments could maybe use a second opinion from someone wiser than I am (perhaps @labbott?).

Comment on lines +268 to +271
/// See rfd.shared.oxide.computer/rfd/593#_production_strength_implementation
/// for details on the states and transitions. Note that the diagram in the RFD
/// includes fine-grained states (e.g. writing), which the actual implementation
/// never dwells in; these states are not explicit in `ApobState`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

Comment on lines 628 to 640
// This is a little tricky: we allow for bytes to either match our
// expected write (for idempotency), _or_ to be `0xFF` (because that
// means they're erased). We have to check every byte to confirm
// that they all match, but can bail immediately if we find a
// non-matching byte that is *also* not erased.
let mut needs_write = false;
for (a, b) in buf.scratch[..n].iter().zip(buf.page[..n].iter()) {
if *a != *b {
all_matches = false;
// You may be tempted to insert a `break` here, but that
// would be incorrect: there could be subsequent bytes which
// do not match *and* are not erased, in which case we must
// return `NotErased`.
needs_write = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments here aer lovely, thank you for adding them --- this is all much clearer now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants