From 6c79629dc31fb77947b43bec4868c9cd6699f639 Mon Sep 17 00:00:00 2001 From: "Cliff L. Biffle" Date: Mon, 29 Jan 2024 12:24:27 -0800 Subject: [PATCH] gimlet inspector command: first version This only does hex dumping of the register contents, which requires the user to manually decode the bits -- but since we don't yet have pretty printing code that consumes the register layout JSON, I thought I'd try to get this landed while I did the rest. --- Cargo.lock | 28 ++++++++ Cargo.toml | 5 ++ README.md | 13 ++++ cmd/gimlet/Cargo.toml | 21 ++++++ cmd/gimlet/src/lib.rs | 149 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 cmd/gimlet/Cargo.toml create mode 100644 cmd/gimlet/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c92a4b065..4ce0f1814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -925,6 +925,15 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gimlet-inspector-protocol" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/gimlet-inspector-protocol#030004d082f4ec70e33cbc00886096b15ef3c2cc" +dependencies = [ + "hubpack", + "serde", +] + [[package]] name = "gimli" version = "0.22.0" @@ -1123,6 +1132,7 @@ dependencies = [ "humility-cmd-extract", "humility-cmd-flash", "humility-cmd-gdb", + "humility-cmd-gimlet", "humility-cmd-gpio", "humility-cmd-hash", "humility-cmd-hiffy", @@ -1439,6 +1449,24 @@ dependencies = [ "tempfile", ] +[[package]] +name = "humility-cmd-gimlet" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "colored", + "gimlet-inspector-protocol", + "hubpack", + "humility-cli", + "humility-cmd", + "humility-core", + "indexmap", + "parse_int", + "serde", + "zerocopy", +] + [[package]] name = "humility-cmd-gpio" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a3d2a5069..dfd6bc90a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "cmd/extract", "cmd/flash", "cmd/gdb", + "cmd/gimlet", "cmd/gpio", "cmd/hash", "cmd/hiffy", @@ -101,6 +102,7 @@ members = [ [workspace.dependencies] # `git`-based deps +gimlet-inspector-protocol = { git = "https://github.com/oxidecomputer/gimlet-inspector-protocol" } 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"} @@ -157,6 +159,7 @@ cmd-exec = { path = "./cmd/exec", package = "humility-cmd-exec" } cmd-extract = { path = "./cmd/extract", package = "humility-cmd-extract" } cmd-flash = { path = "./cmd/flash", package = "humility-cmd-flash" } cmd-gdb = { path = "./cmd/gdb", package = "humility-cmd-gdb" } +cmd-gimlet = { path = "./cmd/gimlet", package = "humility-cmd-gimlet" } cmd-gpio = { path = "./cmd/gpio", package = "humility-cmd-gpio" } cmd-hash = { path = "./cmd/hash", package = "humility-cmd-hash" } cmd-hiffy = { path = "./cmd/hiffy", package = "humility-cmd-hiffy" } @@ -235,6 +238,7 @@ parse_int = "0.4.0" paste = "0.1" path-slash = "0.1.4" postcard = "0.7.0" +pretty-hex = "0.4" proc-macro2 = "1.0" quote = "1.0" rand = "0.8" @@ -301,6 +305,7 @@ cmd-exec = { workspace = true } cmd-extract = { workspace = true } cmd-flash = { workspace = true } cmd-gdb = { workspace = true } +cmd-gimlet = { workspace = true } cmd-gpio = { workspace = true } cmd-hash = { workspace = true } cmd-host = { workspace = true } diff --git a/README.md b/README.md index 571ae3490..6fb8625ba 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,7 @@ a specified target. (In the above example, one could execute `humility - [humility extract](#humility-extract): extract all or part of a Hubris archive - [humility flash](#humility-flash): flash archive onto attached device - [humility gdb](#humility-gdb): Attach to a running system using GDB +- [humility gimlet](#humility-gimlet): Gimlet-specific diagnostic commands - [humility gpio](#humility-gpio): GPIO pin manipulation - [humility hash](#humility-hash): Access to the HASH block - [humility hiffy](#humility-hiffy): manipulate HIF execution @@ -643,6 +644,18 @@ app id, then again to run the console). +### `humility gimlet` + +`humility gimlet` contacts a (recent firmware version) Gimlet over the +management network and extracts certain live diagnostic information. + +```console +$ humility gimlet --ip fe80::0c1d:9aff:fe64:b8c2%en0 read-seq-regs +``` + +For a complete list of subcommands, use `humility gimlet --help`. + + ### `humility gpio` `humility gpio` allows for GPIO pins to be set, reset, queried or diff --git a/cmd/gimlet/Cargo.toml b/cmd/gimlet/Cargo.toml new file mode 100644 index 000000000..7f9490248 --- /dev/null +++ b/cmd/gimlet/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "humility-cmd-gimlet" +version = "0.1.0" +edition = "2021" +description = "Gimlet-specific diagnostic commands" + +[dependencies] +humility.workspace = true +humility-cmd.workspace = true +humility-cli.workspace = true + +hubpack.workspace = true +gimlet-inspector-protocol.workspace = true + +anyhow.workspace = true +clap.workspace = true +colored.workspace = true +indexmap.workspace = true +parse_int.workspace = true +serde.workspace = true +zerocopy.workspace = true diff --git a/cmd/gimlet/src/lib.rs b/cmd/gimlet/src/lib.rs new file mode 100644 index 000000000..b321523e4 --- /dev/null +++ b/cmd/gimlet/src/lib.rs @@ -0,0 +1,149 @@ +// 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 gimlet` +//! +//! `humility gimlet` contacts a (recent firmware version) Gimlet over the +//! management network and extracts certain live diagnostic information. +//! +//! ```console +//! $ humility gimlet --ip fe80::0c1d:9aff:fe64:b8c2%en0 read-seq-regs +//! ``` +//! +//! For a complete list of subcommands, use `humility gimlet --help`. + +use std::net::{SocketAddrV6, UdpSocket}; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use clap::{ArgGroup, IntoApp, Parser}; +use humility::net::decode_iface; +use humility_cli::{ExecutionContext, Subcommand}; +use humility_cmd::{Archive, Command, CommandKind, Dumper}; + +/// This is defined in the gimlet TOML. +const HARDCODED_PORT: u16 = 23547; + +#[derive(Parser, Debug)] +#[clap( + name = "gimlet", about = env!("CARGO_PKG_DESCRIPTION"), + group = ArgGroup::new("target").multiple(false) +)] +struct Args { + /// How long to wait for a response from the target, in milliseconds. + #[clap( + long, short = 'T', default_value_t = 2000, value_name = "ms", + parse(try_from_str = parse_int::parse) + )] + timeout: u64, + + /// UDP port to contact on the target. + #[clap(long, default_value_t = HARDCODED_PORT)] + port: u16, + + /// IPv6 address, e.g. `fe80::0c1d:9aff:fe64:b8c2%en0` + #[clap( + long, + env = "HUMILITY_RPC_IP", + group = "target", + use_value_delimiter = true + )] + ip: String, + + #[clap(subcommand)] + cmd: SubCmd, +} + +#[derive(Parser, Debug)] +enum SubCmd { + /// Read the current contents of the sequencer FPGA registers. + ReadSeqRegs, +} + +pub struct Client { + socket: UdpSocket, + // Allocating this overly-large buffer because we're on the big computer + // where RAM is cheap, and it's less likely to need adjustment this way. + buf: [u8; 1024], +} + +impl Client { + pub fn new(ip: &str, port: u16, timeout: Duration) -> Result { + let Some((ip, scope)) = ip.rsplit_once('%') else { + bail!("Missing scope id in IP (e.g. '%en0')") + }; + + let scopeid = decode_iface(scope)?; + + let dest = SocketAddrV6::new( + ip.parse()?, + port, + 0, // flow info + scopeid, + ); + + let socket = UdpSocket::bind("[::]:0")?; + socket.set_read_timeout(Some(timeout))?; + socket.connect(dest)?; + + Ok(Self { socket, buf: [0; 1024] }) + } + + pub fn read_sequencer_registers(&mut self) -> Result> { + use gimlet_inspector_protocol::{ + QueryV0, Request, SequencerRegistersResponseV0, + }; + + let len = hubpack::serialize( + &mut self.buf, + &Request::V0(QueryV0::SequencerRegisters), + )?; + + self.socket.send(&self.buf[..len])?; + let n = self.socket.recv(&mut self.buf).context("receiving packet")?; + let buf = &self.buf[..n]; + + let (response, extra) = + hubpack::deserialize::(buf)?; + match response { + SequencerRegistersResponseV0::Success => Ok(extra.to_vec()), + e => { + bail!("failed: {e:?}"); + } + } + } +} + +fn run(context: &mut ExecutionContext) -> Result<()> { + let Subcommand::Other(subargs) = context.cli.cmd.as_ref().unwrap(); + let subargs = Args::try_parse_from(subargs)?; + + let mut client = Client::new( + &subargs.ip, + subargs.port, + Duration::from_millis(subargs.timeout), + )?; + + // We can generalize this when we have more than one command defined. + match subargs.cmd { + SubCmd::ReadSeqRegs => { + let bytes = client.read_sequencer_registers()?; + println!("Received register contents follow:"); + let mut dumper = Dumper::new(); + dumper.size = 1; + dumper.dump(&bytes, 0); + } + } + + Ok(()) +} + +pub fn init() -> Command { + Command { + app: Args::command(), + name: "gimlet", + run, + kind: CommandKind::Detached { archive: Archive::Ignored }, + } +}