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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions hil/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ probe-rs = "0.27.0"
rand = "0.8"
reqwest = { workspace = true, default-features = false, features = ["rustls-tls"] }
secrecy.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
tempfile = "3"
thiserror.workspace = true
Expand Down
37 changes: 37 additions & 0 deletions hil/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,40 @@ ors-os artifacts from S3 and pass that as an env var. See [here][aws cli config]
info.

[aws cli config]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html

## Debug board configuration

⚠️ **Warning**: EEPROM programming is a critical operation. Incorrect values can render your device unusable. Always
read and backup the current EEPROM before writing, and verify the configuration values before programming.

With debug board v1.1 comes an embedded EEPROM that can be programmed to set a configuration
to the FTDI chip, such as the serial number.
This serial number enables selection among a few different ones connected to the host.

Use the `ftdi` command to read & write the config:

```sh
# Backup current config first
orb-hil ftdi read --file backup.json

# Then write new config
orb-hil ftdi write ftdi_config.json
```

Here is an example of an FTDI configuration (please set a working serial, like the id of the orb it's connected to):

```json
{
"vendor_id": 1027,
"product_id": 24593,
"serial_number_enable": true,
"max_current_ma": 500,
"self_powered": false,
"remote_wakeup": false,
"pull_down_enable": true,
"manufacturer": "FTDI",
"manufacturer_id": "FT",
"description": "FT4232H",
"serial_number": "cafebabe"
}
```
26 changes: 20 additions & 6 deletions hil/src/boot.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::time::Duration;

use crate::ftdi::{FtdiGpio, FtdiId, OutputState};
use crate::ftdi::{FtdiChannel, FtdiGpio, FtdiId, OutputState};
use color_eyre::{eyre::WrapErr as _, Result};
use tracing::{debug, info};

pub const BUTTON_PIN: crate::ftdi::Pin = FtdiGpio::CTS_PIN;
pub const RECOVERY_PIN: crate::ftdi::Pin = FtdiGpio::RTS_PIN;
pub const BUTTON_PIN: crate::ftdi::Pin = FtdiGpio::DTR_PIN;
pub const RECOVERY_PIN: crate::ftdi::Pin = FtdiGpio::CTS_PIN;
pub const NVIDIA_VENDOR_ID: u16 = 0x0955;
pub const NVIDIA_USB_ETHERNET: u16 = 0x7035;

Expand All @@ -20,15 +20,29 @@ pub async fn is_recovery_mode_detected() -> Result<bool> {
Ok(num_nvidia_devices > 0)
}

/// If `device` is `None`, will get the first available device.
/// The default channel used for button/recovery control on the HIL.
pub const DEFAULT_CHANNEL: FtdiChannel = FtdiChannel::C;

/// If `device` is `None`, will get the first available device, or fall back to
/// the default channel (FT4232H C) if multiple devices are found.
#[tracing::instrument]
pub async fn reboot(recovery: bool, device: Option<&FtdiId>) -> Result<()> {
fn make_ftdi(device: Option<FtdiId>) -> Result<FtdiGpio> {
let builder = FtdiGpio::builder();
let builder = match &device {
Some(FtdiId::Description(desc)) => builder.with_description(desc),
Some(FtdiId::SerialNumber(serial)) => builder.with_serial_number(serial),
None => builder.with_default_device(),
Some(FtdiId::FtdiSerial(serial)) => builder.with_ftdi_serial(serial),
None => match builder.with_default_device() {
Ok(b) => return b.configure().wrap_err("failed to configure ftdi"),
Err(e) => {
tracing::error!("failed to build default ftdi: {}, trying with default channel name", e);
// Fall back to default channel when multiple devices exist
return FtdiGpio::builder()
.with_description(DEFAULT_CHANNEL.description_suffix())
.and_then(|b| b.configure())
.wrap_err("failed to create ftdi device with default channel");
}
},
};
builder
.and_then(|b| b.configure())
Expand Down
97 changes: 86 additions & 11 deletions hil/src/commands/button_ctrl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,112 @@ use clap::Parser;
use color_eyre::{eyre::WrapErr as _, Result};
use humantime::parse_duration;
use std::time::Duration;
use tracing::info;
use tracing::{debug, info};

use crate::boot::BUTTON_PIN;
use crate::ftdi::{FtdiGpio, OutputState};
use crate::boot::{BUTTON_PIN, DEFAULT_CHANNEL};
use crate::ftdi::{FtdiChannel, FtdiGpio, OutputState};

/// Control the orb button over the debug board
#[derive(Debug, Parser)]
pub struct ButtonCtrl {
///Button press duration (e.g., "1s", "500ms")
/// Button press duration (e.g., "1s", "500ms")
#[arg(long, default_value = "1s", value_parser = parse_duration)]
press_duration: Duration,

/// The USB serial number of the FTDI chip (e.g., "FT7ABC12").
/// Will use the default channel (C) for this chip.
#[arg(long, conflicts_with_all = ["ftdi_serial", "desc"])]
usb_serial: Option<String>,

/// The FTDI serial number including channel (e.g., "FT7ABC12C").
/// This is the USB serial + channel letter (A/B/C/D).
#[arg(long, conflicts_with_all = ["usb_serial", "desc"])]
ftdi_serial: Option<String>,

/// The FTDI description (e.g., "FT4232H C").
#[arg(long, conflicts_with_all = ["usb_serial", "ftdi_serial"])]
desc: Option<String>,

/// The channel to use when --usb-serial is provided (A, B, C, or D).
/// Defaults to C.
#[arg(long, default_value = "C", requires = "usb_serial")]
channel: FtdiChannel,
}

impl ButtonCtrl {
pub async fn run(self) -> Result<()> {
fn make_ftdi() -> Result<FtdiGpio> {
FtdiGpio::builder()
.with_default_device()
.and_then(|b| b.configure())
.wrap_err("failed to create ftdi device")
}
let usb_serial = self.usb_serial.clone();
let ftdi_serial = self.ftdi_serial.clone();
let desc = self.desc.clone();
let channel = self.channel;

let make_ftdi = move || -> Result<FtdiGpio> {
let builder = FtdiGpio::builder();
match (usb_serial.as_ref(), ftdi_serial.as_ref(), desc.as_ref()) {
(Some(usb_serial), None, None) => {
debug!(
"using FTDI device with USB serial: {usb_serial}, channel: {:?}",
channel
);
builder
.with_usb_serial(usb_serial, channel)
.and_then(|b| b.configure())
.wrap_err("failed to create ftdi device with USB serial")
}
(None, Some(ftdi_serial), None) => {
debug!("using FTDI device with FTDI serial: {ftdi_serial}");
builder
.with_ftdi_serial(ftdi_serial)
.and_then(|b| b.configure())
.wrap_err("failed to create ftdi device with FTDI serial")
}
(None, None, Some(desc)) => {
debug!("using FTDI device with description: {desc}");
builder
.with_description(desc)
.and_then(|b| b.configure())
.wrap_err("failed to create ftdi device with description")
}
(None, None, None) => {
// Try default device first, fall back to default channel description
match builder.with_default_device() {
Ok(b) => {
b.configure().wrap_err("failed to configure ftdi device")
}
Err(e) => {
debug!("default device selection failed: {e}");
let desc_suffix = DEFAULT_CHANNEL.description_suffix();
debug!(
"attempting to find device with description '{desc_suffix}'"
);
FtdiGpio::builder()
.with_description(desc_suffix)
.and_then(|b| b.configure())
.wrap_err_with(|| {
format!(
"failed to open FTDI device with description \
'{desc_suffix}'"
)
})
}
}
}
_ => unreachable!(),
}
};

info!(
"Holding button for {} seconds",
self.press_duration.as_secs_f32()
);
let press_duration = self.press_duration;
tokio::task::spawn_blocking(move || -> Result<(), color_eyre::Report> {
let mut ftdi = make_ftdi()?;
ftdi.set_pin(BUTTON_PIN, OutputState::Low)?;
std::thread::sleep(self.press_duration);
std::thread::sleep(press_duration);
ftdi.set_pin(BUTTON_PIN, OutputState::High)?;
ftdi.destroy().wrap_err("failed to destroy ftdi")?;

Ok(())
})
.await
Expand Down
Loading