use anyhow::{bail, Context};
use argh::FromArgs;
use enumset::{EnumSet, EnumSetType};
use mctp_linux::{MctpAddr, MctpLinuxListener};
use std::fmt::Write as _;
use std::io::Write;
fn comma_separated<T: EnumSetType + std::fmt::Debug>(e: EnumSet<T>) -> String {
let mut s = String::new();
let mut first = true;
for i in e.iter() {
write!(s, "{}{:?}", if first { "" } else { "," }, i).unwrap();
first = false;
}
s
}
fn print_device_info(
dev: &pldm_fw::DeviceIdentifiers,
fwp: &pldm_fw::FirmwareParameters,
) {
println!("Device: {}", dev);
println!("Firmware Parameters:");
println!(" Active version: {}", fwp.active);
println!(" Pending version: {}", fwp.pending);
println!(
" Update caps: [0x{:x}]:{}",
fwp.caps.as_u32(),
if fwp.caps.is_empty() { " none" } else { "" }
);
for (cap, is_set) in fwp.caps.values() {
println!(" * {}", cap.to_desc(is_set));
}
println!(
" Components:{}",
if fwp.components.is_empty() {
" none"
} else {
""
}
);
for (idx, comp) in fwp.components.iter().enumerate() {
println!(" [{}]", idx);
println!(" Classification: {:?}", comp.classification);
println!(" Index: {:?}", comp.classificationindex);
println!(" Identifier: 0x{:04x}", comp.identifier);
println!(" Active Version: {}", comp.active);
println!(" Pending Version: {}", comp.pending);
println!(
" Activation: [0x{:x}] {}",
comp.activation_methods.as_u32(),
comma_separated(comp.activation_methods)
);
println!(
" Update caps: [0x{:x}] {}",
comp.caps_during_update.as_u32(),
comma_separated(comp.caps_during_update)
);
}
}
fn print_package(pkg: &pldm_fw::pkg::Package) {
println!("Package:");
println!(" Identifier: {}", pkg.identifier);
println!(" Version: {}", pkg.version);
println!(" Applicable devices:");
for (idx, dev) in pkg.devices.iter().enumerate() {
println!(" {:2}: {}", idx, dev.ids);
println!(" version: {}", dev.version);
println!(" options: 0x{:x}", dev.option_flags);
println!(" components: {}", dev.components.as_index_str());
}
println!(" Components:");
for (idx, cmp) in pkg.components.iter().enumerate() {
println!(" {:2}:", idx);
println!(" classification: {:?}", cmp.classification);
println!(" identifier: 0x{:04x}", cmp.identifier);
println!(" version: {}", cmp.version);
println!(" comparison: 0x{:08x}", cmp.comparison_stamp);
println!(" options: 0x{:04x}", cmp.options);
println!(" activation: 0x{:04x}", cmp.activation_method);
println!(" file size: 0x{:04x}", cmp.file_size);
}
}
fn print_device(dev: &pldm_fw::DeviceIdentifiers) {
println!("Device: {}", dev);
}
fn print_update(update: &pldm_fw::ua::Update) {
println!("Update:");
println!(" Package version: {}", update.package.version);
println!(" Apply to index: {}", update.index);
println!(" Components to update:");
for (idx, cmp_idx) in update.components.iter().enumerate() {
let cmp = &update.package.components[*cmp_idx];
println!(
" {:2}: id {:04x}, version {}",
idx, cmp.identifier, cmp.version
);
}
}
fn extract_component(
pkg: &pldm_fw::pkg::Package,
idx: usize,
) -> anyhow::Result<()> {
if idx >= pkg.components.len() {
bail!("no component with index {}", idx);
}
let comp = &pkg.components[idx];
let fname = format!("component-{}.{:04x}.bin", idx, comp.identifier);
let mut f = std::fs::File::create(&fname)
.with_context(|| format!("Can't open output file {}", fname))?;
println!("extracting component {} to {}", idx, fname);
let mut buf = vec![0u8; comp.file_size];
pkg.read_component(comp, 0, &mut buf)?;
f.write_all(&buf)?;
Ok(())
}
fn confirm_update() -> bool {
let mut line = String::new();
print!("\nConfirm update (y,N)? ");
let _ = std::io::stdout().flush();
let rc = std::io::stdin().read_line(&mut line);
if rc.is_err() {
return false;
}
line.trim().to_ascii_lowercase() == "y"
}
fn open_package(fname: String) -> anyhow::Result<pldm_fw::pkg::Package> {
let f = std::fs::File::open(&fname)
.with_context(|| format!("Can't open PLDM package {}", fname))?;
let pkg = pldm_fw::pkg::Package::parse(f)
.with_context(|| format!("Can't parse PLDM package {}", fname))?;
Ok(pkg)
}
#[derive(FromArgs, Debug)]
#[argh(description = "PLDM update utility")]
struct Args {
#[argh(switch, short = 'd')]
debug: bool,
#[argh(subcommand)]
command: Command,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
enum Command {
Inventory(InventoryCommand),
Update(UpdateCommand),
Cancel(CancelCommand),
PkgInfo(PkgInfoCommand),
Version(VersionCommand),
Extract(ExtractCommand),
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "inventory", description = "Query FD inventory")]
struct InventoryCommand {
#[argh(positional)]
addr: MctpAddr,
}
#[derive(FromArgs, Debug)]
#[argh(
subcommand,
name = "update",
description = "Update FD from a package file"
)]
struct UpdateCommand {
#[argh(positional)]
addr: MctpAddr,
#[argh(positional)]
file: String,
#[argh(option)]
component_index: Option<u8>,
#[argh(option)]
force_device: Option<usize>,
#[argh(option)]
force_components: Vec<usize>,
#[argh(switch)]
self_contained_activation: bool,
#[argh(switch, short = 'y')]
confirm: bool,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "cancel", description = "Cancel ongoing update")]
struct CancelCommand {
#[argh(positional)]
addr: MctpAddr,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "pkg-info", description = "Query package contents")]
struct PkgInfoCommand {
#[argh(positional)]
file: String,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "extract", description = "Extract package contents")]
struct ExtractCommand {
#[argh(positional)]
file: String,
#[argh(positional)]
components: Vec<usize>,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "version", description = "Print pldm-fw version")]
struct VersionCommand {}
fn duration_str(d: &chrono::Duration) -> String {
let secs = d.num_seconds();
if secs < 0 {
"unknown".to_string()
} else if secs > 86400 {
format!("{} days", secs / 86400)
} else {
let mut s = secs;
let h = s / 3600;
s -= h * 3600;
let m = s / 60;
s -= m * 60;
format!("{:02}:{:02}:{:02}", h, m, s)
}
}
fn bps_str(bps: f32) -> String {
const B_PER_MB: f32 = 1_000_000.0;
#[allow(non_upper_case_globals)]
const B_PER_kB: f32 = 1_000.0;
let threshold = 0.8;
if bps > (B_PER_MB * threshold) {
format!("{:.2} MB/sec", bps / B_PER_MB)
} else if bps > (B_PER_kB * threshold) {
format!("{:.2} kB/sec", bps / B_PER_kB)
} else {
format!("{:.0} B/sec", bps)
}
}
fn progress(p: &pldm_fw::UpdateTransferProgress) {
if p.complete {
println!(
"Firmware transfer complete, duration {}, {}",
duration_str(&p.duration),
bps_str(p.bps)
);
} else {
let (offset, len) = p.cur_xfer.unwrap_or((0, 0));
println!(
"Data request: offset 0x{:08x}, len 0x{:x}, {:2}% {}, {} remaining",
offset,
len,
p.percent,
bps_str(p.bps),
duration_str(&p.remaining),
);
}
}
fn main() -> anyhow::Result<()> {
let args: Args = argh::from_env();
let level = if args.debug {
log::LevelFilter::Debug
} else {
log::LevelFilter::Warn
};
env_logger::Builder::new().filter_level(level).init();
match args.command {
Command::Inventory(i) => {
let mut ep = i.addr.create_endpoint()?;
let dev = pldm_fw::ua::query_device_identifiers(&mut ep)?;
let params = pldm_fw::ua::query_firmware_parameters(&mut ep)?;
print_device_info(&dev, ¶ms)
}
Command::Update(u) => {
let pkg = open_package(u.file)?;
let mut ep = u.addr.create_endpoint()?;
let ep = &mut ep;
let mut listener = MctpLinuxListener::new(
mctp::MCTP_TYPE_PLDM,
Some(u.addr.net()),
)?;
let dev = pldm_fw::ua::query_device_identifiers(ep)?;
let fwp = pldm_fw::ua::query_firmware_parameters(ep)?;
let mut update = pldm_fw::ua::Update::new(
&dev,
&fwp,
pkg,
u.component_index,
u.force_device,
u.force_components,
)?;
println!("Proposed update:");
print_device(&dev);
print_update(&update);
let c = u.confirm || confirm_update();
if !c {
return Ok(());
}
let _ = pldm_fw::ua::request_update(ep, &update)?;
pldm_fw::ua::pass_component_table(ep, &update)?;
pldm_fw::ua::update_components_progress(
ep,
&mut listener,
&mut update,
progress,
)?;
pldm_fw::ua::activate_firmware(ep, u.self_contained_activation)?;
}
Command::Cancel(c) => {
let mut ep = c.addr.create_endpoint()?;
let _ = pldm_fw::ua::cancel_update(&mut ep);
}
Command::PkgInfo(p) => {
let pkg = open_package(p.file)?;
print_package(&pkg);
}
Command::Extract(e) => {
let pkg = open_package(e.file)?;
if e.components.is_empty() {
println!("No components specified to extract");
}
for idx in e.components {
let res = extract_component(&pkg, idx);
if let Err(e) = res {
println!("Error extracting: {:?}", e);
}
}
}
Command::Version(_) => {
println!("pldm-fw version {}", env!("VERSION"));
return Ok(());
}
}
Ok(())
}