216 lines
6.3 KiB
Rust
216 lines
6.3 KiB
Rust
use clap::{Parser, Subcommand};
|
|
use pbr::{ProgressBar, Units};
|
|
use punt::Operation;
|
|
use rusb::{self, DeviceHandle, UsbContext};
|
|
use std::{error::Error, fs::File, io::Read};
|
|
|
|
const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(500);
|
|
|
|
#[derive(Parser)]
|
|
#[command(author, version, about, long_about = None)]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// List connected devices
|
|
List,
|
|
|
|
/// Flash the adaptor
|
|
Flash {
|
|
#[arg()]
|
|
file: String,
|
|
},
|
|
|
|
/// Enter the bootloader
|
|
EnterBootloader,
|
|
|
|
/// Exit from the bootloader
|
|
ExitBootloader,
|
|
}
|
|
|
|
fn match_usb_device<T: UsbContext>(
|
|
device: rusb::Device<T>,
|
|
vendor_id: u16,
|
|
product_id: u16,
|
|
vendor_string: &str,
|
|
product_string: &str,
|
|
) -> rusb::Result<Option<rusb::DeviceHandle<T>>> {
|
|
let device_desc = device.device_descriptor()?;
|
|
|
|
if device_desc.vendor_id() != vendor_id || device_desc.product_id() != product_id {
|
|
return Ok(None);
|
|
}
|
|
|
|
let device_handle = device.open()?;
|
|
|
|
// Choose first language (the punt bootloader only supports English anyway)
|
|
let language = device_handle.read_languages(TIMEOUT)?[0];
|
|
|
|
let v_string = device_handle.read_manufacturer_string(language, &device_desc, TIMEOUT)?;
|
|
let p_string = device_handle.read_product_string(language, &device_desc, TIMEOUT)?;
|
|
|
|
if v_string != vendor_string || p_string != product_string {
|
|
return Ok(None);
|
|
}
|
|
|
|
Ok(Some(device_handle))
|
|
}
|
|
|
|
fn enter_bootloader<T: UsbContext>(handle: rusb::DeviceHandle<T>) -> rusb::Result<()> {
|
|
match handle.write_control(
|
|
rusb::request_type(
|
|
rusb::Direction::Out,
|
|
rusb::RequestType::Vendor,
|
|
rusb::Recipient::Device,
|
|
),
|
|
0xb0,
|
|
0,
|
|
0,
|
|
&[0u8; 0],
|
|
std::time::Duration::ZERO,
|
|
) {
|
|
// We expect a communication failure here since this command immediately stops all USB
|
|
// communications
|
|
Err(rusb::Error::Io) => Ok(()),
|
|
_ => Err(rusb::Error::Io),
|
|
}
|
|
}
|
|
|
|
fn application_targets<T: rusb::UsbContext>(context: &T) -> rusb::Result<Vec<DeviceHandle<T>>> {
|
|
Ok(context
|
|
.devices()?
|
|
.iter()
|
|
.filter_map(|device| {
|
|
match_usb_device(device, 0x16c0, 0x05e1, "25120.org", "HPGL XY").ok()?
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
fn bootloader_targets<T: rusb::UsbContext + punt::UsbContext>(
|
|
context: &T,
|
|
) -> rusb::Result<Vec<punt::TargetHandle<T>>> {
|
|
Ok(context
|
|
.devices()?
|
|
.iter()
|
|
.filter_map(|device| {
|
|
match_usb_device(device, 0x16c0, 0x05dc, "25120.org", "punt")
|
|
.ok()
|
|
.flatten()
|
|
.map(TryInto::try_into)?
|
|
.ok()
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let cli = Cli::parse();
|
|
|
|
let context = rusb::Context::new()?;
|
|
|
|
match cli.command {
|
|
Commands::List => {
|
|
println!("Connected devices in application mode:");
|
|
for handle in application_targets(&context)? {
|
|
println!(
|
|
"{}",
|
|
handle.read_serial_number_string(
|
|
handle.read_languages(TIMEOUT)?[0],
|
|
&handle.device().device_descriptor()?,
|
|
TIMEOUT
|
|
)?
|
|
);
|
|
}
|
|
println!("Connected devices in bootloader mode:");
|
|
for handle in bootloader_targets(&context)? {
|
|
println!("{}", handle.serial());
|
|
}
|
|
}
|
|
|
|
Commands::EnterBootloader => {
|
|
for handle in application_targets(&context)? {
|
|
enter_bootloader(handle)?;
|
|
}
|
|
}
|
|
|
|
Commands::ExitBootloader => {
|
|
for mut handle in bootloader_targets(&context)? {
|
|
handle.exit_bootloader()?;
|
|
}
|
|
}
|
|
|
|
Commands::Flash { file } => {
|
|
let mut handles;
|
|
loop {
|
|
handles = bootloader_targets(&context)?;
|
|
if !handles.is_empty() {
|
|
break;
|
|
}
|
|
|
|
for handle in application_targets(&context)? {
|
|
enter_bootloader(handle)?;
|
|
}
|
|
}
|
|
|
|
for mut handle in handles {
|
|
println!("Flashing {}.", handle.serial());
|
|
|
|
let bootloader_info = handle.bootloader_info()?;
|
|
let start = bootloader_info.application_base;
|
|
|
|
let mut file = File::open(&file)?;
|
|
let mut buff = Vec::new();
|
|
file.read_to_end(&mut buff)?;
|
|
|
|
let erase = handle.erase_area(start, buff.len())?;
|
|
println!("Erasing {} pages.", erase.total());
|
|
let mut pb = ProgressBar::new(erase.total() as u64);
|
|
pb.show_speed = false;
|
|
pb.show_time_left = false;
|
|
for status in erase {
|
|
pb.set(status? as u64);
|
|
}
|
|
pb.finish();
|
|
|
|
let program = handle.program_at(buff.as_slice(), start)?;
|
|
println!("Flashing {} bytes.", program.total());
|
|
let mut pb = ProgressBar::new(program.total() as u64);
|
|
pb.set_units(Units::Bytes);
|
|
for status in program {
|
|
pb.set(status? as u64);
|
|
}
|
|
pb.finish();
|
|
|
|
match handle.verify(buff.as_slice(), start) {
|
|
Ok(()) => println!("CRC verification successful."),
|
|
Err(e) => {
|
|
println!("CRC verification error: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
|
|
println!("Exiting bootloader.");
|
|
handle.exit_bootloader()?;
|
|
}
|
|
}
|
|
}
|
|
|
|
// let targets: Vec<_> = context
|
|
// .devices()?
|
|
// .iter()
|
|
// .filter_map(|device| {
|
|
// match_usb_device(device, 0x16c0, 0x05e1, "25120.org", "HPGL XY").ok()?
|
|
// })
|
|
// .collect();
|
|
|
|
// for mut handle in targets {
|
|
// println!("Found device: {}", handle.device().bus_number());
|
|
// // device_handle.claim_interface(0)?;
|
|
// println!("Entering Bootloader");
|
|
// enter_bootloader(handle)?;
|
|
// }
|
|
|
|
Ok(())
|
|
}
|