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
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.