use std::path::PathBuf;
use std::{u32, u8};
use clap::ArgMatches;
use failure::{Error, ResultExt};
use super::CliError;
#[derive(Debug)]
pub enum WriteData {
Word(u32),
File(Box<PathBuf>),
}
#[derive(Debug)]
pub enum Command {
Uboot { file: PathBuf, start_uboot: bool },
Dump {
address: Option<u32>,
size: Option<u32>,
hex: bool,
sid: bool,
out: Option<PathBuf>,
},
Write {
addresses: Vec<u32>,
data: Vec<WriteData>,
},
Execute { address: u32 },
Reset64 { address: u32 },
Version,
Clear { address: u32, num_bytes: u32 },
Fill {
address: u32,
num_bytes: u32,
fill_byte: u8,
},
}
pub struct Config {
device: Option<(u8, u8)>,
command: Option<Command>,
}
impl Config {
pub fn from_cli(cli: &ArgMatches) -> Result<Self, Error> {
Ok(Self {
device: Self::get_device_from_cli(&cli)?,
command: Self::get_command_from_cli(&cli)?,
})
}
pub fn get_device(&self) -> Option<(u8, u8)> {
self.device
}
pub fn get_command(&self) -> Option<&Command> {
self.command.as_ref()
}
fn get_device_from_cli(cli: &ArgMatches) -> Result<Option<(u8, u8)>, Error> {
Ok(match cli.value_of("device") {
Some(device_str) => {
let mut split = device_str.split(':');
let bus = split.next();
let addr = split.next();
if let (Some(bus), Some(addr), None) = (bus, addr, split.next()) {
Some((
bus.parse::<u8>().context(CliError {
description: format!(
"bus number must be an integer between 0 and {}",
u8::max_value()
),
})?,
addr.parse::<u8>().context(CliError {
description: format!(
"device address must be an integer between 0 and {}",
u8::max_value()
),
})?,
))
} else {
return Err(CliError {
description: "Device must be in `bus:addr` format".to_owned(),
}
.into());
}
}
None => None,
})
}
fn get_command_from_cli(cli: &ArgMatches) -> Result<Option<Command>, Error> {
if let Some(spl) = cli.subcommand_matches("spl") {
let file = PathBuf::from(spl.value_of("file").unwrap());
if file.exists() {
Ok(Some(Command::Uboot {
file,
start_uboot: spl.is_present("exec"),
}))
} else {
Err(CliError {
description: format!("the file '{}' does not exist", file.display()),
}
.into())
}
} else if let Some(dump) = cli.subcommand_matches("dump") {
if dump.is_present("sid") {
Ok(Some(Command::Dump {
address: None,
size: None,
hex: false,
sid: true,
out: None,
}))
} else {
let addr_str = dump.value_of("addr").unwrap();
let addr = if addr_str.starts_with("0x") {
u32::from_str_radix(addr_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(addr_str, 10)
}
.context(CliError {
description: format!(
"memory address must be an integer from 0x00000000 to {:#010x}",
u32::max_value()
),
})?;
let size = if let Some(size_str) = dump.value_of("size") {
let size = if size_str.starts_with("0x") {
u32::from_str_radix(size_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(size_str, 10)
}
.context(CliError {
description: format!(
"dump size must be an integer from 0x00000000 to {:#010x} (the \
maximum size starting from the given address)",
(u32::max_value() - addr).saturating_add(1)
),
})?;
if size > (u32::max_value() - addr).saturating_add(1) {
return Err(CliError {
description: format!(
"dump size must be an integer from 0x00000000 to {:#010x} (the \
maximum size starting from the given address)",
(u32::max_value() - addr).saturating_add(1)
),
}
.into());
}
Some(size)
} else {
None
};
Ok(Some(Command::Dump {
address: Some(addr),
size,
hex: dump.is_present("hex"),
sid: false,
out: dump.value_of("out").map(PathBuf::from),
}))
}
} else if let Some(write) = cli.subcommand_matches("write") {
let mut value_iter = write.values_of("write_data").unwrap();
let writes = (write.occurrences_of("write_data") / 2) as usize;
let mut addresses = Vec::with_capacity(writes);
let mut data = Vec::with_capacity(writes);
for _ in 0..writes {
let addr_str = value_iter.next().unwrap();
let addr = if addr_str.starts_with("0x") {
u32::from_str_radix(addr_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(addr_str, 10)
}
.context(CliError {
description: format!(
"memory address must be an integer from 0x00000000 to {:#010x}, given \
'{}'",
u32::max_value(),
addr_str
),
})?;
let value_str = value_iter.next().unwrap();
let word = if value_str.starts_with("0x") {
u32::from_str_radix(value_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(value_str, 10)
};
let final_value = match word {
Ok(word) => {
if u32::max_value() - 4 >= addr {
WriteData::Word(word)
} else {
let err_msg = format!(
"cannot write a complete word at address {:#010x}, it would write \
past the end of the memory address space (limit: {:#010x})",
addr,
u32::max_value()
);
return Err(CliError {
description: err_msg,
}
.into());
}
}
Err(e) => {
let path = PathBuf::from(value_str);
if path.exists() {
let metadata =
path.metadata().context("could not read file metadata")?;
let max_bytes = u64::from((u32::max_value() - addr).saturating_add(1));
if metadata.len() > max_bytes {
let err_msg = format!(
"the file '{}' is too big. The maximum file size to write to \
address {:#010x} is {} bytes, but the file had {} bytes",
path.display(),
addr,
max_bytes,
metadata.len()
);
return Err(CliError {
description: err_msg,
}
.into());
}
WriteData::File(Box::new(path))
} else {
return Err(CliError {
description: format!(
"the file '{}' does not exist.\nNote: If you were trying to \
provide a value, the integer conversion failed with this error: \
{}",
path.display(),
e
),
}
.into());
}
}
};
addresses.push(addr);
data.push(final_value);
}
Ok(Some(Command::Write { addresses, data }))
} else if let Some(exec) = cli.subcommand_matches("exec") {
let addr_str = exec.value_of("addr").unwrap();
let addr = if addr_str.starts_with("0x") {
u32::from_str_radix(addr_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(addr_str, 10)
}
.context(CliError {
description: format!(
"memory address must be an integer from 0x00000000 to {:#010x}, given '{}'",
u32::max_value(),
addr_str
),
})?;
Ok(Some(Command::Execute { address: addr }))
} else if let Some(reset64) = cli.subcommand_matches("reset64") {
let addr_str = reset64.value_of("addr").unwrap();
let addr = if addr_str.starts_with("0x") {
u32::from_str_radix(addr_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(addr_str, 10)
}
.context(CliError {
description: format!(
"memory address must be an integer from 0x00000000 to {:#010x}, given '{}'",
u32::max_value(),
addr_str
),
})?;
Ok(Some(Command::Reset64 { address: addr }))
} else if cli.subcommand_matches("version").is_some() {
Ok(Some(Command::Version))
} else if let Some(clear) = cli.subcommand_matches("clear") {
let addr_str = clear.value_of("addr").unwrap();
let address = if addr_str.starts_with("0x") {
u32::from_str_radix(addr_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(addr_str, 10)
}
.context(CliError {
description: format!(
"memory address must be an integer from 0x00000000 to {:#010x}, given '{}'",
u32::max_value(),
addr_str
),
})?;
let num_bytes_str = clear.value_of("num_bytes").unwrap();
let num_bytes = if num_bytes_str.starts_with("0x") {
u32::from_str_radix(num_bytes_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(num_bytes_str, 10)
}
.context(CliError {
description: format!(
"the number of bytes to clear must be an integer from 0x00000000 to {:#010x} \
(the maximum size starting from the given address)",
(u32::max_value() - address).saturating_add(1)
),
})?;
if num_bytes > (u32::max_value() - address).saturating_add(1) {
return Err(CliError {
description: format!(
"clear size must be an integer from 0x00000000 to {:#010x} (the maximum size \
starting from the given address)",
(u32::max_value() - address).saturating_add(1)
),
}
.into());
}
Ok(Some(Command::Clear { address, num_bytes }))
} else if let Some(fill) = cli.subcommand_matches("fill") {
let addr_str = fill.value_of("addr").unwrap();
let address = if addr_str.starts_with("0x") {
u32::from_str_radix(addr_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(addr_str, 10)
}
.context(CliError {
description: format!(
"memory address must be an integer from 0x00000000 to {:#010x}, given '{}'",
u32::max_value(),
addr_str
),
})?;
let num_bytes_str = fill.value_of("num_bytes").unwrap();
let num_bytes = if num_bytes_str.starts_with("0x") {
u32::from_str_radix(num_bytes_str.trim_left_matches("0x"), 16)
} else {
u32::from_str_radix(num_bytes_str, 10)
}
.context(CliError {
description: format!(
"the number of bytes to fill must be an integer from 0x00000000 to {:#010x} \
(the maximum size starting from the given address)",
(u32::max_value() - address).saturating_add(1)
),
})?;
let fill_byte_str = fill.value_of("fill_byte").unwrap();
let fill_byte = if fill_byte_str.starts_with("0x") {
u8::from_str_radix(fill_byte_str.trim_left_matches("0x"), 16)
} else {
u8::from_str_radix(fill_byte_str, 10)
}
.context(CliError {
description: format!(
"the filling byte must be an integer from 0x00 to {:#04x}, given '{}'",
u8::max_value(),
fill_byte_str
),
})?;
Ok(Some(Command::Fill {
address,
num_bytes,
fill_byte,
}))
} else {
Ok(None)
}
}
}