use bossa::bossa;
use clap::Parser;
use cxx::let_cxx_string;
use flexi_logger::Logger;
use log::{debug, error};
use std::path::PathBuf;
use std::time::Instant;
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "Basic Open Source SAM-BA Application (BOSSA)
Flash programmer for Atmel SAM devices.
Copyright (C) 2009-2020 ShumaTech (http://www.shumatech.com/)
Copyright (C) 2018-2023 Jacob Alexander (haata@kiibohd.com)
Examples:
bossac -e -w -v -b image.bin # Erase flash, write flash with image.bin,
# verify the write, and set boot from flash
bossac -r0x10000 image.bin # Read 64KB from flash and store in image.bin",
long_about = None
)]
struct Args {
file: Option<PathBuf>,
#[arg(short, long, default_value = "false")]
erase: bool,
#[arg(short, long, requires = "file", default_value = "false")]
write: bool,
#[arg(
short,
long,
require_equals = true,
value_name = "SIZE",
num_args = 0..=1,
requires = "file",
default_value = None,
default_missing_value = "0"
)]
read: Option<u32>,
#[arg(short, long, requires = "file", default_value = "false")]
verify: bool,
#[arg(short, long, default_value_t = 0)]
offset: u32,
#[arg(short, long, default_value = None)]
port: Option<String>,
#[arg(
short,
long,
require_equals = true,
value_name = "BOOL",
num_args = 0..=1,
default_value = None,
default_missing_value = "true"
)]
boot: Option<bool>,
#[arg(
short = 'c',
long,
require_equals = true,
value_name = "BOOL",
num_args = 0..=1,
default_value = None,
default_missing_value = "true"
)]
bod: Option<bool>,
#[arg(
short = 't',
long,
require_equals = true,
value_name = "BOOL",
num_args = 0..=1,
default_value = None,
default_missing_value = "true"
)]
bor: Option<bool>,
#[arg(
short,
long,
value_name = "REGION",
value_delimiter = ',',
num_args = 0..,
value_parser = clap::value_parser!(u16).range(0..),
default_value = None
)]
lock: Option<Vec<u16>>,
#[arg(
short,
long,
value_name = "REGION",
value_delimiter = ',',
num_args = 0..,
value_parser = clap::value_parser!(u16).range(0..),
default_value = None
)]
unlock: Option<Vec<u16>>,
#[arg(short, long, default_value = "false")]
security: bool,
#[arg(short, long, default_value = "false")]
info: bool,
#[arg(short, long, default_value = "false")]
debug: bool,
#[arg(short = 'U', long, value_name = "BOOL", default_value = "true")]
usb_port: bool,
#[arg(short = 'R', long, default_value = "false")]
reset: bool,
#[arg(short = 'a', long, default_value = "false")]
arduino_erase: bool,
}
fn setup_logging_lite() -> Result<(), ()> {
match Logger::try_with_env_or_str("")
.unwrap()
.format(flexi_logger::colored_default_format)
.format_for_files(flexi_logger::colored_detailed_format)
.duplicate_to_stderr(flexi_logger::Duplicate::All)
.start()
{
Err(_) => Err(()),
Ok(_) => Ok(()),
}
}
pub fn main() -> std::process::ExitCode {
let args = Args::parse();
setup_logging_lite().unwrap();
let mut samba = bossa::new_samba();
let mut port_factory = bossa::new_port_factory();
if args.debug {
debug!("Debug mode enabled");
samba.pin_mut().setDebug(true);
}
let port = if let Some(port) = args.port {
port
} else {
debug!("No port specified, using default");
port_factory.pin_mut().default_name().to_string()
};
debug!("Connecting to port: {}", port);
let_cxx_string!(port = port);
if args.arduino_erase {
debug!("Arduino erase");
let mut port = port_factory.pin_mut().create_port(&port, args.usb_port);
println!("Arduino 1200 baud reset");
if !port.pin_mut().open(
1200,
8,
bossa::Parity::ParityNone,
bossa::StopBit::StopBitOne,
) {
error!("Failed to open port at 1200bps");
return std::process::ExitCode::FAILURE;
}
port.pin_mut().setRTS(true);
port.pin_mut().setDTR(false);
port.pin_mut().close();
if args.debug {
println!("Arduino reset done");
}
}
if !samba.pin_mut().connect(
port_factory.pin_mut().create_port(&port, args.usb_port),
115200,
) {
error!("Failed to connect to port: {}", port);
return std::process::ExitCode::FAILURE;
}
let mut device = bossa::new_device(samba.pin_mut());
device.pin_mut().create();
let mut observer = bossa::new_bossa_observer();
let mut flasher = bossa::new_flasher(samba.pin_mut(), device.pin_mut(), observer.pin_mut());
if args.info {
debug!("Printing device info");
let mut flasher_info = bossa::new_flasher_info();
flasher.pin_mut().info(flasher_info.pin_mut());
flasher_info.pin_mut().print();
}
if let Some(regions) = args.unlock {
if regions.is_empty() {
debug!("Unlocking all regions");
let_cxx_string!(regions = "");
flasher.pin_mut().lock(®ions, false);
} else {
debug!("Unlocking regions: {:?}", regions);
let_cxx_string!(
regions = regions
.iter()
.map(|region| region.to_string())
.collect::<Vec<_>>()
.join(",")
);
flasher.pin_mut().lock(®ions, false);
}
}
if args.erase {
debug!("Erasing flash at offset: {}", args.offset);
let start = Instant::now();
flasher.pin_mut().erase(args.offset);
println!("Done in {:?}", start.elapsed());
}
let mut path_buf = Vec::new();
if let Some(path) = args.file {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
path_buf.extend(path.as_os_str().as_bytes());
path_buf.push(0);
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
path_buf.extend(
path.as_os_str()
.encode_wide()
.chain(Some(0))
.map(|b| {
let b = b.to_ne_bytes();
b.get(0).map(|s| *s).into_iter().chain(b.get(1).map(|s| *s))
})
.flatten(),
);
}
}
if args.write {
debug!(
"Writing flash with {:?} at offset: {}",
path_buf, args.offset
);
let start = Instant::now();
unsafe {
flasher
.pin_mut()
.write(path_buf.as_ptr() as *const i8, args.offset);
}
println!("\nDone in {:?}", start.elapsed());
}
if args.verify {
let mut page_errors = 0;
let mut total_errors = 0;
debug!(
"Verifying flash with {:?} at offset: {}",
path_buf, args.offset
);
let start = Instant::now();
if !unsafe {
flasher.pin_mut().verify(
path_buf.as_ptr() as *const i8,
&mut page_errors,
&mut total_errors,
args.offset,
)
} {
error!("Verify failed {:?}", start.elapsed());
error!("Page errors: {}", page_errors);
error!("Byte errors: {}", total_errors);
return std::process::ExitCode::FAILURE;
}
println!("\nDone in {:?}", start.elapsed());
}
if let Some(size) = args.read {
debug!(
"Reading flash into {:?} at offset: {}",
path_buf, args.offset
);
let start = Instant::now();
unsafe {
flasher
.pin_mut()
.read(path_buf.as_ptr() as *const i8, size, args.offset);
}
println!("\nDone in {:?}", start.elapsed());
}
if let Some(boot) = args.boot {
if boot {
println!("Setting boot: Flash");
} else {
println!("Setting boot: ROM");
}
flasher.pin_mut().setBootFlash(boot);
}
if let Some(brownout) = args.bod {
println!("Setting brownout detect: {}", brownout);
flasher.pin_mut().setBod(brownout);
}
if let Some(brownout) = args.bor {
println!("Setting brownout reset: {}", brownout);
flasher.pin_mut().setBor(brownout);
}
if args.security {
println!("Set security");
flasher.pin_mut().setSecurity();
}
if let Some(regions) = args.lock {
if regions.is_empty() {
debug!("Locking all regions");
let_cxx_string!(regions = "");
flasher.pin_mut().lock(®ions, true);
} else {
debug!("Locking regions: {:?}", regions);
let_cxx_string!(
regions = regions
.iter()
.map(|region| region.to_string())
.collect::<Vec<_>>()
.join(",")
);
flasher.pin_mut().lock(®ions, true);
}
}
debug!("Write options");
flasher.pin_mut().writeOptions();
if args.reset {
debug!("Reset");
flasher.pin_mut().reset();
}
debug!("Done");
std::process::ExitCode::SUCCESS
}