Skip to content
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
27 changes: 27 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"humility-arch-cortex",
"humility-auxflash",
"humility-bin",
"humility-caboose",
"humility-cli",
"humility-core",
Comment thread
mkeeter marked this conversation as resolved.
"humility-doppel",
Expand All @@ -26,6 +27,7 @@ members = [
"humility-validate",
"humility-vpd-lib",
"cmd/auxflash",
"cmd/caboose",
"cmd/console-proxy",
"cmd/counters",
"cmd/dashboard",
Expand Down Expand Up @@ -116,6 +118,7 @@ humility = { path = "./humility-core", package = "humility-core" }
humility-arch-arm = { path = "./humility-arch-arm" }
humility-auxflash = { path = "./humility-auxflash" }
humility-cortex = { path = "./humility-arch-cortex" }
humility-caboose = { path = "./humility-caboose" }
humility-cli = { path = "./humility-cli", default-features = false }
humility-doppel = { path = "./humility-doppel" }
humility-dump-agent = { path = "./humility-dump-agent" }
Expand All @@ -134,6 +137,7 @@ humility-stack = { path = "./humility-stack" }
humility-validate = { path = "./humility-validate" }
humility-vpd-lib = { path = "./humility-vpd-lib" }
cmd-auxflash = { path = "./cmd/auxflash", package = "humility-cmd-auxflash" }
cmd-caboose = { path = "./cmd/caboose", package = "humility-cmd-caboose" }
cmd-console-proxy = { path = "./cmd/console-proxy", package = "humility-cmd-console-proxy" }
cmd-counters = { path = "./cmd/counters", package = "humility-cmd-counters" }
cmd-dashboard = { path = "./cmd/dashboard", package = "humility-cmd-dashboard" }
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ a specified target. (In the above example, one could execute `humility
## Commands

- [humility auxflash](#humility-auxflash): manipulate auxiliary flash
- [humility caboose](#humility-caboose): read from the caboose
- [humility console-proxy](#humility-console-proxy): SP/host console uart proxy
- [humility counters](#humility-counters): display event counters
- [humility dashboard](#humility-dashboard): dashboard for Hubris sensor data
Expand Down Expand Up @@ -295,6 +296,11 @@ This subcommand should be rarely used; `humility flash` will automatically
program auxiliary flash when needed.


### `humility caboose`

Tools to read from the caboose (without an archive)


### `humility console-proxy`

Act as a proxy for the host serial console when it is jumpered to the SP.
Expand Down
14 changes: 14 additions & 0 deletions cmd/caboose/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "humility-cmd-caboose"
version = "0.1.0"
edition.workspace = true
description = "read from the caboose"

[dependencies]
anyhow.workspace = true
clap.workspace = true
tlvc-text.workspace = true

humility-caboose.workspace = true
humility-cli.workspace = true
humility-probes-core.workspace = true
51 changes: 51 additions & 0 deletions cmd/caboose/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! ## `humility caboose`
//!
//! Tools to read from the caboose (without an archive)

use anyhow::Result;
use clap::Parser;
use humility_cli::{ExecutionContext, humility_cmd};

#[derive(Parser, Debug)]
#[clap(name = "caboose", about = env!("CARGO_PKG_DESCRIPTION"))]
pub struct CabooseArgs {
#[clap(subcommand)]
cmd: CabooseCommand,
}

#[derive(Parser, Debug)]
enum CabooseCommand {
Read,
}

fn caboose(subargs: CabooseArgs, context: &mut ExecutionContext) -> Result<()> {
let log = context.log();
match subargs.cmd {
CabooseCommand::Read => {
let mut probe = context.cli.attach_probe(None)?;
let mut t = humility_caboose::read_tlvc_caboose(&mut probe, log)?;

// Strip raw bytes from the end, for pretty-printing
if let Some(tlvc_text::Piece::Bytes(bs)) = t.last()
&& bs.iter().all(|c| *c == 0xFF)
{
t.pop();
}

if t.is_empty() {
panic!("caboose is empty");
}

let mut text = vec![];
tlvc_text::save(&mut text, &t).unwrap();
println!("{}", std::str::from_utf8(&text).unwrap());
Ok(())
Comment thread
mkeeter marked this conversation as resolved.
}
}
}

humility_cmd!(CabooseArgs, caboose);
1 change: 1 addition & 0 deletions humility-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ humility = { workspace = true }
humility-cortex = { workspace = true }
humility-cli = { workspace = true, default-features = false }
cmd-auxflash = { workspace = true }
cmd-caboose = { workspace = true }
cmd-console-proxy = { workspace = true }
cmd-counters = { workspace = true }
cmd-dashboard = { workspace = true }
Expand Down
16 changes: 16 additions & 0 deletions humility-caboose/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "humility-caboose"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true

[dependencies]
anyhow.workspace = true
thiserror.workspace = true
tlvc.workspace = true
tlvc-text.workspace = true

humility.workspace = true
humility-arch-arm.workspace = true
humility-doppel.workspace = true
humility-probes-core.workspace = true
170 changes: 170 additions & 0 deletions humility-caboose/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Library to get [caboose](https://hubris.oxide.computer/reference/#caboose)
//! information from an attached system
//!
//! The main entry point is [`read_tlvc_caboose`].
#![warn(missing_docs)]
use anyhow::Result;
Comment thread
mkeeter marked this conversation as resolved.

use humility::{
core::Core,
log::{Logger, info},
};
use humility_doppel::{
CABOOSE_MAGIC, POSSIBLE_HEADER_MAGIC, POSSIBLE_IMAGE_HEADER_OFFSETS,
};

/// Error returned when trying to read a caboose
#[derive(Debug, thiserror::Error)]
pub enum CabooseError {
/// Failed when calling [`Core::halt`]
#[error("could not halt core")]
CouldNotHalt(#[source] anyhow::Error),

/// Failed when calling [`Core::read_reg`] on the `PC` register
#[error("could not read PC")]
CouldNotReadPc(#[source] anyhow::Error),

/// Failed when calling [`Core::run`]
#[error("could not run core")]
CouldNotRun(#[source] anyhow::Error),

/// Program counter does not match any of our expected ranges
#[error("could not find image start point based on program counter {0:#x}")]
UnknownPc(u32),

/// Flash read failed
#[error("flash read failed")]
FlashReadFailed(#[source] anyhow::Error),

/// Failed to find [`POSSIBLE_HEADER_MAGIC`] at any expected offset
#[error("failed to find header magic with base address {0:#x}")]
MissingMagic(u32),

/// Could not compute caboose offset
#[error("could not compute caboose offset; it is probably missing")]
MissingCaboose,

/// Found a magic value which is not [`CABOOSE_MAGIC`]
#[error("bad caboose magic: expected {CABOOSE_MAGIC:#x}, got {actual:#x}")]
BadCabooseMagic {
/// Magic value that was found in the system
actual: u32,
},

/// Caboose exceeds [`humility::core::CORE_MAX_READSIZE`]
#[error(
"caboose size {caboose_size} exceeds max read size {}",
humility::core::CORE_MAX_READSIZE
)]
GiganticCaboose {
/// Actual size of the caboose
caboose_size: usize,
},

/// Forwarded error from the TLVC library
#[error("tlvc error: {0}")]
TlvcError(String), // tlvc::Error does not implement the `Error` trait
}

/// Reads a TLVC-encoded caboose from an attached system
pub fn read_tlvc_caboose(
core: &mut humility_probes_core::ProbeCore,
log: &Logger,
) -> Result<Vec<tlvc_text::Piece>, CabooseError> {
// We'll get the program counter to estimate where we should try to read the
// image header, since we don't necessarily have our chip info here.
core.halt().map_err(CabooseError::CouldNotHalt)?;
let pc = core
.read_reg(humility_arch_arm::ARMRegister::PC)
.map_err(CabooseError::CouldNotReadPc)?;
core.run().map_err(CabooseError::CouldNotRun)?;

// Find the flash base by looking at the program counter
//
// This assumes that we're executing from flash, which is true for all
// systems that we care about.
let base = if (0x10000..0x50000).contains(&pc) {
0x10000
} else if (0x50000..0x90000).contains(&pc) {
0x50000
} else if (0x08000000..0x08100000).contains(&pc) {
0x08000000
} else {
return Err(CabooseError::UnknownPc(pc));
};
info!(log, "found flash base at {base:#x} (pc at {pc:#x})");

// Find the image header by looking for the appropriate magic word
let mut found_header = None;
for header_offset in POSSIBLE_IMAGE_HEADER_OFFSETS {
let header_magic = core
.read_word_32(base + header_offset)
.map_err(CabooseError::FlashReadFailed)?;
if POSSIBLE_HEADER_MAGIC.contains(&header_magic) {
found_header = Some(header_offset);
info!(log, "found image header at {:#x}", base + header_offset);
break;
}
}
let Some(header_offset) = found_header else {
return Err(CabooseError::MissingMagic(base));
};

// Check that the caboose exists and is valid
let image_size_addr = base
.checked_add(header_offset)
.and_then(|b| b.checked_add(4))
.ok_or(CabooseError::MissingCaboose)?;
let image_size = core
.read_word_32(image_size_addr)
.map_err(CabooseError::FlashReadFailed)?;

let caboose_size_addr = base
.checked_add(image_size)
.and_then(|b| b.checked_sub(4))
.ok_or(CabooseError::MissingCaboose)?;
let caboose_size = core
.read_word_32(caboose_size_addr)
.map_err(CabooseError::FlashReadFailed)?;

let caboose_magic_addr = (base + image_size)
.checked_sub(caboose_size)
.ok_or(CabooseError::MissingCaboose)?;
let caboose_magic = core
.read_word_32(caboose_magic_addr)
.map_err(CabooseError::FlashReadFailed)?;
if caboose_magic != CABOOSE_MAGIC {
return Err(CabooseError::BadCabooseMagic { actual: caboose_magic });
}

// Compute start and end for the raw caboose range (including magic and len)
let raw_caboose_start = base + image_size - caboose_size;
let raw_caboose_end = base + image_size;
info!(
log,
"found caboose at {:#x}..{:#x}", raw_caboose_start, raw_caboose_end,
);

// The caboose data range skips the magic word and length word
let caboose_data_range = raw_caboose_start + 4..raw_caboose_end - 4;
let caboose_data_size = caboose_data_range.len();

if caboose_data_size > humility::core::CORE_MAX_READSIZE {
return Err(CabooseError::GiganticCaboose {
caboose_size: caboose_data_size,
});
}

// Read the whole caboose into memory
let mut caboose_data = vec![0u8; caboose_data_size];
core.read_8(caboose_data_range.start, &mut caboose_data)
.map_err(CabooseError::FlashReadFailed)?;

let reader = tlvc::TlvcReader::begin(caboose_data.as_slice())
.map_err(|e| CabooseError::TlvcError(format!("{e:?}")))?;
Ok(tlvc_text::dump(reader))
}
17 changes: 17 additions & 0 deletions humility-doppel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ use zerocopy::{
Immutable, IntoBytes, KnownLayout, LittleEndian, U16, U32, U64,
};

/// Magic value when reading the caboose
pub const CABOOSE_MAGIC: u32 = 0xCAB0_005E;

/// Value for [`ImageHeader::magic`]
///
/// There are two generations of value, but they have the same header layout.
pub const POSSIBLE_HEADER_MAGIC: [u32; 2] = [0x15356637, 0x64_CE_D6_CA];

/// Possible offsets for the image header relative to the beginning of flash
///
/// The exact offset depends on MCU and versions of the PAC crates.
///
/// - 0xbc and 0xc0 are possible values for the STM32G0
/// - 0x298 is for the STM32H7
/// - 0x130 is for the LPC55
pub const POSSIBLE_IMAGE_HEADER_OFFSETS: [u32; 4] = [0xbc, 0xc0, 0x130, 0x298];

#[derive(Copy, Clone, Debug, Eq, PartialEq, Load)]
pub struct TaskDesc {
pub entry_point: u32,
Expand Down
Loading