Skip to content

Transient boot selection #2050

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ getrandom = { version = "0.2", default-features = false }
goblin = { version = "0.4.3", default-features = true } # goblin::Object doesn't work without everything enabled
heapless = { version = "0.7.17", default-features = false }
heck = { version = "0.5.0", default-features = false }
hex-literal = { version = "0.4.1" }
hkdf = { version = "0.12", default-features = false }
hmac = { version = "0.12.1", default-features = false }
hubpack = { version = "0.1.2", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion app/lpc55xpresso/app-sprot.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ name = "lpc55-update-server"
priority = 3
stacksize = 3000
start = true
sections = {bootstate = "usbsram"}
sections = {bootstate = "usbsram", transient_override = "override"}
uses = ["flash_controller", "hash_crypt"]
notifications = ["flash-irq", "hashcrypt-irq"]
interrupts = {"flash_controller.irq" = "flash-irq", "hash_crypt.irq" = "hashcrypt-irq"}
Expand Down
2 changes: 1 addition & 1 deletion app/lpc55xpresso/app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ priority = 3
max-sizes = {flash = 26720, ram = 16704}
stacksize = 8192
start = true
sections = {bootstate = "usbsram"}
sections = {bootstate = "usbsram", transient_override = "override"}
uses = ["flash_controller", "hash_crypt"]
notifications = ["flash-irq", "hashcrypt-irq"]
interrupts = {"flash_controller.irq" = "flash-irq", "hash_crypt.irq" = "hashcrypt-irq"}
Expand Down
2 changes: 1 addition & 1 deletion app/oxide-rot-1/app-dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ name = "lpc55-update-server"
priority = 3
stacksize = 8192
start = true
sections = {bootstate = "usbsram"}
sections = {bootstate = "usbsram", transient_override = "override"}
uses = ["flash_controller", "hash_crypt"]
notifications = ["flash-irq", "hashcrypt-irq"]
interrupts = {"flash_controller.irq" = "flash-irq", "hash_crypt.irq" = "hashcrypt-irq"}
Expand Down
2 changes: 1 addition & 1 deletion app/oxide-rot-1/app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ name = "lpc55-update-server"
priority = 3
stacksize = 8192
start = true
sections = {bootstate = "usbsram"}
sections = {bootstate = "usbsram", transient_override = "override"}
uses = ["flash_controller", "hash_crypt"]
notifications = ["flash-irq", "hashcrypt-irq"]
interrupts = {"flash_controller.irq" = "flash-irq", "hash_crypt.irq" = "hashcrypt-irq"}
Expand Down
2 changes: 1 addition & 1 deletion app/rot-carrier/app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ priority = 3
# TODO size this appropriately
stacksize = 8192
start = true
sections = {bootstate = "usbsram"}
sections = {bootstate = "usbsram", transient_override = "override"}
uses = ["flash_controller", "hash_crypt"]
notifications = ["flash-irq", "hashcrypt-irq"]
interrupts = {"flash_controller.irq" = "flash-irq", "hash_crypt.irq" = "hashcrypt-irq"}
Expand Down
16 changes: 16 additions & 0 deletions chips/lpc55/memory.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ write = true
execute = false
dma = true

[[override]]
name = "a"
address = 0x3003ffe0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use the non-secure alias 0x2003ffe0 ? This better matches what we do for our other addresses

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

size = 32
read = true
write = true
execute = false

[[override]]
name = "b"
address = 0x3003ffe0
size = 32
read = true
write = true
execute = false

# Info about the images loaded into flash and dumped by stage0 into USB SRAM
# for hubris use.
[[usbsram]]
Expand Down
1 change: 1 addition & 0 deletions drv/lpc55-update-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mutable-statics = { path = "../../lib/mutable-statics" }

cfg-if.workspace = true
hubpack.workspace = true
hex-literal.workspace = true
idol-runtime.workspace = true
lpc55-pac.workspace = true
num-traits.workspace = true
Expand Down
47 changes: 45 additions & 2 deletions drv/lpc55-update-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use drv_lpc55_update_api::{
SlotId, SwitchDuration, UpdateTarget, VersionedRotBootInfo,
};
use drv_update_api::UpdateError;
use hex_literal::hex;
use idol_runtime::{
ClientError, Leased, LenLimit, NotificationHandler, RequestError, R, W,
};
Expand All @@ -40,6 +41,10 @@ const PAGE_SIZE: u32 = BYTES_PER_FLASH_PAGE as u32;
#[link_section = ".bootstate"]
static BOOTSTATE: MaybeUninit<[u8; 0x1000]> = MaybeUninit::uninit();

#[used]
#[link_section = ".transient_override"]
static mut TRANSIENT_OVERRIDE: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();

#[derive(Copy, Clone, PartialEq)]
enum UpdateState {
NoUpdate,
Expand Down Expand Up @@ -503,6 +508,12 @@ impl idl::InOrderUpdateImpl for ServerImpl<'_> {
ringbuf_entry!(Trace::State(self.state));
self.next_block = None;
self.fw_cache.fill(0);
// The sequence: [update, set transient preference, update] is legal.
// Clear any stale transient preference before update.
// Stage0 doesn't support transient override.
if component == RotComponent::Hubris {
set_hubris_transient_override(None);
}
Comment on lines +511 to +516
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I see what this is trying to do but I'm not sure this is the right place. This would make [set transient preference, update] quietly not work as expected. I'm also not sure about the example [update, set transient preference, update] being legal but not supported. Can we clarify what the semantics of transient boot preference are supposed to be? Having it be effective for exactly one boot and clearing it on task startup seems like the least confusing semantics.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay I re-read the RFD and double checked the bootleby source, the transient command is supposed to be nuked on boot by bootleby. I'm still not quite sure what makes this a 'stale' choice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not actually stale until some flash has been altered. Given the sequence where SlotId::B is active:

  • prep_image_update SlotId::A
  • flash to hubris-a with version 1
  • switch_default_hubris_image(SlotId::A, SwitchDuration::Once)
  • prep_image_update SlotId::A
  • // abandon or abort the update before flashing anything
  • // reset the RoT
    What image is preferred to boot?

This is a corner case since we normally expect update, set-preference, reset.
I'm thinking that in this case, since the RoT update server saw an intent to boot once into SlotId::A and then saw intent to update A again, that should cancel the boot once directive.

We could wait until the first block of an image is received or that block is written to cancel the boot once directive.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree it's a corner case. Fixing it in this way is a pretty poor developer experience because there's no indication that the boot setting has changed which will frustrate everyone. The transient boot setting should not be cancelled without some indication. We also have the exact same issue with the persistent settings. I get the theory is to avoid booting into a potentially bad image because we're now relying on bootleby to correctly determine that the slot is unbootable but this feels like it causes more problems than it solves. I think this issue of boot settings would be better tracked as a separate issue.

Ok(())
}

Expand Down Expand Up @@ -907,8 +918,11 @@ impl ServerImpl<'_> {
) -> Result<(), RequestError<UpdateError>> {
match duration {
SwitchDuration::Once => {
// TODO deposit command token into buffer
return Err(UpdateError::NotImplemented.into());
// TODO check Rollback policy vs epoch before activating.
// TODO: prep-image-update should clear transient selection.
// e.g. update, activate, update, reboot should not have
// transient boot set.
set_hubris_transient_override(Some(slot));
}
SwitchDuration::Forever => {
// Locate and return the authoritative CFPA flash word number
Expand Down Expand Up @@ -1337,6 +1351,35 @@ fn bootstate() -> Result<RotBootStateV2, HandoffDataLoadError> {
RotBootStateV2::load_from_addr(addr)
}

fn set_transient_override(preference: [u8; 32]) {
// Safety: Data is consumed by Bootleby on next boot.
// There are no concurrent writers possible.
// Calling this function multiple times is ok.
// Bootleby is careful to vet contents before acting.
unsafe {
TRANSIENT_OVERRIDE.write(preference);
}
}

pub fn set_hubris_transient_override(bank: Option<SlotId>) {
// Preference constants are taken from bootleby:src/lib.rs
const PREFER_SLOT_A: [u8; 32] = hex!(
"edb23f2e9b399c3d57695262f29615910ed10c8d9b261bfc2076b8c16c84f66d"
);
const PREFER_SLOT_B: [u8; 32] = hex!(
"70ed2914e6fdeeebbb02763b96da9faa0160b7fc887425f4d45547071d0ce4ba"
);
// Bootleby writes all zeros after reading. We write all ones to reset.
const PREFER_NOTHING: [u8; 32] = [0xffu8; 32];

match bank {
// Do we need a value that says we were here and cleared?
None => set_transient_override(PREFER_NOTHING),
Some(SlotId::A) => set_transient_override(PREFER_SLOT_A),
Some(SlotId::B) => set_transient_override(PREFER_SLOT_B),
}
}

fn round_up_to_flash_page(offset: u32) -> Option<u32> {
offset.checked_next_multiple_of(BYTES_PER_FLASH_PAGE as u32)
}
Expand Down
Loading