#![doc(html_logo_url = "https://locha.io/i/128.png")]
#![doc(html_favicon_url = "https://locha.io/i/128.png")]
use std::{
cmp::Ordering,
fmt, io,
time::{Duration, Instant},
};
use serial::SerialPort;
#[rustfmt::skip]
pub mod constants;
pub mod ports;
pub mod util;
mod family;
pub use self::family::Family;
pub struct Device<P> {
family: Family,
port: P,
}
impl<P> Device<P>
where
P: SerialPort,
{
pub fn new(port: P, family: Family) -> io::Result<Self> {
let mut device = Device { port, family };
device.init_communications()?;
Ok(device)
}
pub fn family(&self) -> Family {
self.family
}
fn write_cmd<D>(&mut self, cmd: u8, data: &D) -> io::Result<()>
where
D: AsRef<[u8]>,
{
const HDR_LEN: usize = 3;
let data = data.as_ref();
let pkt_len = HDR_LEN + data.len();
if pkt_len > usize::from(std::u8::MAX) {
panic!("packet too big");
}
let mut pkt = Vec::with_capacity(pkt_len);
pkt.push(pkt_len as u8);
pkt.push(command_checksum(cmd, data));
pkt.push(cmd);
pkt.extend_from_slice(data);
log::trace!("sending cmd {:#X}, pkt = {:?}", cmd, pkt);
self.port.write_all(pkt.as_slice())?;
self.port.flush()?;
Ok(())
}
fn read_ack(&mut self) -> io::Result<bool> {
log::trace!("waiting for ACK");
let start_time = Instant::now();
let timeout = Duration::from_secs(1);
let mut ack = vec![0xFF, 0xFF];
loop {
let mut byte = [0u8; 1];
match self.port.read(&mut byte) {
Ok(n) if n == 0 => {
return Err(io::Error::new(
io::ErrorKind::Other,
"unexpected EOF",
));
}
Ok(_) => {
ack.push(byte[0]);
}
Err(e) if e.kind() == io::ErrorKind::TimedOut => {
log::trace!("read timed out");
}
Err(e) => return Err(e),
}
if ack[ack.len() - 2] == 0x00
&& (ack[ack.len() - 1] == constants::ACK
|| ack[ack.len() - 1] == constants::NACK)
{
log::trace!("ACK bytes found {:?}", &ack[2..]);
break;
} else if Instant::now().duration_since(start_time) >= timeout {
log::trace!("ACK bytes not found, timed out");
return Err(io::Error::new(
io::ErrorKind::Other,
"ACK bytes not found, timed out",
));
}
}
log::trace!("found ACK bytes after {} bytes", ack.len() - 2);
match (ack[ack.len() - 2], ack[ack.len() - 1]) {
(0x00, constants::ACK) => Ok(true),
(0x00, constants::NACK) => Ok(false),
_ => unreachable!(),
}
}
fn write_ack(&mut self, ack: bool) -> io::Result<()> {
let data: [u8; 2] =
[0x00, if ack { constants::ACK } else { constants::NACK }];
self.port.write_all(&data)?;
self.port.flush()?;
Ok(())
}
fn read_response(&mut self, response: &mut [u8]) -> io::Result<()> {
const HDR_LEN: usize = 2;
log::trace!("waiting for response header");
let mut hdr = [0u8; HDR_LEN];
self.port.read_exact(&mut hdr)?;
log::trace!(
"response header received, len = {}, cksum = {:#X}",
hdr[0],
hdr[1]
);
let payload_len = hdr[0] as usize - HDR_LEN;
match response.len().cmp(&payload_len) {
Ordering::Greater => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"received response is too small, expected {}, found {}",
response.len(),
payload_len,
),
))
}
Ordering::Less => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"received response is too big, expected {}, found {}",
response.len(),
payload_len,
),
))
}
_ => (),
}
log::trace!(
"waiting for rest of response, expecting {} bytes",
response.len()
);
self.port.read_exact(response)?;
Ok(())
}
fn perform_auto_baud(&mut self) -> io::Result<()> {
let data = [0x55u8, 0x55u8];
self.port.write_all(&data)?;
if !self.read_ack()? {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"couldn't synchronize bootloader baudrate",
));
}
log::debug!("Auto baud finished correctly");
Ok(())
}
fn init_communications(&mut self) -> io::Result<()> {
log::debug!("Sending dummy test command to check communication");
self.write_cmd(0, &[])?;
if self.read_ack().is_err() {
log::debug!("No response received, performing auto baud procedure");
self.perform_auto_baud()?;
}
Ok(())
}
pub fn ping(&mut self) -> io::Result<bool> {
self.write_cmd(constants::CMD_PING, &[])?;
self.read_ack()
}
pub fn download(
&mut self,
program_address: u32,
program_size: u32,
) -> io::Result<()> {
const CMD_DOWNLOAD_LEN: usize = 8;
let mut data = [0u8; CMD_DOWNLOAD_LEN];
(&mut data[..4]).copy_from_slice(&program_address.to_be_bytes());
(&mut data[4..]).copy_from_slice(&program_size.to_be_bytes());
self.write_cmd(constants::CMD_DOWNLOAD, &data)?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"COMMAND_DOWNLOAD not acknowledged",
));
}
Ok(())
}
pub fn get_status(&mut self) -> io::Result<u8> {
self.write_cmd(constants::CMD_GET_STATUS, &[])?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"COMMAND_GET_STATUS not acknowledged",
));
}
let mut response = [0u8; 1];
self.read_response(&mut response)?;
self.write_ack(true)?;
Ok(response[0])
}
pub fn send_data<D>(&mut self, data: &D) -> io::Result<bool>
where
D: AsRef<[u8]>,
{
assert!(data.as_ref().len() <= constants::MAX_BYTES_PER_TRANSFER);
self.write_cmd(constants::CMD_SEND_DATA, data)?;
self.read_ack()
}
pub fn get_chip_id(&mut self) -> io::Result<u32> {
const CHIP_ID_RESPONSE_LEN: usize = 4;
self.write_cmd(constants::CMD_GET_CHIP_ID, &[])?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"bootloader didn't acknowledge the command",
));
}
let mut response = [0u8; CHIP_ID_RESPONSE_LEN];
self.read_response(&mut response)?;
self.write_ack(true)?;
Ok(u32::from_be_bytes(response))
}
pub fn erase(&mut self, address: u32, byte_count: u32) -> io::Result<()> {
const CMD_ERASE_LEN: usize = 8;
if !self.family.supports_erase() {
panic!("`COMMAND_ERASE` is not supported");
}
let mut data = [0u8; CMD_ERASE_LEN];
(&mut data[..4]).copy_from_slice(&address.to_be_bytes());
(&mut data[4..]).copy_from_slice(&byte_count.to_be_bytes());
self.write_cmd(constants::CC2538_CMD_ERASE, &data)?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"failed to erase",
));
}
Ok(())
}
pub fn sector_erase(&mut self, address: u32) -> io::Result<()> {
const CMD_SECTOR_ERASE_LEN: usize = 4;
if !self.family.supports_sector_erase() {
panic!("`COMMAND_SECTOR_ERASE` is not supported");
}
assert!(
address % self.family.sector_size() == 0,
"invalid sector address"
);
let mut data = [0u8; CMD_SECTOR_ERASE_LEN];
data.copy_from_slice(&address.to_be_bytes());
self.write_cmd(constants::CC26X0_CMD_SECTOR_ERASE, &data)?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"failed to erase sector",
));
}
Ok(())
}
pub fn set_xosc(&mut self) -> io::Result<()> {
if !self.family.supports_set_xosc() {
panic!("XOSC switch is not supported");
}
self.write_cmd(constants::CC2538_CMD_SET_XOSC, &[])?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"failed to switch to xosc",
));
}
Ok(())
}
pub fn memory_read_32(
&mut self,
address: u32,
data: &mut [u8],
) -> io::Result<()> {
const MEMORY_READ_LEN: usize = 6;
if let Family::CC2538 = self.family {
panic!("32-bit memory accesses are only allowed on CC26xx");
}
assert!(
data.len() <= (63 * 4),
"only a maximum of 63 accesses can be done on word mode"
);
assert!(
data.len() % 4 == 0,
"number of bytes is not divisible from 4"
);
assert!(
(address & 0x03) == 0,
"memory address must be 32-bits aligned"
);
log::trace!(
"memory_read_32 `{}` elements at start address `{:#X}`",
data.len() / 4,
address
);
let mut cmd = [0u8; MEMORY_READ_LEN];
(&mut cmd[..4]).copy_from_slice(&address.to_be_bytes());
cmd[4] = 1;
cmd[5] = (data.len() / 4) as u8;
self.write_cmd(constants::CMD_MEMORY_READ, &cmd)?;
let ack = self.read_ack()?;
if !ack {
return Err(io::Error::new(
io::ErrorKind::Other,
"failed to read memory",
));
}
self.read_response(data)?;
self.write_ack(true)?;
Ok(())
}
}
impl<P> fmt::Debug for Device<P>
where
P: SerialPort,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("Device")
.field("family", &self.family)
.field("port", &())
.finish()
}
}
fn command_checksum(cmd: u8, data: &[u8]) -> u8 {
let mut checksum: u8 = cmd;
for byte in data {
checksum = checksum.overflowing_add(*byte).0;
}
checksum
}
pub fn port_settings() -> serial::PortSettings {
serial::PortSettings {
baud_rate: serial::BaudRate::Baud115200,
char_size: serial::CharSize::Bits8,
parity: serial::Parity::ParityNone,
stop_bits: serial::StopBits::Stop1,
flow_control: serial::FlowControl::FlowNone,
}
}
#[allow(clippy::needless_bool)]
pub fn invoke_bootloader<P>(
port: &mut P,
inverted: bool,
bootloader_active_high: bool,
) -> serial::Result<()>
where
P: SerialPort,
{
fn set_bootloader_pin<P: SerialPort>(
port: &mut P,
inverted: bool,
level: bool,
) -> serial::Result<()> {
if inverted {
port.set_rts(level)
} else {
port.set_dtr(level)
}
}
fn set_reset_pin<P: SerialPort>(
port: &mut P,
inverted: bool,
level: bool,
) -> serial::Result<()> {
if inverted {
port.set_dtr(level)
} else {
port.set_rts(level)
}
}
set_bootloader_pin(
port,
inverted,
if !bootloader_active_high { true } else { false },
)?;
set_reset_pin(port, inverted, false)?;
set_reset_pin(port, inverted, true)?;
set_reset_pin(port, inverted, false)?;
#[cfg(not(test))]
std::thread::sleep(Duration::from_millis(2));
set_bootloader_pin(
port,
inverted,
if !bootloader_active_high { false } else { true },
)?;
Ok(())
}
#[cfg(test)]
pub mod test {
use super::*;
#[test]
fn test_command_checksum() {
const DATA: &[u8] = &[0xde, 0xad, 0xbe, 0xef];
assert_eq!(command_checksum(0xCA, DATA), 0x02);
}
#[test]
#[allow(bare_trait_objects)]
fn test_invoke_bootloader() {
struct DummySerialPort {
rts_state: bool,
dtr_state: bool,
}
impl SerialPort for DummySerialPort {
fn timeout(&self) -> Duration {
unreachable!()
}
fn set_timeout(
&mut self,
_timeout: Duration,
) -> serial::Result<()> {
unreachable!()
}
fn configure(
&mut self,
_settings: &serial::PortSettings,
) -> serial::Result<()> {
unreachable!()
}
fn reconfigure(
&mut self,
_setup: &Fn(
&mut serial::SerialPortSettings,
) -> serial::Result<()>,
) -> serial::Result<()> {
unreachable!()
}
fn set_rts(&mut self, level: bool) -> serial::Result<()> {
self.rts_state = level;
Ok(())
}
fn set_dtr(&mut self, level: bool) -> serial::Result<()> {
self.dtr_state = level;
Ok(())
}
fn read_cts(&mut self) -> serial::Result<bool> {
unreachable!()
}
fn read_dsr(&mut self) -> serial::Result<bool> {
unreachable!()
}
fn read_ri(&mut self) -> serial::Result<bool> {
unreachable!()
}
fn read_cd(&mut self) -> serial::Result<bool> {
unreachable!()
}
}
impl io::Read for DummySerialPort {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
unreachable!()
}
}
impl io::Write for DummySerialPort {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
unreachable!()
}
fn flush(&mut self) -> io::Result<()> {
unreachable!()
}
}
let mut port = DummySerialPort {
rts_state: false,
dtr_state: false,
};
invoke_bootloader(&mut port, false, false).unwrap();
assert_eq!(port.rts_state, false);
assert_eq!(port.dtr_state, false);
port.rts_state = false;
port.dtr_state = false;
invoke_bootloader(&mut port, true, false).unwrap();
assert_eq!(port.rts_state, false);
assert_eq!(port.dtr_state, false);
port.rts_state = false;
port.dtr_state = true;
invoke_bootloader(&mut port, false, true).unwrap();
assert_eq!(port.rts_state, false);
assert_eq!(port.dtr_state, true);
port.rts_state = true;
port.dtr_state = false;
invoke_bootloader(&mut port, true, true).unwrap();
assert_eq!(port.rts_state, true);
assert_eq!(port.dtr_state, false);
}
}