Skip to content

Commit

Permalink
gimlet inspector command: first version
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cbiffle committed Feb 1, 2024
1 parent 136f158 commit 6c79629
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 0 deletions.
28 changes: 28 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ members = [
"cmd/extract",
"cmd/flash",
"cmd/gdb",
"cmd/gimlet",
"cmd/gpio",
"cmd/hash",
"cmd/hiffy",
Expand Down Expand Up @@ -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"}
Expand Down Expand Up @@ -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" }
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 }
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions cmd/gimlet/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
149 changes: 149 additions & 0 deletions cmd/gimlet/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<Vec<u8>> {
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::<SequencerRegistersResponseV0>(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 },
}
}

0 comments on commit 6c79629

Please sign in to comment.