diff --git a/Cargo.lock b/Cargo.lock index 0feff764..25c9ffa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1684,12 +1684,16 @@ dependencies = [ "anyhow", "chrono", "clap 3.2.23", + "hif", "humility-cli", "humility-cmd", "humility-core", "humility-doppel", + "humility-hiffy", + "humility-idol", "humility-log", "ipcc-data", + "turin-post-decoder", ] [[package]] @@ -4235,6 +4239,11 @@ dependencies = [ "toml_edit 0.14.4", ] +[[package]] +name = "turin-post-decoder" +version = "0.4.0" +source = "git+https://github.com/oxidecomputer/turin-post-decoder#2ced4872bdedf2a21485b1406565f6d7f97c331b" + [[package]] name = "twox-hash" version = "2.1.1" diff --git a/Cargo.toml b/Cargo.toml index de1d2fda..4973003e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,15 +92,16 @@ hif = { git = "https://github.com/oxidecomputer/hif" } humpty = { git = "https://github.com/oxidecomputer/humpty", version = "0.1.3" } idol = {git = "https://github.com/oxidecomputer/idolatry.git"} idt8a3xxxx = { git = "https://github.com/oxidecomputer/idt8a3xxxx" } +ipcc-data = { git = "https://github.com/oxidecomputer/ipcc-rs" } measurement-token = { git = "https://github.com/oxidecomputer/lpc55_support", default-features = false } pmbus = { git = "https://github.com/oxidecomputer/pmbus" } serialport = { git = "https://github.com/jgallagher/serialport-rs", branch = "illumos-support" } spd = { git = "https://github.com/oxidecomputer/spd" } tlvc = { git = "https://github.com/oxidecomputer/tlvc" } tlvc-text = {git = "https://github.com/oxidecomputer/tlvc"} +turin-post-decoder = { git = "https://github.com/oxidecomputer/turin-post-decoder" } vsc7448-info = { git = "https://github.com/oxidecomputer/vsc7448.git" } vsc7448-types = { git = "https://github.com/oxidecomputer/vsc7448.git" } -ipcc-data = { git = "https://github.com/oxidecomputer/ipcc-rs" } # # We depend on the oxide-stable branch of Oxide's fork of probe-rs to assure diff --git a/README.md b/README.md index e6f06e6a..e6cc7318 100644 --- a/README.md +++ b/README.md @@ -986,7 +986,7 @@ within Humility, use `-L` (`--list-functions`). ### `humility host` `humility host` pretty-prints host state, which is sent to the SP over IPCC. -It is only functional on a Gimlet SP image. +It is only functional on a Gimlet or Cosmo SP image. #### `humility host last-panic` Pretty prints the value of `LAST_HOST_PANIC` @@ -1042,6 +1042,45 @@ humility: reading LAST_HOST_BOOT_FAIL [0; 4096] ``` +#### `humility host cosmo last-post-code` +Pretty-prints the last POST code seen by the sequencer FPGA (Cosmo only) +```console +$ humility host cosmo last-post-code +humility: attached to 0483:374f:002A001C4D46500F20373033 via ST-Link V3 +Bootloader Code: 0xed80000f + Source: ASP TEE + Status: BL_ERR_BOUNDARY_CHECK (0x0f) + Detail: Out of Boundary Condition Reached +``` + +#### `humility host cosmo post-codes` +Pretty-prints all last POST codes seen by the sequencer FPGA (Cosmo only) +```console +$ humility host cosmo post-codes +humility: attached to 0483:374f:002A001C4D46500F20373033 via ST-Link V3 +Bootloader Code: 0xee1000b3 + Source: ASP BL2 + Status: BL_SUCCESS_BYPASS_IDEVID_CHECK (0xb3) + Detail: IDEVID validation failed but bypassed (unsecure) +Bootloader Code: 0xee1000a0 + Source: ASP BL2 + Status: BL_SUCCESS_C_MAIN (0xa0) + Detail: Successfully entered C Main +Bootloader Code: 0xee1000a3 + Source: ASP BL2 + Status: BL_SUCCESS_DETECT_BOOT_MODE (0xa3) + Detail: Boot Mode detected and sent to slaves +Bootloader Code: 0xee1000a2 + Source: ASP BL2 + Status: BL_SUCCESS_DERIVE_HMAC_KEY (0xa2) + Detail: HMAC key derived +Bootloader Code: 0xee1000a2 + Source: ASP BL2 + Status: BL_SUCCESS_DERIVE_HMAC_KEY (0xa2) + Detail: HMAC key derived +# etc... +``` + ### `humility hydrate` diff --git a/cmd/host/Cargo.toml b/cmd/host/Cargo.toml index 2084cf46..5f8069cc 100644 --- a/cmd/host/Cargo.toml +++ b/cmd/host/Cargo.toml @@ -8,12 +8,16 @@ description = "Pretty-printing of host state" [dependencies] anyhow.workspace = true -clap.workspace = true chrono.workspace = true +clap.workspace = true +hif.workspace = true ipcc-data.workspace = true +turin-post-decoder.workspace = true humility.workspace = true -humility-cmd.workspace = true humility-cli.workspace = true +humility-cmd.workspace = true humility-doppel.workspace = true +humility-hiffy.workspace = true +humility-idol.workspace = true humility-log.workspace = true diff --git a/cmd/host/src/lib.rs b/cmd/host/src/lib.rs index 1fb2ef01..6b76ff2f 100644 --- a/cmd/host/src/lib.rs +++ b/cmd/host/src/lib.rs @@ -5,7 +5,7 @@ //! ## `humility host` //! `humility host` pretty-prints host state, which is sent to the SP over IPCC. //! -//! It is only functional on a Gimlet SP image. +//! It is only functional on a Gimlet or Cosmo SP image. //! //! ### `humility host last-panic` //! Pretty prints the value of `LAST_HOST_PANIC` @@ -60,8 +60,47 @@ //! humility: reading LAST_HOST_BOOT_FAIL //! [0; 4096] //! ``` +//! +//! ### `humility host cosmo last-post-code` +//! Pretty-prints the last POST code seen by the sequencer FPGA (Cosmo only) +//! ```console +//! $ humility host cosmo last-post-code +//! humility: attached to 0483:374f:002A001C4D46500F20373033 via ST-Link V3 +//! Bootloader Code: 0xed80000f +//! Source: ASP TEE +//! Status: BL_ERR_BOUNDARY_CHECK (0x0f) +//! Detail: Out of Boundary Condition Reached +//! ``` +//! +//! ### `humility host cosmo post-codes` +//! Pretty-prints all last POST codes seen by the sequencer FPGA (Cosmo only) +//! ```console +//! $ humility host cosmo post-codes +//! humility: attached to 0483:374f:002A001C4D46500F20373033 via ST-Link V3 +//! Bootloader Code: 0xee1000b3 +//! Source: ASP BL2 +//! Status: BL_SUCCESS_BYPASS_IDEVID_CHECK (0xb3) +//! Detail: IDEVID validation failed but bypassed (unsecure) +//! Bootloader Code: 0xee1000a0 +//! Source: ASP BL2 +//! Status: BL_SUCCESS_C_MAIN (0xa0) +//! Detail: Successfully entered C Main +//! Bootloader Code: 0xee1000a3 +//! Source: ASP BL2 +//! Status: BL_SUCCESS_DETECT_BOOT_MODE (0xa3) +//! Detail: Boot Mode detected and sent to slaves +//! Bootloader Code: 0xee1000a2 +//! Source: ASP BL2 +//! Status: BL_SUCCESS_DERIVE_HMAC_KEY (0xa2) +//! Detail: HMAC key derived +//! Bootloader Code: 0xee1000a2 +//! Source: ASP BL2 +//! Status: BL_SUCCESS_DERIVE_HMAC_KEY (0xa2) +//! Detail: HMAC key derived +//! # etc... +//! ``` -use anyhow::{Result, anyhow}; +use anyhow::{Result, anyhow, bail}; use chrono::DateTime; use clap::{CommandFactory, Parser}; @@ -69,6 +108,8 @@ use humility::{core::Core, hubris::HubrisArchive, reflect, reflect::Load}; use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; use humility_doppel as doppel; +use humility_hiffy::HiffyContext; +use humility_idol::HubrisIdol; use humility_log::msg; #[derive(Parser, Debug)] @@ -77,6 +118,25 @@ enum HostCommand { BootFail, /// Print the last host panic LastPanic, + /// Cosmo-specific host commands + Cosmo { + #[clap(subcommand)] + cmd: CosmoHostCommand, + }, +} + +#[derive(Parser, Debug)] +enum CosmoHostCommand { + /// Prints the most recent POST code + LastPostCode { + #[clap(long)] + raw: bool, + }, + /// Dumps the POST code buffer + PostCodes { + #[clap(long)] + raw: bool, + }, } #[derive(Parser, Debug)] @@ -285,6 +345,158 @@ fn print_panic(d: Vec) -> Result<()> { Ok(()) } +/// Print a warning message if the archive is not for a `cosmo` board +fn check_post_code_target(hubris: &HubrisArchive) { + if !hubris.manifest.board.as_ref().is_some_and(|b| b.contains("cosmo")) { + humility::warn!( + "POST code buffer is only present on 'cosmo' hardware{}; \ + hiffy may fail and time out", + if let Some(board) = &hubris.manifest.board { + format!(" but this is a '{board}'") + } else { + String::new() + } + ) + } +} + +fn host_post_codes( + hubris: &HubrisArchive, + core: &mut dyn Core, + raw: bool, +) -> Result<()> { + check_post_code_target(hubris); + use hif::*; + + let mut context = HiffyContext::new(hubris, core, 5000)?; + let op = hubris.get_idol_command("Sequencer.post_code_buffer_len")?; + let value = humility_hiffy::hiffy_call( + hubris, + core, + &mut context, + &op, + &[], + None, + None, + )?; + let Ok(reflect::Value::Base(reflect::Base::U32(count))) = value else { + bail!( + "Got bad value from post_code_buffer_len: \ + expected U32, got {value:?}" + ); + }; + + let op = hubris.get_idol_command("Sequencer.get_post_code")?; + let handle_value = |v| { + let Ok(reflect::Value::Base(reflect::Base::U32(v))) = v else { + bail!("Got bad value from get_post_code: expected U32, got {v:?}"); + }; + if raw { + println!("{v:08x}"); + } else { + let decoded = turin_post_decoder::decode(v); + let detail = decoded.lines().join("\n"); + println!("{detail}"); + } + Ok(()) + }; + + // For network-attached systems, function calls are cheap and we can just + // spam them. For debugger-attached systems, we'll want to run a HIF loop + // to avoid dispatch overhead. + if core.is_net() { + for index in 0..count { + let value = humility_hiffy::hiffy_call( + hubris, + core, + &mut context, + &op, + &[("index", humility_idol::IdolArgument::Scalar(index as u64))], + None, + None, + )?; + handle_value(value)?; + } + } else { + let send = context.get_function("Send", 4)?; + let ret_size = hubris.typesize(op.ok)? as u32; + assert_eq!(ret_size, 4); + + // Each returned value is a FunctionResult::Success(&[..]), which + // encodes as 6 bytes: 1 byte variant tag, 1 byte slice length, 4 bytes + // of u32. + let max_chunk_size = context.rstack_size() / 6; + + // Write a little program to read all of the post codes + for start in (0..count).step_by(max_chunk_size) { + let end = (start + max_chunk_size as u32).min(count); + let label = Target(0); + let mut ops = vec![ + Op::Push32(op.task.task()), // task id + Op::Push16(op.code), // opcode + Op::Push32(start), // Starting index + Op::Push32(0), // Comparison target (dummy) + Op::Label(label), // loop start + ]; + { + // block representing the hiffy loop + ops.push(Op::Drop); // Drop comparison target + + // Expand u32 -> [u8; 4], since that's expected by `send` + ops.push(Op::Expand32); + ops.push(Op::Push(4)); // Payload size + ops.push(Op::Push32(ret_size)); // Return size + ops.push(Op::Call(send.id)); + ops.push(Op::DropN(2)); // Drop payload and return size + ops.push(Op::Collect32); + ops.push(Op::Push(1)); // Increment by four + ops.push(Op::Add); // index += 1 + ops.push(Op::Push32(end)); // Comparison target + ops.push(Op::BranchGreaterThan(label)); // Jump to loop start + } + ops.push(Op::DropN(4)); // Cleanup + ops.push(Op::Done); // Finish + + for r in context.run(core, ops.as_slice(), None)? { + let v = humility_hiffy::hiffy_decode(hubris, &op, r)?; + handle_value(v)?; + } + } + } + Ok(()) +} + +fn host_last_post_code( + hubris: &HubrisArchive, + core: &mut dyn Core, + raw: bool, +) -> Result<()> { + check_post_code_target(hubris); + + let mut context = HiffyContext::new(hubris, core, 5000)?; + let op = hubris.get_idol_command("Sequencer.last_post_code")?; + let value = humility_hiffy::hiffy_call( + hubris, + core, + &mut context, + &op, + &[], + None, + None, + )?; + let Ok(reflect::Value::Base(reflect::Base::U32(v))) = value else { + bail!("Got bad value from last_post_code: expected U32, got {value:?}"); + }; + if raw { + println!("{v:08x}"); + } else { + let decoded = turin_post_decoder::decode(v); + let detail = decoded.lines().join("\n"); + println!("{detail}"); + } + Ok(()) +} + fn host(context: &mut ExecutionContext) -> Result<()> { let Subcommand::Other(subargs) = context.cli.cmd.as_ref().unwrap(); let subargs = HostArgs::try_parse_from(subargs)?; @@ -294,6 +506,14 @@ fn host(context: &mut ExecutionContext) -> Result<()> { match subargs.cmd { HostCommand::BootFail => host_boot_fail(hubris, core), HostCommand::LastPanic => host_last_panic(hubris, core), + HostCommand::Cosmo { cmd } => match cmd { + CosmoHostCommand::PostCodes { raw } => { + host_post_codes(hubris, core, raw) + } + CosmoHostCommand::LastPostCode { raw } => { + host_last_post_code(hubris, core, raw) + } + }, } }