Skip to content

Commit 86d3a0a

Browse files
authored
Pass measurement token between RoT and SP (#2138)
This PR implements RFD 568: SP resets and measurement (https://rfd.shared.oxide.computer/rfd/0568) The goal of the RFD is to solve the cold boot problem, where the RoT takes several seconds to start, but must measure the SP at reset. For reasons described in the RFD, we choose to have the SP reset itself at startup, giving the RoT a chance to catch it. More specifically: - The RoT can detect an SP reset, hold it in reset, and measure it. After measuring it, the RoT deposits a token at a particular location in the SP's RAM. - When the SP boots, it checks for this token. If the token is present, then it continues booting. Otherwise, it sleeps for a little while, then resets itself. This occurs a limited number of times; if we exceed a maximum retry count, then the SP continues to boot, feeling vaguely guilty about not having been measured. Together, these changes mean that if we power on a system, the SP will reset itself about about 12 times over the course of 2.5 seconds (with 200ms pauses in between) before the RoT boots up. It will then be measured by the RoT, and continue to boot normally. If the RoT isn't present, the SP stops resetting after about 4 seconds (20 resets). There are a bunch of changes to make this possible! - The `kernel` now knows about `extern-region`s and generates `BASE` / `END` symbols in its linker script. This lets us specify a `handoff` region from which it reads the tokens. Unfortunately, this address has to be hard-coded in the RoT, since the RoT doesn't have a way of determining its location from the SP. - I refactored `lpc55-swd` to extract `reset_into_debug_halt` into a standalone function, because it's now used for both measurements and to reset the SP before depositing the token into memory. There are various other cleanups here; in particular, I removed the stateful `Undo` bitfield in favor of smaller functions which cleaning up before returning.
1 parent 437c053 commit 86d3a0a

File tree

16 files changed

+377
-217
lines changed

16 files changed

+377
-217
lines changed

Cargo.lock

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ hubtools = { git = "https://github.com/oxidecomputer/hubtools", default-features
151151
idol = { git = "https://github.com/oxidecomputer/idolatry.git", default-features = false }
152152
idol-runtime = { git = "https://github.com/oxidecomputer/idolatry.git", default-features = false }
153153
lpc55_sign = { git = "https://github.com/oxidecomputer/lpc55_support", default-features = false }
154+
measurement-token = { git = "https://github.com/oxidecomputer/lpc55_support", default-features = false }
154155
ordered-toml = { git = "https://github.com/oxidecomputer/ordered-toml", default-features = false }
155156
pmbus = { git = "https://github.com/oxidecomputer/pmbus", default-features = false }
156157
salty = { version = "0.3", default-features = false }

app/grapefruit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "0.1.0"
66

77
[features]
88
dump = ["kern/dump"]
9+
measurement-handoff = ["drv-stm32h7-startup/measurement-handoff"]
910

1011
[dependencies]
1112
cortex-m = { workspace = true }

app/grapefruit/base.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ register-map = "../../drv/spartan7-loader/grapefruit/gfruit_top.json"
1414
[kernel]
1515
name = "grapefruit"
1616
requires = {flash = 32768, ram = 8192}
17-
features = ["dump"]
17+
features = ["dump", "measurement-handoff"]
18+
extern-regions = ["dtcm"]
1819

1920
[caboose]
2021
tasks = ["control_plane_agent"]

build/kconfig/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ use std::collections::{BTreeMap, BTreeSet};
88
/// Application configuration passed into the kernel build.
99
#[derive(Clone, Debug, Serialize, Deserialize)]
1010
pub struct KernelConfig {
11+
/// Features enabled in the kernel
12+
pub features: Vec<String>,
13+
14+
/// External regions used in the kernel
15+
pub extern_regions: BTreeMap<String, std::ops::Range<u32>>,
16+
1117
/// Tasks in the app image. The order of tasks is significant.
1218
pub tasks: Vec<TaskConfig>,
1319

build/xtask/src/config.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,10 +581,27 @@ impl Config {
581581
task: &str,
582582
image_name: &str,
583583
) -> Result<IndexMap<String, Range<u32>>> {
584-
self.tasks
584+
let extern_regions = &self
585+
.tasks
585586
.get(task)
586587
.ok_or_else(|| anyhow!("no such task {task}"))?
587-
.extern_regions
588+
.extern_regions;
589+
self.get_extern_regions(extern_regions, image_name)
590+
}
591+
592+
pub fn kernel_extern_regions(
593+
&self,
594+
image_name: &str,
595+
) -> Result<IndexMap<String, Range<u32>>> {
596+
self.get_extern_regions(&self.kernel.extern_regions, image_name)
597+
}
598+
599+
fn get_extern_regions(
600+
&self,
601+
extern_regions: &Vec<String>,
602+
image_name: &str,
603+
) -> Result<IndexMap<String, Range<u32>>> {
604+
extern_regions
588605
.iter()
589606
.map(|r| {
590607
let mut regions = self
@@ -710,6 +727,8 @@ pub struct Kernel {
710727
pub features: Vec<String>,
711728
#[serde(default)]
712729
pub no_default_features: bool,
730+
#[serde(default)]
731+
pub extern_regions: Vec<String>,
713732
}
714733

715734
fn default_name() -> String {

build/xtask/src/dist.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ pub const DEFAULT_KERNEL_STACK: u32 = 1024;
4242
/// that generates the Humility binary necessary for Hubris's CI has run.
4343
/// Once that binary is in place, you should be able to bump this version
4444
/// without breaking CI.
45-
const HUBRIS_ARCHIVE_VERSION: u32 = 9;
45+
///
46+
/// # Changelog
47+
/// Version 10 requires Humility to be aware of the `handoff` kernel feature,
48+
/// which lets the RoT inform the SP when measurements have been taken. If
49+
/// Humility is unaware of this feature, the SP will reset itself repeatedly,
50+
/// which interferes with subsequent programming of auxiliary flash.
51+
const HUBRIS_ARCHIVE_VERSION: u32 = 10;
4652

4753
/// `PackageConfig` contains a bundle of data that's commonly used when
4854
/// building a full app image, grouped together to avoid passing a bunch
@@ -445,6 +451,18 @@ pub fn package(
445451
}
446452
}
447453
}
454+
// Same check for the kernel. This may be overly conservative, because
455+
// the kernel is special, but we can always make it less strict later.
456+
for r in &cfg.toml.kernel.extern_regions {
457+
if let Some(v) = alloc_regions.get(r) {
458+
bail!(
459+
"cannot use region '{r}' as extern region in \
460+
the kernel because it's used as a normal region by \
461+
[{}]",
462+
v.join(", ")
463+
);
464+
}
465+
}
448466

449467
let mut extern_regions = MultiMap::new();
450468
for (task_name, task) in cfg.toml.tasks.iter() {
@@ -1529,11 +1547,13 @@ fn build_kernel(
15291547
kconfig.hash(&mut image_id);
15301548
allocs.hash(&mut image_id);
15311549

1550+
let extern_regions = cfg.toml.kernel_extern_regions(image_name)?;
15321551
generate_kernel_linker_script(
15331552
"memory.x",
15341553
&allocs.kernel,
15351554
cfg.toml.kernel.stacksize.unwrap_or(DEFAULT_KERNEL_STACK),
15361555
&cfg.toml.all_regions("flash".to_string())?,
1556+
&extern_regions,
15371557
image_name,
15381558
)?;
15391559

@@ -1812,7 +1832,6 @@ fn generate_task_linker_script(
18121832

18131833
fn append_image_names(
18141834
linkscr: &mut std::fs::File,
1815-
18161835
images: &IndexMap<String, Range<u32>>,
18171836
image_name: &str,
18181837
) -> Result<()> {
@@ -1882,6 +1901,7 @@ fn generate_kernel_linker_script(
18821901
map: &BTreeMap<String, Range<u32>>,
18831902
stacksize: u32,
18841903
images: &IndexMap<String, Range<u32>>,
1904+
extern_regions: &IndexMap<String, Range<u32>>,
18851905
image_name: &str,
18861906
) -> Result<()> {
18871907
// Put the linker script somewhere the linker can find it
@@ -1948,6 +1968,7 @@ fn generate_kernel_linker_script(
19481968
.unwrap();
19491969

19501970
append_image_names(&mut linkscr, images, image_name)?;
1971+
append_extern_regions(&mut linkscr, extern_regions)?;
19511972
Ok(())
19521973
}
19531974

@@ -2859,6 +2880,11 @@ pub fn make_kconfig(
28592880
flat_shared.retain(|name, _v| used_shared_regions.contains(name.as_str()));
28602881

28612882
Ok(build_kconfig::KernelConfig {
2883+
features: toml.kernel.features.clone(),
2884+
extern_regions: toml
2885+
.kernel_extern_regions(image_name)?
2886+
.into_iter()
2887+
.collect(),
28622888
irqs,
28632889
tasks,
28642890
shared_regions: flat_shared,

chips/stm32h7/memory-large.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ read = true
1414
write = true
1515
execute = false # let's assume XN until proven otherwise
1616

17+
# Data tightly-coupled memory
18+
#
19+
# This region is used in production images to pass a token from the RoT to the
20+
# SP indicating that measurement has happened. Using this region (and specific
21+
# token values) is hard-coded in the `measurement-token` crate, which is shared
22+
# between Hubris and Humility.
23+
[[dtcm]]
24+
address = 0x20000000
25+
size = 131072
26+
read = true
27+
write = true
28+
execute = false
29+
1730
# Network buffers are placed in sram1, which is directly accessible by the
1831
# Ethernet MAC. We limit this use of sram1 to 64 KiB, and preserve the
1932
# remainder to be used for disjoint purposes (e.g., as an external region).

drv/lpc55-swd/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ zerocopy = { workspace = true }
1313
zerocopy-derive = { workspace = true }
1414
bitflags = { workspace = true }
1515
static_assertions = { workspace = true }
16+
measurement-token = { workspace = true }
1617

1718
attest-api = { path = "../../task/attest-api" }
1819
drv-lpc55-gpio-api = { path = "../lpc55-gpio-api" }

drv/lpc55-swd/src/armv7debug.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,6 @@ pub trait DpAddressable {
1515
const ADDRESS: u32;
1616
}
1717

18-
// For keeping track of unwinding debug actions
19-
bitflags! {
20-
#[derive(PartialEq, Eq, Copy, Clone)]
21-
pub struct Undo: u8 {
22-
// Need self.swd_finish()
23-
const SWD = 1 << 0;
24-
// Need self.sp_reset_leave(true)
25-
const RESET = 1 << 1;
26-
// Need DEMCR = 0
27-
const VC_CORERESET = 1 << 2;
28-
// Need to clear debug enable.
29-
const DEBUGEN = 1 << 3;
30-
}
31-
}
32-
3318
// RW 0x00000000 Debug Halting Control and Status Register
3419
// Some DHCSR bits have different read vs. write meanings
3520
// Specifically, the MAGIC value enables writing other control bits

0 commit comments

Comments
 (0)