pubusecrate::protocol::PropertyTag; usecrate::protocol::{
CommandTag, FramingPacketType, ResponseTag, COMMAND_FLAG_HAS_DATA, COMMAND_MAX_PARAMS,
FRAMING_HEADER_SIZE, MIN_MAX_PACKET_SIZE, START_BYTE,
}; pubusecrate::transport::Transport; use anyhow::{anyhow, bail, Context as _, Result}; use std::fmt; use std::io; use std::time::{Duration, Instant};
// TODO(jrreinhart): Replace anyhow errors with custom error types using thiserror.
/// Represents a connected target device running the NXP MCU bootloader. pubstruct Device {
transport: Box<dyn Transport>,
max_packet_size: usize,
}
// Public API impl Device { pubfn new(transport: Box<dyn Transport>) -> Self {
Device {
transport, // Initial max packet size is the minimum boot ROM packet size. // This is updated to GetProperty(MaxPacketSize).
max_packet_size: MIN_MAX_PACKET_SIZE,
}
}
pubfn transport(&self) -> &dyn Transport {
&*self.transport
}
let max_packet_size = self.get_property_single(PropertyTag::MaxPacketSize)?;
log::info!("MaxPacketSize: 0x{max_packet_size:X}"); if (max_packet_size as usize) < self.max_packet_size {
bail!("MaxPacketSize (0x{max_packet_size:X}) too small!");
} self.max_packet_size = max_packet_size as usize;
// "The parameter count field in the header is set to greater than 1, to always include the // status code and one or many property values." if response.len() < 2 {
bail!("Unexpected response param count: {}", response.len());
}
let status_code: u32 = response[0]; let property_values = response.split_off(1);
if status_code != 0 {
bail!("Command returned status {status_code}");
}
// "The parameter count set to 2 for the status code and the data byte count parameters" if response.len() != 2 {
bail!("Unexpected response param count: {}", response.len());
}
let status_code: u32 = response[0]; if status_code != 0 {
bail!("Command returned status {status_code}");
}
let ret_byte_count = response[1] as usize; if ret_byte_count > byte_count as usize {
bail!("Returned byte count {ret_byte_count} > requested {byte_count}");
}
// Read data loop letmut result: Vec<u8> = Vec::with_capacity(ret_byte_count); while result.len() < ret_byte_count { letmut data = self.read_data_packet()?;
// "The sending side may abort the data phase early by sending // a zero-length data packet." if data.is_empty() {
log::warn!("Data phase aborted by sender"); break;
}
result.append(&mut data);
}
// Read final generic response self.read_generic_response(COMMAND)
.context("Reading final response")?;
/// Enables the IRQ notify function on the given port/pin tuple pubfn set_irqn_pin(&mutself, port: u8, pin: u8) -> Result<()> { // RT500RM 18.7.2.9 SPI In-System Programming // RT500RM 18.7.2.7.17 Supported properties in GetProperty and SetProperty // IRQ notifier pin: // bit[7:0] pin // bit[15:8] port // bit[31] enable let value: u32 = (1 << 31) | ((port as u32) << 8) | (pin as u32); self.set_property(PropertyTag::IrqNotifierPin, value)
}
/// Sets up the IRQ notify pin. /// /// This does two things: /// /// 1. Configures the MCU bootrom ISP to use the given port/pin for its IRQ notify output pin. /// 2. Configures the host-side transport to watch for IRQ notifications on the gpio line. /// /// # Arguments /// /// * `port` - The MCU GPIO port number of the IRQ pin. E.g. for PIO0_7, this is 0. /// * `pin` - The MCU GPIO pin number of the IRQ pin. E.g. for PIO0_7, this is 7. /// * `irq_line` - The GPIO line to monitor for IRQ notifications. pubfn setup_irqn(&mutself, port: u8, pin: u8, irq_line: &gpio_cdev::Line) -> Result<()> { // First, configure the MCU to use the IRQ Notifier Pin. self.set_irqn_pin(port, pin)
.context("Setting IRQ notifier pin")?;
// Then tell the transport to respect it. self.transport.set_irq_line(irq_line)?;
// Verify it works before going any further. self.ping().context("Verifying irq line")?;
/// Reads a packet which starts with a sync byte and a packet type. /// /// # Arguments /// /// * `size` - The size of the packet. Must be at least 2 as it includes /// the start byte and packet type. fn read_sync_packet_until(&mutself, size: usize, deadline: Instant) -> Result<SyncPacket> { // The buffer must be at least two bytes: // * Start byte (0x5A) // * Packet type
assert!(size >= 2); letmut buf: Vec<u8> = vec![0; size];
// Read the start byte and packet type byte let start_buf = buf.get_mut(0..2).unwrap(); // length checked on entry self.read_start_until(start_buf, deadline)
.context("Reading packet start")?;
assert_eq!(start_buf[0], START_BYTE); if start_buf[1] == START_BYTE {
bail!("Duplicate start byte");
}
// Read the remainder of the requested packet let rest = buf.get_mut(2..).unwrap(); // length checked on entry if !rest.is_empty() { self.read_until(rest, deadline)
.context("Reading rest of packet")?;
}
/// Reads and ACKs a framing packet with an expected packet type and returns the payload bytes. fn read_framing_packet(&mutself, expected_packet_type: FramingPacketType) -> Result<Vec<u8>> { let deadline = Instant::now() + Self::DEFAULT_TIMEOUT;
// First read the framing packet header // RT500RM Table 99. Framing Packet Format let header = self
.read_sync_packet_until(FRAMING_HEADER_SIZE, deadline)
.context("Reading framing packet")?;
// Decode the framing header let length = u16::from_le_bytes(header.data[2..4].try_into().unwrap()); let hdr_crc = u16::from_le_bytes(header.data[4..6].try_into().unwrap());
// Now read the payload letmut payload = vec![0; length.into()]; if length > 0 { self.read_until(payload.as_mut_slice(), deadline)
.context("Reading payload")?;
}
// Validate packet type
header.check_packet_type(expected_packet_type)?;
/// Sends a command and reads the ACK, with a deadline. fn send_command_until(&mutself, tag: CommandTag, params: &[u32], deadline: Instant) -> Result<()> { // TODO: This naming feels inconsistent let flags: u8 = 0; self.write_command_flags(tag, flags, params)?; self.read_ack_until(deadline)
}
/// Sends a command and reads the ACK. fn send_command(&mutself, tag: CommandTag, params: &[u32]) -> Result<()> { let deadline = Instant::now() + Self::DEFAULT_TIMEOUT; self.send_command_until(tag, params, deadline)
}
/// Sends a command, indicating a data phase, and reads the ACK. fn send_command_has_data(&mutself, tag: CommandTag, params: &[u32]) -> Result<()> { // TODO: This naming feels inconsistent let flags: u8 = COMMAND_FLAG_HAS_DATA; self.write_command_flags(tag, flags, params)?; self.read_ack()
}
/// Reads a response packet from the device and returns the "parameters". fn read_response_expect_data(
&mutself,
expected_tag: ResponseTag,
expect_data: bool,
) -> Result<Vec<u32>> { // RT500RM 18.7.2.6.6 Response packet // "The responses are carried using the same command packet format wrapped with framing // packet data." let response = self
.read_framing_packet(FramingPacketType::Command)
.context("Reading response packet")?;
// Decode the response packet header. // RT500RM 18.7.2.6.6 Response packet: "same command packet format": // RT500RM 18.7.2.6.5 Command packet let resp_tag = ResponseTag::try_from(response[0])
.map_err(|_| anyhow!("Unknown response tag: 0x{:02X}", response[0]))?; let flags = response[1]; let has_data: bool = (flags & COMMAND_FLAG_HAS_DATA) != 0; // [2] reserved let param_count = response[3] as usize;
if resp_tag != expected_tag {
bail!("Unexpected response tag {resp_tag:?}, expected {expected_tag:?}");
} if has_data != expect_data {
bail!( "Response {} have data; {} expect data", if has_data { "does" } else { "doesn't" }, if expect_data { "did" } else { "didn't" }
);
}
// Unpack the params let param_bytes = &response[4..]; if param_count * size_of::<u32>() != param_bytes.len() {
bail!( "Param count ({}) / param bytes ({}) mismatch",
param_count,
param_bytes.len()
);
} let params = param_bytes
.chunks(size_of::<u32>())
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
.collect();
// Table 106. GenericResponse Parameters // "The parameter count field in the header is always set to 2, for status // code and command tag parameters." if response.len() != 2 {
bail!("Unexpected response param count: {}", response.len())
}
let status_code: u32 = response[0];
let command_tag_val = u8::try_from(response[1]).map_err(|_| {
anyhow!( "Generic response command tag too large: 0x{:08X}",
response[1]
)
})?; let command_tag = CommandTag::try_from(command_tag_val).map_err(|_| {
anyhow!( "Unknown generic response command tag: 0x{:02X}",
response[1]
)
})?;
if command_tag != expected_command {
bail!( "Unexpected generic response command tag {:?}, expected {:?}",
command_tag,
expected_command
);
}
if status_code != 0 { // TODO: Return this an a custom error type
bail!( "Command {:?} returned status 0x{:08X}",
command_tag,
status_code
);
}
Ok(())
}
/// Writes a data packet and reads the ACK. fn write_data_packet(&mutself, data: &[u8]) -> Result<()> { self.write_framing_packet(FramingPacketType::Data, data)?; self.read_ack()
}
/// Reads a data packet and sends an ACK. fn read_data_packet(&mutself) -> Result<Vec<u8>> { self.read_framing_packet(FramingPacketType::Data)
.context("Reading data packet")
}
}
fn check_packet_type(&self, expected: FramingPacketType) -> Result<()> { let actual = self.packet_type()?; if actual != expected {
bail!( "Unexpected packet type: {:?}, expected {:?}",
actual,
expected
);
}
Ok(())
}
}
// RT500RM 18.7.2.6.4 CRC16 algorithm // "The CRC is computed over each byte in the framing packet header, excluding the crc16 field // itself, plus all of the payload bytes. The CRC algorithm is the XMODEM variant of CRC-16." // poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 check=0x31c3 const CRC: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_XMODEM);
#[cfg(test)] mod tests { usesuper::*; use std::collections::VecDeque; use std::io;
fn init_logger() { let _ = env_logger::builder() // Include all events in tests
.filter_level(log::LevelFilter::max()) // Ensure events are captured by `cargo test`
.is_test(true) // Ignore errors initializing the logger if tests race to configure it
.try_init();
}
#[derive(Default)] struct TestTransport { // Data is popped by read() // push_back(), pop_front()
read_q: VecDeque<u8>,
// Data is pushed by write() // push_back(), pop_front()
write_q: VecDeque<u8>,
}
pubfn take_written(&mutself, n: usize) -> Result<Vec<u8>> { if n > self.write_q.len() {
bail!( "Failed to take {} bytes from write queue; only {} avail: {:X?}",
n, self.write_q.len(), self.write_q
);
}
Ok(self.write_q.drain(0..n).collect::<Vec<u8>>())
}
device.test_transport().push_ack(); // Ack the WriteMemory command
device.test_transport().push_for_read(&GENERIC_RESPONSE); // Initial
device.test_transport().push_ack(); // Ack the data packet
device.test_transport().push_for_read(&GENERIC_RESPONSE); // Final
let result = device.read_memory(0x20000400, 0x10, Some(0))?;
device
.test_transport()
.assert_written(&EXPECTED_READ_MEM_COMMAND);
device.test_transport().assert_ack_written(); // Ack the read memory response
device.test_transport().assert_ack_written(); // Ack the data packet
device.test_transport().assert_ack_written(); // Ack the final generic response
device
.test_transport()
.assert_written(&EXPECTED_CONFIG_MEM_COMMAND);
device.test_transport().assert_ack_written(); // We should ack the generic response